diff --git a/helper/shadow/value.go b/helper/shadow/value.go new file mode 100644 index 000000000..bf30c12a8 --- /dev/null +++ b/helper/shadow/value.go @@ -0,0 +1,51 @@ +package shadow + +import ( + "sync" +) + +// Value is a struct that coordinates a value between two +// parallel routines. It is similar to atomic.Value except that when +// Value is called if it isn't set it will wait for it. +type Value struct { + lock sync.Mutex + cond *sync.Cond + value interface{} + valueSet bool +} + +// Value returns the value that was set. +func (w *Value) Value() interface{} { + w.lock.Lock() + defer w.lock.Unlock() + + // If we already have a value just return + for !w.valueSet { + // No value, setup the condition variable if we have to + if w.cond == nil { + w.cond = sync.NewCond(&w.lock) + } + + // Wait on it + w.cond.Wait() + } + + // Return the value + return w.value +} + +// SetValue sets the value. +func (w *Value) SetValue(v interface{}) { + w.lock.Lock() + defer w.lock.Unlock() + + // Set the value + w.valueSet = true + w.value = v + + // If we have a condition, clear it + if w.cond != nil { + w.cond.Broadcast() + w.cond = nil + } +} diff --git a/helper/shadow/value_test.go b/helper/shadow/value_test.go new file mode 100644 index 000000000..069b30852 --- /dev/null +++ b/helper/shadow/value_test.go @@ -0,0 +1,43 @@ +package shadow + +import ( + "testing" + "time" +) + +func TestValue(t *testing.T) { + var v Value + + // Start trying to get the value + valueCh := make(chan interface{}) + go func() { + valueCh <- v.Value() + }() + + // We should not get the value + select { + case <-valueCh: + t.Fatal("shouldn't receive value") + case <-time.After(10 * time.Millisecond): + } + + // Set the value + v.SetValue(42) + val := <-valueCh + + // Verify + if val != 42 { + t.Fatalf("bad: %#v", val) + } + + // We should be able to ask for the value again immediately + if val := v.Value(); val != 42 { + t.Fatalf("bad: %#v", val) + } + + // We can change the value + v.SetValue(84) + if val := v.Value(); val != 84 { + t.Fatalf("bad: %#v", val) + } +} diff --git a/terraform/eval_context_shadow.go b/terraform/eval_context_shadow.go index b8f82f1bf..66bd49d27 100644 --- a/terraform/eval_context_shadow.go +++ b/terraform/eval_context_shadow.go @@ -1,8 +1,11 @@ package terraform import ( + "errors" + "fmt" "sync" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/config" ) @@ -77,6 +80,14 @@ func NewShadowEvalContext(ctx EvalContext) (EvalContext, ShadowEvalContext) { return real, shadow } +var ( + // errShadow is the error returned by the shadow context when + // things go wrong. This should be ignored and the error result from + // Close should be checked instead since that'll contain more detailed + // error. + errShadow = errors.New("shadow error") +) + // shadowEvalContextReal is the EvalContext that does real work. type shadowEvalContextReal struct { EvalContext @@ -85,6 +96,8 @@ type shadowEvalContextReal struct { // shadowEvalContextShadow is the EvalContext that shadows the real one // and leans on that for data. type shadowEvalContextShadow struct { + Shared *shadowEvalContextShared + PathValue []string Providers map[string]ResourceProvider DiffValue *Diff @@ -92,6 +105,9 @@ type shadowEvalContextShadow struct { StateValue *State StateLock *sync.RWMutex + // The collection of errors that were found during the shadow run + Error error + // Fields relating to closing the context. Closing signals that // the execution of the real context completed. closeLock sync.Mutex @@ -117,11 +133,6 @@ type shadowEvalContextShared struct { ProviderWaiters map[string]*sync.Cond } -func (c *shadowEvalContextShadow) Close() error { - // TODO - return nil -} - func (c *shadowEvalContextShadow) Path() []string { return c.PathValue } @@ -133,16 +144,34 @@ func (c *shadowEvalContextShadow) Hook(f func(Hook) (HookAction, error)) error { return nil } -func (c *shadowEvalContextShadow) Input() UIInput { - // TODO - return nil -} - func (c *shadowEvalContextShadow) InitProvider(n string) (ResourceProvider, error) { // Initialize our shadow provider here. We also wait for the // real context to initialize the same provider. If it doesn't // before close, then an error is reported. + c.Shared.Lock() + defer c.Shared.Unlock() + + // We must only initialize once + if _, ok := c.Providers[n]; ok { + c.Error = multierror.Append(c.Error, fmt.Errorf( + "Provider %q already initialized", n)) + return nil, errShadow + } + + // If we don't have the provider yet, wait for it to initialize + if _, ok := c.Shared.Providers[n]; !ok { + cond := c.Shared.ProviderWaiters[n] + if cond == nil { + cond = sync.NewCond(&c.Shared.Mutex) + c.Shared.ProviderWaiters[n] = cond + } + + // TODO: break on close + cond.Wait() + } + + // We have the provider, set it up if it isn't in our list // TODO: shadow provider return nil, nil @@ -156,6 +185,10 @@ func (c *shadowEvalContextShadow) State() (*State, *sync.RWMutex) { return c.StateValue, c.StateLock } +// TODO: All the functions below are EvalContext functions that must be impl. + +func (c *shadowEvalContextShadow) Close() error { return nil } +func (c *shadowEvalContextShadow) Input() UIInput { return nil } func (c *shadowEvalContextShadow) Provider(n string) ResourceProvider { return nil } func (c *shadowEvalContextShadow) CloseProvider(n string) error { return nil } func (c *shadowEvalContextShadow) ConfigureProvider(string, *ResourceConfig) error { return nil } diff --git a/terraform/shadow_resource_provider.go b/terraform/shadow_resource_provider.go new file mode 100644 index 000000000..7b512146a --- /dev/null +++ b/terraform/shadow_resource_provider.go @@ -0,0 +1,219 @@ +package terraform + +import ( + "fmt" + "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 + + // CloseShadow should be called when the _real_ side is complete. + // This will immediately end any blocked calls and return any errors. + // + // Any operations on the shadow provider after this is undefined. It + // could be fine, it could result in crashes, etc. Do not use the + // shadow after this is called. + CloseShadow() error +} + +// 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, + } + + 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) Resources() []ResourceType { + result := p.ResourceProvider.Resources() + p.Shared.Resources.SetValue(result) + return result +} + +func (p *shadowResourceProviderReal) DataSources() []DataSource { + result := p.ResourceProvider.DataSources() + p.Shared.DataSources.SetValue(result) + return result +} + +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 +} + +// 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 + + Error error // Error is the list of errors from the shadow + ErrorLock sync.Mutex +} + +type shadowResourceProviderShared struct { + CloseErr shadow.Value + Input shadow.Value + Resources shadow.Value + DataSources shadow.Value +} + +func (p *shadowResourceProviderShadow) CloseShadow() error { return nil } + +func (p *shadowResourceProviderShadow) Resources() []ResourceType { + v := p.Shared.Resources.Value() + if v == nil { + return nil + } + + return v.([]ResourceType) +} + +func (p *shadowResourceProviderShadow) DataSources() []DataSource { + v := p.Shared.DataSources.Value() + if v == nil { + return nil + } + + return v.([]DataSource) +} + +func (p *shadowResourceProviderShadow) Close() error { + v := p.Shared.CloseErr.Value() + if v == nil { + return nil + } + + return v.(error) +} + +type shadowResourceProviderInput struct { + Config *ResourceConfig + Result *ResourceConfig + ResultErr 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 + // TODO + + // Return the results + return result.Result, result.ResultErr +} + +// TODO +// TODO +// TODO +// TODO +// TODO + +func (p *shadowResourceProviderShadow) Validate(c *ResourceConfig) ([]string, []error) { + return nil, nil +} + +func (p *shadowResourceProviderShadow) ValidateResource(t string, c *ResourceConfig) ([]string, []error) { + return nil, nil +} + +func (p *shadowResourceProviderShadow) Configure(c *ResourceConfig) error { + return nil +} + +func (p *shadowResourceProviderShadow) Apply( + info *InstanceInfo, + state *InstanceState, + diff *InstanceDiff) (*InstanceState, error) { + return nil, nil +} + +func (p *shadowResourceProviderShadow) Diff( + info *InstanceInfo, + state *InstanceState, + desired *ResourceConfig) (*InstanceDiff, error) { + return nil, nil +} + +func (p *shadowResourceProviderShadow) Refresh( + info *InstanceInfo, + s *InstanceState) (*InstanceState, error) { + return nil, nil +} + +func (p *shadowResourceProviderShadow) ImportState(info *InstanceInfo, id string) ([]*InstanceState, error) { + return nil, nil +} + +func (p *shadowResourceProviderShadow) ValidateDataSource(t string, c *ResourceConfig) ([]string, []error) { + return nil, nil +} + +func (p *shadowResourceProviderShadow) ReadDataDiff( + info *InstanceInfo, + desired *ResourceConfig) (*InstanceDiff, error) { + return nil, nil +} + +func (p *shadowResourceProviderShadow) ReadDataApply( + info *InstanceInfo, + d *InstanceDiff) (*InstanceState, error) { + return nil, nil +}