From 1da54955c6892855390ccc8163b1e55aea3e77dc Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 14 Aug 2017 16:33:15 -0700 Subject: [PATCH] core: remove shadow graph infrastructure The shadow graph was incredibly useful during the 0.7 cycle but these days it is idle, since we're not planning any significant graph-related changes for the forseeable future. The shadow graph infrastructure is somewhat burdensome since any change to the ResourceProvider interface must have shims written. Since we _are_ expecting changes to the ResourceProvider interface in the next few releases, I'm calling "YAGNI" on the shadow graph support to reduce our maintenence burden. If we do end up wanting to use shadow graph again in future, we'll always be able to pull it out of version control and then make whatever changes we skipped making in the mean time, but we can avoid that cost in the mean time while we don't have any evidence that we'll need to pay it. --- terraform/context.go | 119 +-- terraform/context_import.go | 2 +- terraform/context_validate_test.go | 2 +- terraform/shadow.go | 28 - terraform/shadow_components.go | 273 ------ terraform/shadow_context.go | 158 ---- terraform/shadow_resource_provider.go | 815 ------------------ terraform/shadow_resource_provider_test.go | 531 ------------ terraform/shadow_resource_provisioner.go | 282 ------ terraform/shadow_resource_provisioner_test.go | 178 ---- 10 files changed, 8 insertions(+), 2380 deletions(-) delete mode 100644 terraform/shadow.go delete mode 100644 terraform/shadow_components.go delete mode 100644 terraform/shadow_context.go delete mode 100644 terraform/shadow_resource_provider.go delete mode 100644 terraform/shadow_resource_provider_test.go delete mode 100644 terraform/shadow_resource_provisioner.go delete mode 100644 terraform/shadow_resource_provisioner_test.go 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) - } -}