diff --git a/terraform/context.go b/terraform/context.go index a814a85dd..aaa77abf9 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/hcl" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" - "github.com/hashicorp/terraform/helper/experiment" ) // InputMode defines what sort of input will be asked for when Input @@ -465,7 +464,7 @@ func (c *Context) Input(mode InputMode) error { } // Do the walk - if _, err := c.walk(graph, nil, walkInput); err != nil { + if _, err := c.walk(graph, walkInput); err != nil { return err } } @@ -506,7 +505,7 @@ func (c *Context) Apply() (*State, error) { } // Walk the graph - walker, err := c.walk(graph, graph, operation) + walker, err := c.walk(graph, operation) if len(walker.ValidationErrors) > 0 { err = multierror.Append(err, walker.ValidationErrors...) } @@ -575,7 +574,7 @@ func (c *Context) Plan() (*Plan, error) { } // Do the walk - walker, err := c.walk(graph, graph, operation) + walker, err := c.walk(graph, operation) if err != nil { return nil, err } @@ -630,7 +629,7 @@ func (c *Context) Refresh() (*State, error) { } // Do the walk - if _, err := c.walk(graph, graph, walkRefresh); err != nil { + if _, err := c.walk(graph, walkRefresh); err != nil { return nil, err } @@ -705,7 +704,7 @@ func (c *Context) Validate() ([]string, []error) { } // Walk - walker, err := c.walk(graph, graph, walkValidate) + walker, err := c.walk(graph, walkValidate) if err != nil { return nil, multierror.Append(errs, err).Errors } @@ -792,33 +791,11 @@ func (c *Context) releaseRun() { c.runContext = nil } -func (c *Context) walk( - graph, shadow *Graph, operation walkOperation) (*ContextGraphWalker, error) { +func (c *Context) walk(graph *Graph, operation walkOperation) (*ContextGraphWalker, error) { // Keep track of the "real" context which is the context that does // the real work: talking to real providers, modifying real state, etc. realCtx := c - // If we don't want shadowing, remove it - if !experiment.Enabled(experiment.X_shadow) { - shadow = nil - } - - // Just log this so we can see it in a debug log - if !c.shadow { - log.Printf("[WARN] terraform: shadow graph disabled") - shadow = nil - } - - // If we have a shadow graph, walk that as well - var shadowCtx *Context - var shadowCloser Shadow - if shadow != nil { - // Build the shadow context. In the process, override the real context - // with the one that is wrapped so that the shadow context can verify - // the results of the real. - realCtx, shadowCtx, shadowCloser = newShadowContext(c) - } - log.Printf("[DEBUG] Starting graph walk: %s", operation.String()) walker := &ContextGraphWalker{ @@ -837,90 +814,6 @@ func (c *Context) walk( close(watchStop) <-watchWait - // If we have a shadow graph and we interrupted the real graph, then - // we just close the shadow and never verify it. It is non-trivial to - // recreate the exact execution state up until an interruption so this - // isn't supported with shadows at the moment. - if shadowCloser != nil && c.sh.Stopped() { - // Ignore the error result, there is nothing we could care about - shadowCloser.CloseShadow() - - // Set it to nil so we don't do anything - shadowCloser = nil - } - - // If we have a shadow graph, wait for that to complete. - if shadowCloser != nil { - // Build the graph walker for the shadow. We also wrap this in - // a panicwrap so that panics are captured. For the shadow graph, - // we just want panics to be normal errors rather than to crash - // Terraform. - shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{ - Context: shadowCtx, - Operation: operation, - }) - - // Kick off the shadow walk. This will block on any operations - // on the real walk so it is fine to start first. - log.Printf("[INFO] Starting shadow graph walk: %s", operation.String()) - shadowCh := make(chan error) - go func() { - shadowCh <- shadow.Walk(shadowWalker) - }() - - // Notify the shadow that we're done - if err := shadowCloser.CloseShadow(); err != nil { - c.shadowErr = multierror.Append(c.shadowErr, err) - } - - // Wait for the walk to end - log.Printf("[DEBUG] Waiting for shadow graph to complete...") - shadowWalkErr := <-shadowCh - - // Get any shadow errors - if err := shadowCloser.ShadowError(); err != nil { - c.shadowErr = multierror.Append(c.shadowErr, err) - } - - // Verify the contexts (compare) - if err := shadowContextVerify(realCtx, shadowCtx); err != nil { - c.shadowErr = multierror.Append(c.shadowErr, err) - } - - // At this point, if we're supposed to fail on error, then - // we PANIC. Some tests just verify that there is an error, - // so simply appending it to realErr and returning could hide - // shadow problems. - // - // This must be done BEFORE appending shadowWalkErr since the - // shadowWalkErr may include expected errors. - // - // We only do this if we don't have a real error. In the case of - // a real error, we can't guarantee what nodes were and weren't - // traversed in parallel scenarios so we can't guarantee no - // shadow errors. - if c.shadowErr != nil && contextFailOnShadowError && realErr == nil { - panic(multierror.Prefix(c.shadowErr, "shadow graph:")) - } - - // Now, if we have a walk error, we append that through - if shadowWalkErr != nil { - c.shadowErr = multierror.Append(c.shadowErr, shadowWalkErr) - } - - if c.shadowErr == nil { - log.Printf("[INFO] Shadow graph success!") - } else { - log.Printf("[ERROR] Shadow graph error: %s", c.shadowErr) - - // If we're supposed to fail on shadow errors, then report it - if contextFailOnShadowError { - realErr = multierror.Append(realErr, multierror.Prefix( - c.shadowErr, "shadow graph:")) - } - } - } - return walker, realErr } diff --git a/terraform/context_import.go b/terraform/context_import.go index f1d57760d..e94014319 100644 --- a/terraform/context_import.go +++ b/terraform/context_import.go @@ -66,7 +66,7 @@ func (c *Context) Import(opts *ImportOpts) (*State, error) { } // Walk it - if _, err := c.walk(graph, nil, walkImport); err != nil { + if _, err := c.walk(graph, walkImport); err != nil { return c.state, err } diff --git a/terraform/context_validate_test.go b/terraform/context_validate_test.go index 60cef142d..34a0c8615 100644 --- a/terraform/context_validate_test.go +++ b/terraform/context_validate_test.go @@ -1005,7 +1005,7 @@ func TestContext2Validate_PlanGraphBuilder(t *testing.T) { t.Fatalf("error attmepting to Build PlanGraphBuilder: %s", err) } defer c.acquireRun("validate-test")() - walker, err := c.walk(graph, graph, walkValidate) + walker, err := c.walk(graph, walkValidate) if err != nil { t.Fatal(err) } diff --git a/terraform/shadow.go b/terraform/shadow.go deleted file mode 100644 index 46325595f..000000000 --- a/terraform/shadow.go +++ /dev/null @@ -1,28 +0,0 @@ -package terraform - -// Shadow is the interface that any "shadow" structures must implement. -// -// A shadow structure is an interface implementation (typically) that -// shadows a real implementation and verifies that the same behavior occurs -// on both. The semantics of this behavior are up to the interface itself. -// -// A shadow NEVER modifies real values or state. It must always be safe to use. -// -// For example, a ResourceProvider shadow ensures that the same operations -// are done on the same resources with the same configurations. -// -// The typical usage of a shadow following this interface is to complete -// the real operations, then call CloseShadow which tells the shadow that -// the real side is done. Then, once the shadow is also complete, call -// ShadowError to find any errors that may have been caught. -type Shadow interface { - // CloseShadow tells the shadow that the REAL implementation is - // complete. Therefore, any calls that would block should now return - // immediately since no more changes will happen to the real side. - CloseShadow() error - - // ShadowError returns the errors that the shadow has found. - // This should be called AFTER CloseShadow and AFTER the shadow is - // known to be complete (no more calls to it). - ShadowError() error -} diff --git a/terraform/shadow_components.go b/terraform/shadow_components.go deleted file mode 100644 index 116cf84f9..000000000 --- a/terraform/shadow_components.go +++ /dev/null @@ -1,273 +0,0 @@ -package terraform - -import ( - "fmt" - "sync" - - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/helper/shadow" -) - -// newShadowComponentFactory creates a shadowed contextComponentFactory -// so that requests to create new components result in both a real and -// shadow side. -func newShadowComponentFactory( - f contextComponentFactory) (contextComponentFactory, *shadowComponentFactory) { - // Create the shared data - shared := &shadowComponentFactoryShared{contextComponentFactory: f} - - // Create the real side - real := &shadowComponentFactory{ - shadowComponentFactoryShared: shared, - } - - // Create the shadow - shadow := &shadowComponentFactory{ - shadowComponentFactoryShared: shared, - Shadow: true, - } - - return real, shadow -} - -// shadowComponentFactory is the shadow side. Any components created -// with this factory are fake and will not cause real work to happen. -// -// Unlike other shadowers, the shadow component factory will allow the -// shadow to create _any_ component even if it is never requested on the -// real side. This is because errors will happen later downstream as function -// calls are made to the shadows that are never matched on the real side. -type shadowComponentFactory struct { - *shadowComponentFactoryShared - - Shadow bool // True if this should return the shadow - lock sync.Mutex -} - -func (f *shadowComponentFactory) ResourceProvider( - n, uid string) (ResourceProvider, error) { - f.lock.Lock() - defer f.lock.Unlock() - - real, shadow, err := f.shadowComponentFactoryShared.ResourceProvider(n, uid) - var result ResourceProvider = real - if f.Shadow { - result = shadow - } - - return result, err -} - -func (f *shadowComponentFactory) ResourceProvisioner( - n, uid string) (ResourceProvisioner, error) { - f.lock.Lock() - defer f.lock.Unlock() - - real, shadow, err := f.shadowComponentFactoryShared.ResourceProvisioner(n, uid) - var result ResourceProvisioner = real - if f.Shadow { - result = shadow - } - - return result, err -} - -// CloseShadow is called when the _real_ side is complete. This will cause -// all future blocking operations to return immediately on the shadow to -// ensure the shadow also completes. -func (f *shadowComponentFactory) CloseShadow() error { - // If we aren't the shadow, just return - if !f.Shadow { - return nil - } - - // Lock ourselves so we don't modify state - f.lock.Lock() - defer f.lock.Unlock() - - // Grab our shared state - shared := f.shadowComponentFactoryShared - - // If we're already closed, its an error - if shared.closed { - return fmt.Errorf("component factory shadow already closed") - } - - // Close all the providers and provisioners and return the error - var result error - for _, n := range shared.providerKeys { - _, shadow, err := shared.ResourceProvider(n, n) - if err == nil && shadow != nil { - if err := shadow.CloseShadow(); err != nil { - result = multierror.Append(result, err) - } - } - } - - for _, n := range shared.provisionerKeys { - _, shadow, err := shared.ResourceProvisioner(n, n) - if err == nil && shadow != nil { - if err := shadow.CloseShadow(); err != nil { - result = multierror.Append(result, err) - } - } - } - - // Mark ourselves as closed - shared.closed = true - - return result -} - -func (f *shadowComponentFactory) ShadowError() error { - // If we aren't the shadow, just return - if !f.Shadow { - return nil - } - - // Lock ourselves so we don't modify state - f.lock.Lock() - defer f.lock.Unlock() - - // Grab our shared state - shared := f.shadowComponentFactoryShared - - // If we're not closed, its an error - if !shared.closed { - return fmt.Errorf("component factory must be closed to retrieve errors") - } - - // Close all the providers and provisioners and return the error - var result error - for _, n := range shared.providerKeys { - _, shadow, err := shared.ResourceProvider(n, n) - if err == nil && shadow != nil { - if err := shadow.ShadowError(); err != nil { - result = multierror.Append(result, err) - } - } - } - - for _, n := range shared.provisionerKeys { - _, shadow, err := shared.ResourceProvisioner(n, n) - if err == nil && shadow != nil { - if err := shadow.ShadowError(); err != nil { - result = multierror.Append(result, err) - } - } - } - - return result -} - -// shadowComponentFactoryShared is shared data between the two factories. -// -// It is NOT SAFE to run any function on this struct in parallel. Lock -// access to this struct. -type shadowComponentFactoryShared struct { - contextComponentFactory - - closed bool - providers shadow.KeyedValue - providerKeys []string - provisioners shadow.KeyedValue - provisionerKeys []string -} - -// shadowResourceProviderFactoryEntry is the entry that is stored in -// the Shadows key/value for a provider. -type shadowComponentFactoryProviderEntry struct { - Real ResourceProvider - Shadow shadowResourceProvider - Err error -} - -type shadowComponentFactoryProvisionerEntry struct { - Real ResourceProvisioner - Shadow shadowResourceProvisioner - Err error -} - -func (f *shadowComponentFactoryShared) ResourceProvider( - n, uid string) (ResourceProvider, shadowResourceProvider, error) { - // Determine if we already have a value - raw, ok := f.providers.ValueOk(uid) - if !ok { - // Build the entry - var entry shadowComponentFactoryProviderEntry - - // No value, initialize. Create the original - p, err := f.contextComponentFactory.ResourceProvider(n, uid) - if err != nil { - entry.Err = err - p = nil // Just to be sure - } - - if p != nil { - // Create the shadow - real, shadow := newShadowResourceProvider(p) - entry.Real = real - entry.Shadow = shadow - - if f.closed { - shadow.CloseShadow() - } - } - - // Store the value - f.providers.SetValue(uid, &entry) - f.providerKeys = append(f.providerKeys, uid) - raw = &entry - } - - // Read the entry - entry, ok := raw.(*shadowComponentFactoryProviderEntry) - if !ok { - return nil, nil, fmt.Errorf("Unknown value for shadow provider: %#v", raw) - } - - // Return - return entry.Real, entry.Shadow, entry.Err -} - -func (f *shadowComponentFactoryShared) ResourceProvisioner( - n, uid string) (ResourceProvisioner, shadowResourceProvisioner, error) { - // Determine if we already have a value - raw, ok := f.provisioners.ValueOk(uid) - if !ok { - // Build the entry - var entry shadowComponentFactoryProvisionerEntry - - // No value, initialize. Create the original - p, err := f.contextComponentFactory.ResourceProvisioner(n, uid) - if err != nil { - entry.Err = err - p = nil // Just to be sure - } - - if p != nil { - // For now, just create a mock since we don't support provisioners yet - real, shadow := newShadowResourceProvisioner(p) - entry.Real = real - entry.Shadow = shadow - - if f.closed { - shadow.CloseShadow() - } - } - - // Store the value - f.provisioners.SetValue(uid, &entry) - f.provisionerKeys = append(f.provisionerKeys, uid) - raw = &entry - } - - // Read the entry - entry, ok := raw.(*shadowComponentFactoryProvisionerEntry) - if !ok { - return nil, nil, fmt.Errorf("Unknown value for shadow provisioner: %#v", raw) - } - - // Return - return entry.Real, entry.Shadow, entry.Err -} diff --git a/terraform/shadow_context.go b/terraform/shadow_context.go deleted file mode 100644 index 5588af252..000000000 --- a/terraform/shadow_context.go +++ /dev/null @@ -1,158 +0,0 @@ -package terraform - -import ( - "fmt" - "strings" - - "github.com/hashicorp/go-multierror" - "github.com/mitchellh/copystructure" -) - -// newShadowContext creates a new context that will shadow the given context -// when walking the graph. The resulting context should be used _only once_ -// for a graph walk. -// -// The returned Shadow should be closed after the graph walk with the -// real context is complete. Errors from the shadow can be retrieved there. -// -// Most importantly, any operations done on the shadow context (the returned -// context) will NEVER affect the real context. All structures are deep -// copied, no real providers or resources are used, etc. -func newShadowContext(c *Context) (*Context, *Context, Shadow) { - // Copy the targets - targetRaw, err := copystructure.Copy(c.targets) - if err != nil { - panic(err) - } - - // Copy the variables - varRaw, err := copystructure.Copy(c.variables) - if err != nil { - panic(err) - } - - // Copy the provider inputs - providerInputRaw, err := copystructure.Copy(c.providerInputConfig) - if err != nil { - panic(err) - } - - // The factories - componentsReal, componentsShadow := newShadowComponentFactory(c.components) - - // Create the shadow - shadow := &Context{ - components: componentsShadow, - destroy: c.destroy, - diff: c.diff.DeepCopy(), - hooks: nil, - meta: c.meta, - module: c.module, - state: c.state.DeepCopy(), - targets: targetRaw.([]string), - variables: varRaw.(map[string]interface{}), - - // NOTE(mitchellh): This is not going to work for shadows that are - // testing that input results in the proper end state. At the time - // of writing, input is not used in any state-changing graph - // walks anyways, so this checks nothing. We set it to this to avoid - // any panics but even a "nil" value worked here. - uiInput: new(MockUIInput), - - // Hardcoded to 4 since parallelism in the shadow doesn't matter - // a ton since we're doing far less compared to the real side - // and our operations are MUCH faster. - parallelSem: NewSemaphore(4), - providerInputConfig: providerInputRaw.(map[string]map[string]interface{}), - } - - // Create the real context. This is effectively just a copy of - // the context given except we need to modify some of the values - // to point to the real side of a shadow so the shadow can compare values. - real := &Context{ - // The fields below are changed. - components: componentsReal, - - // The fields below are direct copies - destroy: c.destroy, - diff: c.diff, - // diffLock - no copy - hooks: c.hooks, - meta: c.meta, - module: c.module, - sh: c.sh, - state: c.state, - // stateLock - no copy - targets: c.targets, - uiInput: c.uiInput, - variables: c.variables, - - // l - no copy - parallelSem: c.parallelSem, - providerInputConfig: c.providerInputConfig, - runContext: c.runContext, - runContextCancel: c.runContextCancel, - shadowErr: c.shadowErr, - } - - return real, shadow, &shadowContextCloser{ - Components: componentsShadow, - } -} - -// shadowContextVerify takes the real and shadow context and verifies they -// have equal diffs and states. -func shadowContextVerify(real, shadow *Context) error { - var result error - - // The states compared must be pruned so they're minimal/clean - real.state.prune() - shadow.state.prune() - - // Compare the states - if !real.state.Equal(shadow.state) { - result = multierror.Append(result, fmt.Errorf( - "Real and shadow states do not match! "+ - "Real state:\n\n%s\n\n"+ - "Shadow state:\n\n%s\n\n", - real.state, shadow.state)) - } - - // Compare the diffs - if !real.diff.Equal(shadow.diff) { - result = multierror.Append(result, fmt.Errorf( - "Real and shadow diffs do not match! "+ - "Real diff:\n\n%s\n\n"+ - "Shadow diff:\n\n%s\n\n", - real.diff, shadow.diff)) - } - - return result -} - -// shadowContextCloser is the io.Closer returned by newShadowContext that -// closes all the shadows and returns the results. -type shadowContextCloser struct { - Components *shadowComponentFactory -} - -// Close closes the shadow context. -func (c *shadowContextCloser) CloseShadow() error { - return c.Components.CloseShadow() -} - -func (c *shadowContextCloser) ShadowError() error { - err := c.Components.ShadowError() - if err == nil { - return nil - } - - // This is a sad edge case: if the configuration contains uuid() at - // any point, we cannot reason aboyt the shadow execution. Tested - // with Context2Plan_shadowUuid. - if strings.Contains(err.Error(), "uuid()") { - err = nil - } - - return err -} diff --git a/terraform/shadow_resource_provider.go b/terraform/shadow_resource_provider.go deleted file mode 100644 index 9741d7e79..000000000 --- a/terraform/shadow_resource_provider.go +++ /dev/null @@ -1,815 +0,0 @@ -package terraform - -import ( - "fmt" - "log" - "sync" - - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/helper/shadow" -) - -// shadowResourceProvider implements ResourceProvider for the shadow -// eval context defined in eval_context_shadow.go. -// -// This is used to verify behavior with a real provider. This shouldn't -// be used directly. -type shadowResourceProvider interface { - ResourceProvider - Shadow -} - -// newShadowResourceProvider creates a new shadowed ResourceProvider. -// -// This will assume a well behaved real ResourceProvider. For example, -// it assumes that the `Resources` call underneath doesn't change values -// since once it is called on the real provider, it will be cached and -// returned in the shadow since number of calls to that shouldn't affect -// actual behavior. -// -// However, with calls like Apply, call order is taken into account, -// parameters are checked for equality, etc. -func newShadowResourceProvider(p ResourceProvider) (ResourceProvider, shadowResourceProvider) { - // Create the shared data - shared := shadowResourceProviderShared{} - - // Create the real provider that does actual work - real := &shadowResourceProviderReal{ - ResourceProvider: p, - Shared: &shared, - } - - // Create the shadow that watches the real value - shadow := &shadowResourceProviderShadow{ - Shared: &shared, - - resources: p.Resources(), - dataSources: p.DataSources(), - } - - return real, shadow -} - -// shadowResourceProviderReal is the real resource provider. Function calls -// to this will perform real work. This records the parameters and return -// values and call order for the shadow to reproduce. -type shadowResourceProviderReal struct { - ResourceProvider - - Shared *shadowResourceProviderShared -} - -func (p *shadowResourceProviderReal) Close() error { - var result error - if c, ok := p.ResourceProvider.(ResourceProviderCloser); ok { - result = c.Close() - } - - p.Shared.CloseErr.SetValue(result) - return result -} - -func (p *shadowResourceProviderReal) Input( - input UIInput, c *ResourceConfig) (*ResourceConfig, error) { - cCopy := c.DeepCopy() - - result, err := p.ResourceProvider.Input(input, c) - p.Shared.Input.SetValue(&shadowResourceProviderInput{ - Config: cCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) Validate(c *ResourceConfig) ([]string, []error) { - warns, errs := p.ResourceProvider.Validate(c) - p.Shared.Validate.SetValue(&shadowResourceProviderValidate{ - Config: c.DeepCopy(), - ResultWarn: warns, - ResultErr: errs, - }) - - return warns, errs -} - -func (p *shadowResourceProviderReal) Configure(c *ResourceConfig) error { - cCopy := c.DeepCopy() - - err := p.ResourceProvider.Configure(c) - p.Shared.Configure.SetValue(&shadowResourceProviderConfigure{ - Config: cCopy, - Result: err, - }) - - return err -} - -func (p *shadowResourceProviderReal) Stop() error { - return p.ResourceProvider.Stop() -} - -func (p *shadowResourceProviderReal) ValidateResource( - t string, c *ResourceConfig) ([]string, []error) { - key := t - configCopy := c.DeepCopy() - - // Real operation - warns, errs := p.ResourceProvider.ValidateResource(t, c) - - // Initialize to ensure we always have a wrapper with a lock - p.Shared.ValidateResource.Init( - key, &shadowResourceProviderValidateResourceWrapper{}) - - // Get the result - raw := p.Shared.ValidateResource.Value(key) - wrapper, ok := raw.(*shadowResourceProviderValidateResourceWrapper) - if !ok { - // If this fails then we just continue with our day... the shadow - // will fail to but there isn't much we can do. - log.Printf( - "[ERROR] unknown value in ValidateResource shadow value: %#v", raw) - return warns, errs - } - - // Lock the wrapper for writing and record our call - wrapper.Lock() - defer wrapper.Unlock() - - wrapper.Calls = append(wrapper.Calls, &shadowResourceProviderValidateResource{ - Config: configCopy, - Warns: warns, - Errors: errs, - }) - - // With it locked, call SetValue again so that it triggers WaitForChange - p.Shared.ValidateResource.SetValue(key, wrapper) - - // Return the result - return warns, errs -} - -func (p *shadowResourceProviderReal) Apply( - info *InstanceInfo, - state *InstanceState, - diff *InstanceDiff) (*InstanceState, error) { - // Thse have to be copied before the call since call can modify - stateCopy := state.DeepCopy() - diffCopy := diff.DeepCopy() - - result, err := p.ResourceProvider.Apply(info, state, diff) - p.Shared.Apply.SetValue(info.uniqueId(), &shadowResourceProviderApply{ - State: stateCopy, - Diff: diffCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) Diff( - info *InstanceInfo, - state *InstanceState, - desired *ResourceConfig) (*InstanceDiff, error) { - // Thse have to be copied before the call since call can modify - stateCopy := state.DeepCopy() - desiredCopy := desired.DeepCopy() - - result, err := p.ResourceProvider.Diff(info, state, desired) - p.Shared.Diff.SetValue(info.uniqueId(), &shadowResourceProviderDiff{ - State: stateCopy, - Desired: desiredCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) Refresh( - info *InstanceInfo, - state *InstanceState) (*InstanceState, error) { - // Thse have to be copied before the call since call can modify - stateCopy := state.DeepCopy() - - result, err := p.ResourceProvider.Refresh(info, state) - p.Shared.Refresh.SetValue(info.uniqueId(), &shadowResourceProviderRefresh{ - State: stateCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) ValidateDataSource( - t string, c *ResourceConfig) ([]string, []error) { - key := t - configCopy := c.DeepCopy() - - // Real operation - warns, errs := p.ResourceProvider.ValidateDataSource(t, c) - - // Initialize - p.Shared.ValidateDataSource.Init( - key, &shadowResourceProviderValidateDataSourceWrapper{}) - - // Get the result - raw := p.Shared.ValidateDataSource.Value(key) - wrapper, ok := raw.(*shadowResourceProviderValidateDataSourceWrapper) - if !ok { - // If this fails then we just continue with our day... the shadow - // will fail to but there isn't much we can do. - log.Printf( - "[ERROR] unknown value in ValidateDataSource shadow value: %#v", raw) - return warns, errs - } - - // Lock the wrapper for writing and record our call - wrapper.Lock() - defer wrapper.Unlock() - - wrapper.Calls = append(wrapper.Calls, &shadowResourceProviderValidateDataSource{ - Config: configCopy, - Warns: warns, - Errors: errs, - }) - - // Set it - p.Shared.ValidateDataSource.SetValue(key, wrapper) - - // Return the result - return warns, errs -} - -func (p *shadowResourceProviderReal) ReadDataDiff( - info *InstanceInfo, - desired *ResourceConfig) (*InstanceDiff, error) { - // These have to be copied before the call since call can modify - desiredCopy := desired.DeepCopy() - - result, err := p.ResourceProvider.ReadDataDiff(info, desired) - p.Shared.ReadDataDiff.SetValue(info.uniqueId(), &shadowResourceProviderReadDataDiff{ - Desired: desiredCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -func (p *shadowResourceProviderReal) ReadDataApply( - info *InstanceInfo, - diff *InstanceDiff) (*InstanceState, error) { - // Thse have to be copied before the call since call can modify - diffCopy := diff.DeepCopy() - - result, err := p.ResourceProvider.ReadDataApply(info, diff) - p.Shared.ReadDataApply.SetValue(info.uniqueId(), &shadowResourceProviderReadDataApply{ - Diff: diffCopy, - Result: result.DeepCopy(), - ResultErr: err, - }) - - return result, err -} - -// shadowResourceProviderShadow is the shadow resource provider. Function -// calls never affect real resources. This is paired with the "real" side -// which must be called properly to enable recording. -type shadowResourceProviderShadow struct { - Shared *shadowResourceProviderShared - - // Cached values that are expected to not change - resources []ResourceType - dataSources []DataSource - - Error error // Error is the list of errors from the shadow - ErrorLock sync.Mutex -} - -type shadowResourceProviderShared struct { - // NOTE: Anytime a value is added here, be sure to add it to - // the Close() method so that it is closed. - - CloseErr shadow.Value - Input shadow.Value - Validate shadow.Value - Configure shadow.Value - ValidateResource shadow.KeyedValue - Apply shadow.KeyedValue - Diff shadow.KeyedValue - Refresh shadow.KeyedValue - ValidateDataSource shadow.KeyedValue - ReadDataDiff shadow.KeyedValue - ReadDataApply shadow.KeyedValue -} - -func (p *shadowResourceProviderShared) Close() error { - return shadow.Close(p) -} - -func (p *shadowResourceProviderShadow) CloseShadow() error { - err := p.Shared.Close() - if err != nil { - err = fmt.Errorf("close error: %s", err) - } - - return err -} - -func (p *shadowResourceProviderShadow) ShadowError() error { - return p.Error -} - -func (p *shadowResourceProviderShadow) Resources() []ResourceType { - return p.resources -} - -func (p *shadowResourceProviderShadow) DataSources() []DataSource { - return p.dataSources -} - -func (p *shadowResourceProviderShadow) Close() error { - v := p.Shared.CloseErr.Value() - if v == nil { - return nil - } - - return v.(error) -} - -func (p *shadowResourceProviderShadow) Input( - input UIInput, c *ResourceConfig) (*ResourceConfig, error) { - // Get the result of the input call - raw := p.Shared.Input.Value() - if raw == nil { - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderInput) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'input' shadow value: %#v", raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !c.Equal(result.Config) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Input had unequal configurations (real, then shadow):\n\n%#v\n\n%#v", - result.Config, c)) - p.ErrorLock.Unlock() - } - - // Return the results - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) Validate(c *ResourceConfig) ([]string, []error) { - // Get the result of the validate call - raw := p.Shared.Validate.Value() - if raw == nil { - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderValidate) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'validate' shadow value: %#v", raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !c.Equal(result.Config) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Validate had unequal configurations (real, then shadow):\n\n%#v\n\n%#v", - result.Config, c)) - p.ErrorLock.Unlock() - } - - // Return the results - return result.ResultWarn, result.ResultErr -} - -func (p *shadowResourceProviderShadow) Configure(c *ResourceConfig) error { - // Get the result of the call - raw := p.Shared.Configure.Value() - if raw == nil { - return nil - } - - result, ok := raw.(*shadowResourceProviderConfigure) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'configure' shadow value: %#v", raw)) - return nil - } - - // Compare the parameters, which should be identical - if !c.Equal(result.Config) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Configure had unequal configurations (real, then shadow):\n\n%#v\n\n%#v", - result.Config, c)) - p.ErrorLock.Unlock() - } - - // Return the results - return result.Result -} - -// Stop returns immediately. -func (p *shadowResourceProviderShadow) Stop() error { - return nil -} - -func (p *shadowResourceProviderShadow) ValidateResource(t string, c *ResourceConfig) ([]string, []error) { - // Unique key - key := t - - // Get the initial value - raw := p.Shared.ValidateResource.Value(key) - - // Find a validation with our configuration - var result *shadowResourceProviderValidateResource - for { - // Get the value - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ValidateResource' call for %q:\n\n%#v", - key, c)) - return nil, nil - } - - wrapper, ok := raw.(*shadowResourceProviderValidateResourceWrapper) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ValidateResource' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Look for the matching call with our configuration - wrapper.RLock() - for _, call := range wrapper.Calls { - if call.Config.Equal(c) { - result = call - break - } - } - wrapper.RUnlock() - - // If we found a result, exit - if result != nil { - break - } - - // Wait for a change so we can get the wrapper again - raw = p.Shared.ValidateResource.WaitForChange(key) - } - - return result.Warns, result.Errors -} - -func (p *shadowResourceProviderShadow) Apply( - info *InstanceInfo, - state *InstanceState, - diff *InstanceDiff) (*InstanceState, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.Apply.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'apply' call for %q:\n\n%#v\n\n%#v", - key, state, diff)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderApply) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'apply' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !state.Equal(result.State) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Apply %q: state had unequal states (real, then shadow):\n\n%#v\n\n%#v", - key, result.State, state)) - p.ErrorLock.Unlock() - } - - if !diff.Equal(result.Diff) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Apply %q: unequal diffs (real, then shadow):\n\n%#v\n\n%#v", - key, result.Diff, diff)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) Diff( - info *InstanceInfo, - state *InstanceState, - desired *ResourceConfig) (*InstanceDiff, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.Diff.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'diff' call for %q:\n\n%#v\n\n%#v", - key, state, desired)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderDiff) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'diff' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !state.Equal(result.State) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Diff %q had unequal states (real, then shadow):\n\n%#v\n\n%#v", - key, result.State, state)) - p.ErrorLock.Unlock() - } - if !desired.Equal(result.Desired) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Diff %q had unequal states (real, then shadow):\n\n%#v\n\n%#v", - key, result.Desired, desired)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) Refresh( - info *InstanceInfo, - state *InstanceState) (*InstanceState, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.Refresh.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'refresh' call for %q:\n\n%#v", - key, state)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderRefresh) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'refresh' shadow value: %#v", raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !state.Equal(result.State) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Refresh %q had unequal states (real, then shadow):\n\n%#v\n\n%#v", - key, result.State, state)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) ValidateDataSource( - t string, c *ResourceConfig) ([]string, []error) { - // Unique key - key := t - - // Get the initial value - raw := p.Shared.ValidateDataSource.Value(key) - - // Find a validation with our configuration - var result *shadowResourceProviderValidateDataSource - for { - // Get the value - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ValidateDataSource' call for %q:\n\n%#v", - key, c)) - return nil, nil - } - - wrapper, ok := raw.(*shadowResourceProviderValidateDataSourceWrapper) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ValidateDataSource' shadow value: %#v", raw)) - return nil, nil - } - - // Look for the matching call with our configuration - wrapper.RLock() - for _, call := range wrapper.Calls { - if call.Config.Equal(c) { - result = call - break - } - } - wrapper.RUnlock() - - // If we found a result, exit - if result != nil { - break - } - - // Wait for a change so we can get the wrapper again - raw = p.Shared.ValidateDataSource.WaitForChange(key) - } - - return result.Warns, result.Errors -} - -func (p *shadowResourceProviderShadow) ReadDataDiff( - info *InstanceInfo, - desired *ResourceConfig) (*InstanceDiff, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.ReadDataDiff.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ReadDataDiff' call for %q:\n\n%#v", - key, desired)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderReadDataDiff) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ReadDataDiff' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !desired.Equal(result.Desired) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "ReadDataDiff %q had unequal configs (real, then shadow):\n\n%#v\n\n%#v", - key, result.Desired, desired)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) ReadDataApply( - info *InstanceInfo, - d *InstanceDiff) (*InstanceState, error) { - // Unique key - key := info.uniqueId() - raw := p.Shared.ReadDataApply.Value(key) - if raw == nil { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ReadDataApply' call for %q:\n\n%#v", - key, d)) - return nil, nil - } - - result, ok := raw.(*shadowResourceProviderReadDataApply) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'ReadDataApply' shadow value for %q: %#v", key, raw)) - return nil, nil - } - - // Compare the parameters, which should be identical - if !d.Equal(result.Diff) { - p.ErrorLock.Lock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "ReadDataApply: unequal diffs (real, then shadow):\n\n%#v\n\n%#v", - result.Diff, d)) - p.ErrorLock.Unlock() - } - - return result.Result, result.ResultErr -} - -func (p *shadowResourceProviderShadow) ImportState(info *InstanceInfo, id string) ([]*InstanceState, error) { - panic("import not supported by shadow graph") -} - -// The structs for the various function calls are put below. These structs -// are used to carry call information across the real/shadow boundaries. - -type shadowResourceProviderInput struct { - Config *ResourceConfig - Result *ResourceConfig - ResultErr error -} - -type shadowResourceProviderValidate struct { - Config *ResourceConfig - ResultWarn []string - ResultErr []error -} - -type shadowResourceProviderConfigure struct { - Config *ResourceConfig - Result error -} - -type shadowResourceProviderValidateResourceWrapper struct { - sync.RWMutex - - Calls []*shadowResourceProviderValidateResource -} - -type shadowResourceProviderValidateResource struct { - Config *ResourceConfig - Warns []string - Errors []error -} - -type shadowResourceProviderApply struct { - State *InstanceState - Diff *InstanceDiff - Result *InstanceState - ResultErr error -} - -type shadowResourceProviderDiff struct { - State *InstanceState - Desired *ResourceConfig - Result *InstanceDiff - ResultErr error -} - -type shadowResourceProviderRefresh struct { - State *InstanceState - Result *InstanceState - ResultErr error -} - -type shadowResourceProviderValidateDataSourceWrapper struct { - sync.RWMutex - - Calls []*shadowResourceProviderValidateDataSource -} - -type shadowResourceProviderValidateDataSource struct { - Config *ResourceConfig - Warns []string - Errors []error -} - -type shadowResourceProviderReadDataDiff struct { - Desired *ResourceConfig - Result *InstanceDiff - ResultErr error -} - -type shadowResourceProviderReadDataApply struct { - Diff *InstanceDiff - Result *InstanceState - ResultErr error -} diff --git a/terraform/shadow_resource_provider_test.go b/terraform/shadow_resource_provider_test.go deleted file mode 100644 index be2fba0ea..000000000 --- a/terraform/shadow_resource_provider_test.go +++ /dev/null @@ -1,531 +0,0 @@ -package terraform - -import ( - "fmt" - "reflect" - "testing" - "time" -) - -func TestShadowResourceProvider_impl(t *testing.T) { - var _ Shadow = new(shadowResourceProviderShadow) -} - -func TestShadowResourceProvider_cachedValues(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Resources - { - actual := shadow.Resources() - expected := real.Resources() - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad:\n\n%#v\n\n%#v", actual, expected) - } - } - - // DataSources - { - actual := shadow.DataSources() - expected := real.DataSources() - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad:\n\n%#v\n\n%#v", actual, expected) - } - } -} - -func TestShadowResourceProviderInput(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - ui := new(MockUIInput) - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnConfig := testResourceConfig(t, map[string]interface{}{ - "bar": "baz", - }) - - // Configure the mock - mock.InputReturnConfig = returnConfig - - // Verify that it blocks until the real input is called - var actual *ResourceConfig - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - actual, err = shadow.Input(ui, config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real input - realResult, realErr := real.Input(ui, config) - if !realResult.Equal(returnConfig) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %s", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !actual.Equal(returnConfig) { - t.Fatalf("bad: %#v", actual) - } - if err != nil { - t.Fatalf("bad: %s", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderInput_badInput(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - ui := new(MockUIInput) - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - configBad := testResourceConfig(t, map[string]interface{}{ - "foo": "nope", - }) - - // Call the real with one - real.Input(ui, config) - - // Call the shadow with another - _, err := shadow.Input(ui, configBad) - if err != nil { - t.Fatalf("bad: %s", err) - } - - // Verify we have an error - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err == nil { - t.Fatal("should error") - } -} - -func TestShadowResourceProviderValidate(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnWarns := []string{"foo"} - returnErrs := []error{fmt.Errorf("bar")} - - // Configure the mock - mock.ValidateReturnWarns = returnWarns - mock.ValidateReturnErrors = returnErrs - - // Verify that it blocks until the real func is called - var warns []string - var errs []error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - warns, errs = shadow.Validate(config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realWarns, realErrs := real.Validate(config) - if !reflect.DeepEqual(realWarns, returnWarns) { - t.Fatalf("bad: %#v", realWarns) - } - if !reflect.DeepEqual(realErrs, returnErrs) { - t.Fatalf("bad: %#v", realWarns) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(warns, returnWarns) { - t.Fatalf("bad: %#v", warns) - } - if !reflect.DeepEqual(errs, returnErrs) { - t.Fatalf("bad: %#v", errs) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderValidate_badInput(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - configBad := testResourceConfig(t, map[string]interface{}{ - "foo": "nope", - }) - - // Call the real with one - real.Validate(config) - - // Call the shadow with another - shadow.Validate(configBad) - - // Verify we have an error - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err == nil { - t.Fatal("should error") - } -} - -func TestShadowResourceProviderConfigure(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnErr := fmt.Errorf("bar") - - // Configure the mock - mock.ConfigureReturnError = returnErr - - // Verify that it blocks until the real func is called - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - err = shadow.Configure(config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realErr := real.Configure(config) - if !reflect.DeepEqual(realErr, returnErr) { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(err, returnErr) { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderConfigure_badInput(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - configBad := testResourceConfig(t, map[string]interface{}{ - "foo": "nope", - }) - - // Call the real with one - real.Configure(config) - - // Call the shadow with another - shadow.Configure(configBad) - - // Verify we have an error - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err == nil { - t.Fatal("should error") - } -} - -func TestShadowResourceProviderApply(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: "foo"} - diff := &InstanceDiff{Destroy: true} - mockResult := &InstanceState{ID: "bar"} - - // Configure the mock - mock.ApplyReturn = mockResult - - // Verify that it blocks until the real func is called - var result *InstanceState - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - result, err = shadow.Apply(info, state, diff) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realResult, realErr := real.Apply(info, state, diff) - if !realResult.Equal(mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !result.Equal(mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderApply_modifyDiff(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: "foo"} - diff := &InstanceDiff{} - mockResult := &InstanceState{ID: "foo"} - - // Configure the mock - mock.ApplyFn = func( - info *InstanceInfo, - s *InstanceState, d *InstanceDiff) (*InstanceState, error) { - d.Destroy = true - return s, nil - } - - // Call the real func - realResult, realErr := real.Apply(info, state.DeepCopy(), diff.DeepCopy()) - if !realResult.Equal(mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // Verify the shadow returned the same values - result, err := shadow.Apply(info, state.DeepCopy(), diff.DeepCopy()) - if !result.Equal(mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderApply_modifyState(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: ""} - diff := &InstanceDiff{} - mockResult := &InstanceState{ID: "foo"} - - // Configure the mock - mock.ApplyFn = func( - info *InstanceInfo, - s *InstanceState, d *InstanceDiff) (*InstanceState, error) { - s.ID = "foo" - return s, nil - } - - // Call the real func - realResult, realErr := real.Apply(info, state.DeepCopy(), diff) - if !realResult.Equal(mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // Verify the shadow returned the same values - result, err := shadow.Apply(info, state.DeepCopy(), diff) - if !result.Equal(mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderDiff(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: "foo"} - desired := testResourceConfig(t, map[string]interface{}{"foo": "bar"}) - mockResult := &InstanceDiff{Destroy: true} - - // Configure the mock - mock.DiffReturn = mockResult - - // Verify that it blocks until the real func is called - var result *InstanceDiff - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - result, err = shadow.Diff(info, state, desired) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realResult, realErr := real.Diff(info, state, desired) - if !reflect.DeepEqual(realResult, mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(result, mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProviderRefresh(t *testing.T) { - mock := new(MockResourceProvider) - real, shadow := newShadowResourceProvider(mock) - - // Test values - info := &InstanceInfo{Id: "foo"} - state := &InstanceState{ID: "foo"} - mockResult := &InstanceState{ID: "bar"} - - // Configure the mock - mock.RefreshReturn = mockResult - - // Verify that it blocks until the real func is called - var result *InstanceState - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - result, err = shadow.Refresh(info, state) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realResult, realErr := real.Refresh(info, state) - if !realResult.Equal(mockResult) { - t.Fatalf("bad: %#v", realResult) - } - if realErr != nil { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !result.Equal(mockResult) { - t.Fatalf("bad: %#v", result) - } - if err != nil { - t.Fatalf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} diff --git a/terraform/shadow_resource_provisioner.go b/terraform/shadow_resource_provisioner.go deleted file mode 100644 index 60a490889..000000000 --- a/terraform/shadow_resource_provisioner.go +++ /dev/null @@ -1,282 +0,0 @@ -package terraform - -import ( - "fmt" - "io" - "log" - "sync" - - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/helper/shadow" -) - -// shadowResourceProvisioner implements ResourceProvisioner for the shadow -// eval context defined in eval_context_shadow.go. -// -// This is used to verify behavior with a real provisioner. This shouldn't -// be used directly. -type shadowResourceProvisioner interface { - ResourceProvisioner - Shadow -} - -// newShadowResourceProvisioner creates a new shadowed ResourceProvisioner. -func newShadowResourceProvisioner( - p ResourceProvisioner) (ResourceProvisioner, shadowResourceProvisioner) { - // Create the shared data - shared := shadowResourceProvisionerShared{ - Validate: shadow.ComparedValue{ - Func: shadowResourceProvisionerValidateCompare, - }, - } - - // Create the real provisioner that does actual work - real := &shadowResourceProvisionerReal{ - ResourceProvisioner: p, - Shared: &shared, - } - - // Create the shadow that watches the real value - shadow := &shadowResourceProvisionerShadow{ - Shared: &shared, - } - - return real, shadow -} - -// shadowResourceProvisionerReal is the real resource provisioner. Function calls -// to this will perform real work. This records the parameters and return -// values and call order for the shadow to reproduce. -type shadowResourceProvisionerReal struct { - ResourceProvisioner - - Shared *shadowResourceProvisionerShared -} - -func (p *shadowResourceProvisionerReal) Close() error { - var result error - if c, ok := p.ResourceProvisioner.(ResourceProvisionerCloser); ok { - result = c.Close() - } - - p.Shared.CloseErr.SetValue(result) - return result -} - -func (p *shadowResourceProvisionerReal) Validate(c *ResourceConfig) ([]string, []error) { - warns, errs := p.ResourceProvisioner.Validate(c) - p.Shared.Validate.SetValue(&shadowResourceProvisionerValidate{ - Config: c, - ResultWarn: warns, - ResultErr: errs, - }) - - return warns, errs -} - -func (p *shadowResourceProvisionerReal) Apply( - output UIOutput, s *InstanceState, c *ResourceConfig) error { - err := p.ResourceProvisioner.Apply(output, s, c) - - // Write the result, grab a lock for writing. This should nver - // block long since the operations below don't block. - p.Shared.ApplyLock.Lock() - defer p.Shared.ApplyLock.Unlock() - - key := s.ID - raw, ok := p.Shared.Apply.ValueOk(key) - if !ok { - // Setup a new value - raw = &shadow.ComparedValue{ - Func: shadowResourceProvisionerApplyCompare, - } - - // Set it - p.Shared.Apply.SetValue(key, raw) - } - - compareVal, ok := raw.(*shadow.ComparedValue) - if !ok { - // Just log and return so that we don't cause the real side - // any side effects. - log.Printf("[ERROR] unknown value in 'apply': %#v", raw) - return err - } - - // Write the resulting value - compareVal.SetValue(&shadowResourceProvisionerApply{ - Config: c, - ResultErr: err, - }) - - return err -} - -func (p *shadowResourceProvisionerReal) Stop() error { - return p.ResourceProvisioner.Stop() -} - -// shadowResourceProvisionerShadow is the shadow resource provisioner. Function -// calls never affect real resources. This is paired with the "real" side -// which must be called properly to enable recording. -type shadowResourceProvisionerShadow struct { - Shared *shadowResourceProvisionerShared - - Error error // Error is the list of errors from the shadow - ErrorLock sync.Mutex -} - -type shadowResourceProvisionerShared struct { - // NOTE: Anytime a value is added here, be sure to add it to - // the Close() method so that it is closed. - - CloseErr shadow.Value - Validate shadow.ComparedValue - Apply shadow.KeyedValue - ApplyLock sync.Mutex // For writing only -} - -func (p *shadowResourceProvisionerShared) Close() error { - closers := []io.Closer{ - &p.CloseErr, - } - - for _, c := range closers { - // This should never happen, but we don't panic because a panic - // could affect the real behavior of Terraform and a shadow should - // never be able to do that. - if err := c.Close(); err != nil { - return err - } - } - - return nil -} - -func (p *shadowResourceProvisionerShadow) CloseShadow() error { - err := p.Shared.Close() - if err != nil { - err = fmt.Errorf("close error: %s", err) - } - - return err -} - -func (p *shadowResourceProvisionerShadow) ShadowError() error { - return p.Error -} - -func (p *shadowResourceProvisionerShadow) Close() error { - v := p.Shared.CloseErr.Value() - if v == nil { - return nil - } - - return v.(error) -} - -func (p *shadowResourceProvisionerShadow) Validate(c *ResourceConfig) ([]string, []error) { - // Get the result of the validate call - raw := p.Shared.Validate.Value(c) - if raw == nil { - return nil, nil - } - - result, ok := raw.(*shadowResourceProvisionerValidate) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'validate' shadow value: %#v", raw)) - return nil, nil - } - - // We don't need to compare configurations because we key on the - // configuration so just return right away. - return result.ResultWarn, result.ResultErr -} - -func (p *shadowResourceProvisionerShadow) Apply( - output UIOutput, s *InstanceState, c *ResourceConfig) error { - // Get the value based on the key - key := s.ID - raw := p.Shared.Apply.Value(key) - if raw == nil { - return nil - } - - compareVal, ok := raw.(*shadow.ComparedValue) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'apply' shadow value: %#v", raw)) - return nil - } - - // With the compared value, we compare against our config - raw = compareVal.Value(c) - if raw == nil { - return nil - } - - result, ok := raw.(*shadowResourceProvisionerApply) - if !ok { - p.ErrorLock.Lock() - defer p.ErrorLock.Unlock() - p.Error = multierror.Append(p.Error, fmt.Errorf( - "Unknown 'apply' shadow value: %#v", raw)) - return nil - } - - return result.ResultErr -} - -func (p *shadowResourceProvisionerShadow) Stop() error { - // For the shadow, we always just return nil since a Stop indicates - // that we were interrupted and shadows are disabled during interrupts - // anyways. - return nil -} - -// The structs for the various function calls are put below. These structs -// are used to carry call information across the real/shadow boundaries. - -type shadowResourceProvisionerValidate struct { - Config *ResourceConfig - ResultWarn []string - ResultErr []error -} - -type shadowResourceProvisionerApply struct { - Config *ResourceConfig - ResultErr error -} - -func shadowResourceProvisionerValidateCompare(k, v interface{}) bool { - c, ok := k.(*ResourceConfig) - if !ok { - return false - } - - result, ok := v.(*shadowResourceProvisionerValidate) - if !ok { - return false - } - - return c.Equal(result.Config) -} - -func shadowResourceProvisionerApplyCompare(k, v interface{}) bool { - c, ok := k.(*ResourceConfig) - if !ok { - return false - } - - result, ok := v.(*shadowResourceProvisionerApply) - if !ok { - return false - } - - return c.Equal(result.Config) -} diff --git a/terraform/shadow_resource_provisioner_test.go b/terraform/shadow_resource_provisioner_test.go deleted file mode 100644 index 7e37d264a..000000000 --- a/terraform/shadow_resource_provisioner_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package terraform - -import ( - "errors" - "fmt" - "reflect" - "testing" - "time" -) - -func TestShadowResourceProvisioner_impl(t *testing.T) { - var _ Shadow = new(shadowResourceProvisionerShadow) -} - -func TestShadowResourceProvisionerValidate(t *testing.T) { - mock := new(MockResourceProvisioner) - real, shadow := newShadowResourceProvisioner(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnWarns := []string{"foo"} - returnErrs := []error{fmt.Errorf("bar")} - - // Configure the mock - mock.ValidateReturnWarns = returnWarns - mock.ValidateReturnErrors = returnErrs - - // Verify that it blocks until the real func is called - var warns []string - var errs []error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - warns, errs = shadow.Validate(config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realWarns, realErrs := real.Validate(config) - if !reflect.DeepEqual(realWarns, returnWarns) { - t.Fatalf("bad: %#v", realWarns) - } - if !reflect.DeepEqual(realErrs, returnErrs) { - t.Fatalf("bad: %#v", realWarns) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(warns, returnWarns) { - t.Fatalf("bad: %#v", warns) - } - if !reflect.DeepEqual(errs, returnErrs) { - t.Fatalf("bad: %#v", errs) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProvisionerValidate_diff(t *testing.T) { - mock := new(MockResourceProvisioner) - real, shadow := newShadowResourceProvisioner(mock) - - // Test values - config := testResourceConfig(t, map[string]interface{}{ - "foo": "bar", - }) - returnWarns := []string{"foo"} - returnErrs := []error{fmt.Errorf("bar")} - - // Configure the mock - mock.ValidateReturnWarns = returnWarns - mock.ValidateReturnErrors = returnErrs - - // Run a real validation with a config - real.Validate(testResourceConfig(t, map[string]interface{}{"bar": "baz"})) - - // Verify that it blocks until the real func is called - var warns []string - var errs []error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - warns, errs = shadow.Validate(config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realWarns, realErrs := real.Validate(config) - if !reflect.DeepEqual(realWarns, returnWarns) { - t.Fatalf("bad: %#v", realWarns) - } - if !reflect.DeepEqual(realErrs, returnErrs) { - t.Fatalf("bad: %#v", realWarns) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if !reflect.DeepEqual(warns, returnWarns) { - t.Fatalf("bad: %#v", warns) - } - if !reflect.DeepEqual(errs, returnErrs) { - t.Fatalf("bad: %#v", errs) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } -} - -func TestShadowResourceProvisionerApply(t *testing.T) { - mock := new(MockResourceProvisioner) - real, shadow := newShadowResourceProvisioner(mock) - - // Test values - output := new(MockUIOutput) - state := &InstanceState{ID: "foo"} - config := testResourceConfig(t, map[string]interface{}{"foo": "bar"}) - mockReturn := errors.New("err") - - // Configure the mock - mock.ApplyReturnError = mockReturn - - // Verify that it blocks until the real func is called - var err error - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - err = shadow.Apply(output, state, config) - }() - - select { - case <-doneCh: - t.Fatal("should block until finished") - case <-time.After(10 * time.Millisecond): - } - - // Call the real func - realErr := real.Apply(output, state, config) - if realErr != mockReturn { - t.Fatalf("bad: %#v", realErr) - } - - // The shadow should finish now - <-doneCh - - // Verify the shadow returned the same values - if err != mockReturn { - t.Errorf("bad: %#v", err) - } - - // Verify we have no errors - if err := shadow.CloseShadow(); err != nil { - t.Fatalf("bad: %s", err) - } - if err := shadow.ShadowError(); err != nil { - t.Fatalf("bad: %s", err) - } -}