terraform: wip on shadow graph, compiles
This commit is contained in:
parent
983f5f1e40
commit
35f13f9c52
|
@ -0,0 +1,174 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
||||
// ShadowEvalContext is an EvalContext that is used to "shadow" a real
|
||||
// eval context for comparing whether two separate graph executions result
|
||||
// in the same output.
|
||||
//
|
||||
// This eval context will never communicate with a real provider and will
|
||||
// never modify real state.
|
||||
type ShadowEvalContext interface {
|
||||
EvalContext
|
||||
|
||||
// Close should be called when the _real_ EvalContext operations
|
||||
// are complete. This will immediately end any blocks calls and record
|
||||
// any errors.
|
||||
//
|
||||
// The returned error is the result of the shadow run. If it is nil,
|
||||
// then the shadow run seemingly completed successfully. You should
|
||||
// still compare the resulting states, diffs from both the real and shadow
|
||||
// contexts to verify equivalent end state.
|
||||
//
|
||||
// If the error is non-nil, then an error occurred during the execution
|
||||
// itself. In this scenario, you should not compare diffs/states since
|
||||
// they can't be considered accurate since operations during execution
|
||||
// failed.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// NewShadowEvalContext creates a new shadowed EvalContext. This returns
|
||||
// the real EvalContext that should be used with the real evaluation and
|
||||
// will communicate with real providers and write real state as well as
|
||||
// the ShadowEvalContext that should be used with the test graph.
|
||||
//
|
||||
// This should be called before the ctx is ever used in order to ensure
|
||||
// a consistent shadow state.
|
||||
func NewShadowEvalContext(ctx EvalContext) (EvalContext, ShadowEvalContext) {
|
||||
real := &shadowEvalContextReal{EvalContext: ctx}
|
||||
|
||||
// Copy the diff. We do this using some weird scoping so that the
|
||||
// "diff" (real) value never leaks out and can be used.
|
||||
var diffCopy *Diff
|
||||
{
|
||||
diff, lock := ctx.Diff()
|
||||
lock.RLock()
|
||||
diffCopy = diff
|
||||
// TODO: diffCopy = diff.DeepCopy()
|
||||
lock.RUnlock()
|
||||
}
|
||||
|
||||
// Copy the state. We do this using some weird scoping so that the
|
||||
// "state" (real) value never leaks out and can be used.
|
||||
var stateCopy *State
|
||||
{
|
||||
state, lock := ctx.State()
|
||||
lock.RLock()
|
||||
stateCopy = state.DeepCopy()
|
||||
lock.RUnlock()
|
||||
}
|
||||
|
||||
// Build the shadow copy. For safety, we don't even give the shadow
|
||||
// copy a reference to the real context. This means that it would be
|
||||
// very difficult (impossible without some real obvious mistakes) for
|
||||
// the shadow context to do "real" work.
|
||||
shadow := &shadowEvalContextShadow{
|
||||
PathValue: ctx.Path(),
|
||||
StateValue: stateCopy,
|
||||
StateLock: new(sync.RWMutex),
|
||||
DiffValue: diffCopy,
|
||||
DiffLock: new(sync.RWMutex),
|
||||
}
|
||||
|
||||
return real, shadow
|
||||
}
|
||||
|
||||
// shadowEvalContextReal is the EvalContext that does real work.
|
||||
type shadowEvalContextReal struct {
|
||||
EvalContext
|
||||
}
|
||||
|
||||
// shadowEvalContextShadow is the EvalContext that shadows the real one
|
||||
// and leans on that for data.
|
||||
type shadowEvalContextShadow struct {
|
||||
PathValue []string
|
||||
Providers map[string]ResourceProvider
|
||||
DiffValue *Diff
|
||||
DiffLock *sync.RWMutex
|
||||
StateValue *State
|
||||
StateLock *sync.RWMutex
|
||||
|
||||
// Fields relating to closing the context. Closing signals that
|
||||
// the execution of the real context completed.
|
||||
closeLock sync.Mutex
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
// Shared is the shared state between the shadow and real contexts when
|
||||
// a shadow context is active. This is used by the real context to setup
|
||||
// some state, trigger condition variables, etc.
|
||||
type shadowEvalContextShared struct {
|
||||
// This lock must be held when modifying just about anything in this
|
||||
// structure. It is a "big" lock but the work done here is usually very
|
||||
// fast so we do this.
|
||||
sync.Mutex
|
||||
|
||||
// Providers is the map of active (initialized) providers.
|
||||
//
|
||||
// ProviderWaiters is the condition variable associated with waiting
|
||||
// for a provider to be initialized. If this is non-nil for a provider,
|
||||
// then that will be triggered when the provider is initialized.
|
||||
Providers map[string]struct{}
|
||||
ProviderWaiters map[string]*sync.Cond
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Close() error {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Path() []string {
|
||||
return c.PathValue
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Hook(f func(Hook) (HookAction, error)) error {
|
||||
// Don't do anything on hooks. Mission critical behavior should not
|
||||
// depend on hooks and at the time of writing it does not depend on
|
||||
// hooks. In the future we could also test hooks but not now.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Input() UIInput {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) InitProvider(n string) (ResourceProvider, error) {
|
||||
// Initialize our shadow provider here. We also wait for the
|
||||
// real context to initialize the same provider. If it doesn't
|
||||
// before close, then an error is reported.
|
||||
|
||||
// TODO: shadow provider
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Diff() (*Diff, *sync.RWMutex) {
|
||||
return c.DiffValue, c.DiffLock
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) State() (*State, *sync.RWMutex) {
|
||||
return c.StateValue, c.StateLock
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Provider(n string) ResourceProvider { return nil }
|
||||
func (c *shadowEvalContextShadow) CloseProvider(n string) error { return nil }
|
||||
func (c *shadowEvalContextShadow) ConfigureProvider(string, *ResourceConfig) error { return nil }
|
||||
func (c *shadowEvalContextShadow) SetProviderConfig(string, *ResourceConfig) error { return nil }
|
||||
func (c *shadowEvalContextShadow) ParentProviderConfig(string) *ResourceConfig { return nil }
|
||||
func (c *shadowEvalContextShadow) ProviderInput(string) map[string]interface{} { return nil }
|
||||
func (c *shadowEvalContextShadow) SetProviderInput(string, map[string]interface{}) {}
|
||||
func (c *shadowEvalContextShadow) InitProvisioner(string) (ResourceProvisioner, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *shadowEvalContextShadow) Provisioner(string) ResourceProvisioner { return nil }
|
||||
func (c *shadowEvalContextShadow) CloseProvisioner(string) error { return nil }
|
||||
func (c *shadowEvalContextShadow) Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *shadowEvalContextShadow) SetVariables(string, map[string]interface{}) {}
|
|
@ -0,0 +1,10 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShadowEvalContext_impl(t *testing.T) {
|
||||
var _ EvalContext = new(shadowEvalContextReal)
|
||||
var _ EvalContext = new(shadowEvalContextShadow)
|
||||
}
|
Loading…
Reference in New Issue