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.
This commit is contained in:
Martin Atkins 2017-08-14 16:33:15 -07:00
parent 46bc77ff9a
commit 1da54955c6
10 changed files with 8 additions and 2380 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}