terraform: remove shadow eval context since we're not shadowing that
We allow the built in context to work as expected and shadow just the components now. This is better since it allows us to use much more of the REAL structures.
This commit is contained in:
parent
a014b098b0
commit
d30cfef4d2
|
@ -1,312 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/helper/shadow"
|
||||
)
|
||||
|
||||
// ShadowEvalContext is an EvalContext that is used to "shadow" a real
|
||||
// eval context for comparing whether two separate graph executions result
|
||||
// in the same output.
|
||||
//
|
||||
// This eval context will never communicate with a real provider and will
|
||||
// never modify real state.
|
||||
type ShadowEvalContext interface {
|
||||
EvalContext
|
||||
|
||||
// Close should be called when the _real_ EvalContext operations
|
||||
// are complete. This will immediately end any blocks calls and record
|
||||
// any errors.
|
||||
//
|
||||
// The returned error is the result of the shadow run. If it is nil,
|
||||
// then the shadow run seemingly completed successfully. You should
|
||||
// still compare the resulting states, diffs from both the real and shadow
|
||||
// contexts to verify equivalent end state.
|
||||
//
|
||||
// If the error is non-nil, then an error occurred during the execution
|
||||
// itself. In this scenario, you should not compare diffs/states since
|
||||
// they can't be considered accurate since operations during execution
|
||||
// failed.
|
||||
CloseShadow() error
|
||||
}
|
||||
|
||||
// NewShadowEvalContext creates a new shadowed EvalContext. This returns
|
||||
// the real EvalContext that should be used with the real evaluation and
|
||||
// will communicate with real providers and write real state as well as
|
||||
// the ShadowEvalContext that should be used with the test graph.
|
||||
//
|
||||
// This should be called before the ctx is ever used in order to ensure
|
||||
// a consistent shadow state.
|
||||
func NewShadowEvalContext(ctx EvalContext) (EvalContext, ShadowEvalContext) {
|
||||
var shared shadowEvalContextShared
|
||||
real := &shadowEvalContextReal{
|
||||
EvalContext: ctx,
|
||||
Shared: &shared,
|
||||
}
|
||||
|
||||
// Copy the diff. We do this using some weird scoping so that the
|
||||
// "diff" (real) value never leaks out and can be used.
|
||||
var diffCopy *Diff
|
||||
{
|
||||
diff, lock := ctx.Diff()
|
||||
if lock != nil {
|
||||
lock.RLock()
|
||||
diffCopy = diff
|
||||
// TODO: diffCopy = diff.DeepCopy()
|
||||
lock.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the state. We do this using some weird scoping so that the
|
||||
// "state" (real) value never leaks out and can be used.
|
||||
var stateCopy *State
|
||||
{
|
||||
state, lock := ctx.State()
|
||||
if lock != nil {
|
||||
lock.RLock()
|
||||
stateCopy = state.DeepCopy()
|
||||
lock.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Build the shadow copy. For safety, we don't even give the shadow
|
||||
// copy a reference to the real context. This means that it would be
|
||||
// very difficult (impossible without some real obvious mistakes) for
|
||||
// the shadow context to do "real" work.
|
||||
shadow := &shadowEvalContextShadow{
|
||||
Shared: &shared,
|
||||
|
||||
PathValue: ctx.Path(),
|
||||
StateValue: stateCopy,
|
||||
StateLock: new(sync.RWMutex),
|
||||
DiffValue: diffCopy,
|
||||
DiffLock: new(sync.RWMutex),
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Shared *shadowEvalContextShared
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextReal) InitProvider(n string) (ResourceProvider, error) {
|
||||
// Initialize the real provider
|
||||
p, err := c.EvalContext.InitProvider(n)
|
||||
|
||||
// Create the shadow
|
||||
var real ResourceProvider
|
||||
var shadow shadowResourceProvider
|
||||
if err == nil {
|
||||
real, shadow = newShadowResourceProvider(p)
|
||||
}
|
||||
|
||||
// Store the result
|
||||
c.Shared.Providers.SetValue(n, &shadowEvalContextInitProvider{
|
||||
Shadow: shadow,
|
||||
ResultErr: err,
|
||||
})
|
||||
|
||||
return real, err
|
||||
}
|
||||
|
||||
// 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
|
||||
DiffLock *sync.RWMutex
|
||||
StateValue *State
|
||||
StateLock *sync.RWMutex
|
||||
|
||||
// The collection of errors that were found during the shadow run
|
||||
Error error
|
||||
ErrorLock sync.Mutex
|
||||
|
||||
// Fields relating to closing the context. Closing signals that
|
||||
// the execution of the real context completed.
|
||||
closeLock sync.Mutex
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
// Shared is the shared state between the shadow and real contexts when
|
||||
// a shadow context is active. This is used by the real context to setup
|
||||
// some state, trigger condition variables, etc.
|
||||
type shadowEvalContextShared struct {
|
||||
Providers shadow.KeyedValue
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) CloseShadow() error {
|
||||
// TODO: somehow shut this thing down
|
||||
return c.Error
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Path() []string {
|
||||
return c.PathValue
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Hook(f func(Hook) (HookAction, error)) error {
|
||||
// Don't do anything on hooks. Mission critical behavior should not
|
||||
// depend on hooks and at the time of writing it does not depend on
|
||||
// hooks. In the future we could also test hooks but not now.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) InitProvider(n string) (ResourceProvider, error) {
|
||||
// Wait for the provider value
|
||||
raw := c.Shared.Providers.Value(n)
|
||||
if raw == nil {
|
||||
return nil, c.err(fmt.Errorf(
|
||||
"Unknown 'InitProvider' call for %q", n))
|
||||
}
|
||||
|
||||
result, ok := raw.(*shadowEvalContextInitProvider)
|
||||
if !ok {
|
||||
return nil, c.err(fmt.Errorf(
|
||||
"Unknown 'InitProvider' shadow value: %#v", raw))
|
||||
}
|
||||
|
||||
result.Lock()
|
||||
defer result.Unlock()
|
||||
|
||||
if result.Init {
|
||||
// Record the error but continue...
|
||||
c.err(fmt.Errorf(
|
||||
"InitProvider: provider %q already initialized", n))
|
||||
}
|
||||
|
||||
result.Init = true
|
||||
return result.Shadow, result.ResultErr
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Provider(n string) ResourceProvider {
|
||||
// Wait for the provider value
|
||||
raw := c.Shared.Providers.Value(n)
|
||||
if raw == nil {
|
||||
c.err(fmt.Errorf(
|
||||
"Unknown 'Provider' call for %q", n))
|
||||
return nil
|
||||
}
|
||||
|
||||
result, ok := raw.(*shadowEvalContextInitProvider)
|
||||
if !ok {
|
||||
c.err(fmt.Errorf(
|
||||
"Unknown 'Provider' shadow value: %#v", raw))
|
||||
return nil
|
||||
}
|
||||
|
||||
result.Lock()
|
||||
defer result.Unlock()
|
||||
|
||||
if !result.Init {
|
||||
// Record the error but continue...
|
||||
c.err(fmt.Errorf(
|
||||
"Provider: provider %q requested but not initialized", n))
|
||||
}
|
||||
|
||||
return result.Shadow
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) CloseProvider(n string) error {
|
||||
// Wait for the provider value
|
||||
raw, ok := c.Shared.Providers.ValueOk(n)
|
||||
if !ok {
|
||||
c.err(fmt.Errorf(
|
||||
"CloseProvider called for uninitialized provider %q", n))
|
||||
return nil
|
||||
}
|
||||
if raw == nil {
|
||||
c.err(fmt.Errorf(
|
||||
"Unknown 'CloseProvider' call for %q", n))
|
||||
return nil
|
||||
}
|
||||
|
||||
result, ok := raw.(*shadowEvalContextInitProvider)
|
||||
if !ok {
|
||||
c.err(fmt.Errorf(
|
||||
"Unknown 'CloseProvider' shadow value: %#v", raw))
|
||||
return nil
|
||||
}
|
||||
|
||||
result.Lock()
|
||||
defer result.Unlock()
|
||||
|
||||
if !result.Init {
|
||||
// Record the error but continue...
|
||||
c.err(fmt.Errorf(
|
||||
"CloseProvider: provider %q requested but not initialized", n))
|
||||
} else if result.Closed {
|
||||
c.err(fmt.Errorf(
|
||||
"CloseProvider: provider %q requested but already closed", n))
|
||||
}
|
||||
|
||||
result.Closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) Diff() (*Diff, *sync.RWMutex) {
|
||||
return c.DiffValue, c.DiffLock
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) State() (*State, *sync.RWMutex) {
|
||||
return c.StateValue, c.StateLock
|
||||
}
|
||||
|
||||
func (c *shadowEvalContextShadow) err(err error) error {
|
||||
c.ErrorLock.Lock()
|
||||
defer c.ErrorLock.Unlock()
|
||||
c.Error = multierror.Append(c.Error, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: All the functions below are EvalContext functions that must be impl.
|
||||
|
||||
func (c *shadowEvalContextShadow) Input() UIInput { return nil }
|
||||
func (c *shadowEvalContextShadow) ConfigureProvider(string, *ResourceConfig) error { return nil }
|
||||
func (c *shadowEvalContextShadow) SetProviderConfig(string, *ResourceConfig) error { return nil }
|
||||
func (c *shadowEvalContextShadow) ParentProviderConfig(string) *ResourceConfig { return nil }
|
||||
func (c *shadowEvalContextShadow) ProviderInput(string) map[string]interface{} { return nil }
|
||||
func (c *shadowEvalContextShadow) SetProviderInput(string, map[string]interface{}) {}
|
||||
|
||||
func (c *shadowEvalContextShadow) InitProvisioner(string) (ResourceProvisioner, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *shadowEvalContextShadow) Provisioner(string) ResourceProvisioner { return nil }
|
||||
func (c *shadowEvalContextShadow) CloseProvisioner(string) error { return nil }
|
||||
|
||||
func (c *shadowEvalContextShadow) Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (c *shadowEvalContextShadow) SetVariables(string, map[string]interface{}) {}
|
||||
|
||||
// The structs for the various function calls are put below. These structs
|
||||
// are used to carry call information across the real/shadow boundaries.
|
||||
|
||||
type shadowEvalContextInitProvider struct {
|
||||
Shadow shadowResourceProvider
|
||||
ResultErr error
|
||||
|
||||
sync.Mutex // Must be held to modify the field below
|
||||
Init bool // Keeps track of whether it has been initialized in the shadow
|
||||
Closed bool // Keeps track of whether this provider is closed
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestShadowEvalContext_impl(t *testing.T) {
|
||||
var _ EvalContext = new(shadowEvalContextReal)
|
||||
var _ EvalContext = new(shadowEvalContextShadow)
|
||||
}
|
||||
|
||||
func TestShadowEvalContextInitProvider(t *testing.T) {
|
||||
mock := new(MockEvalContext)
|
||||
real, shadow := NewShadowEvalContext(mock)
|
||||
|
||||
// Args, results
|
||||
name := "foo"
|
||||
mockResult := new(MockResourceProvider)
|
||||
|
||||
// Configure the mock
|
||||
mock.InitProviderProvider = mockResult
|
||||
|
||||
// Verify that it blocks until the real func is called
|
||||
var result ResourceProvider
|
||||
var err error
|
||||
doneCh := make(chan struct{})
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
result, err = shadow.InitProvider(name)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-doneCh:
|
||||
t.Fatal("should block until finished")
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
|
||||
// Call the real func
|
||||
realResult, realErr := real.InitProvider(name)
|
||||
if realErr != nil {
|
||||
t.Fatalf("bad: %#v", realErr)
|
||||
}
|
||||
realResult.Configure(nil)
|
||||
if !mockResult.ConfigureCalled {
|
||||
t.Fatalf("bad: %#v", realResult)
|
||||
}
|
||||
mockResult.ConfigureCalled = false
|
||||
|
||||
// The shadow should finish now
|
||||
<-doneCh
|
||||
|
||||
// Verify the shadow returned the same values
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %#v", err)
|
||||
}
|
||||
|
||||
// Verify that the returned value is a shadow. Calling one function
|
||||
// shouldn't affect the other.
|
||||
result.Configure(nil)
|
||||
if mockResult.ConfigureCalled {
|
||||
t.Fatal("close should not be called")
|
||||
}
|
||||
|
||||
// And doing some work should result in that value
|
||||
mockErr := fmt.Errorf("yo")
|
||||
mockResult.ConfigureReturnError = mockErr
|
||||
realResult.Configure(nil)
|
||||
if err := result.Configure(nil); !reflect.DeepEqual(err, mockErr) {
|
||||
t.Fatalf("bad: %#v", err)
|
||||
}
|
||||
|
||||
// Verify we have no errors
|
||||
if err := shadow.CloseShadow(); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShadowEvalContextInitProvider_doubleInit(t *testing.T) {
|
||||
mock := new(MockEvalContext)
|
||||
real, shadow := NewShadowEvalContext(mock)
|
||||
|
||||
// Args, results
|
||||
name := "foo"
|
||||
mockResult := new(MockResourceProvider)
|
||||
|
||||
// Configure the mock
|
||||
mock.InitProviderProvider = mockResult
|
||||
|
||||
// Call the real func
|
||||
real.InitProvider(name)
|
||||
|
||||
// Get the provider twice
|
||||
shadow.InitProvider(name)
|
||||
p, err := shadow.InitProvider(name)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if p == nil {
|
||||
t.Fatal("should return provider")
|
||||
}
|
||||
|
||||
if err := shadow.CloseShadow(); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShadowEvalContextProvider(t *testing.T) {
|
||||
mock := new(MockEvalContext)
|
||||
real, shadow := NewShadowEvalContext(mock)
|
||||
|
||||
// Args, results
|
||||
name := "foo"
|
||||
mockResult := new(MockResourceProvider)
|
||||
|
||||
// Configure the mock
|
||||
mock.InitProviderProvider = mockResult
|
||||
|
||||
// Call the real func
|
||||
real.InitProvider(name)
|
||||
shadow.InitProvider(name)
|
||||
|
||||
// Get the provider twice
|
||||
p := shadow.Provider(name)
|
||||
if p == nil {
|
||||
t.Fatal("should return provider")
|
||||
}
|
||||
|
||||
if err := shadow.CloseShadow(); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShadowEvalContextProvider_noInit(t *testing.T) {
|
||||
mock := new(MockEvalContext)
|
||||
real, shadow := NewShadowEvalContext(mock)
|
||||
|
||||
// Args, results
|
||||
name := "foo"
|
||||
mockResult := new(MockResourceProvider)
|
||||
|
||||
// Configure the mock
|
||||
mock.InitProviderProvider = mockResult
|
||||
|
||||
// Call the real func
|
||||
real.InitProvider(name)
|
||||
|
||||
// Get the provider w/o calling init
|
||||
p := shadow.Provider(name)
|
||||
if p == nil {
|
||||
t.Fatal("should return provider")
|
||||
}
|
||||
|
||||
if err := shadow.CloseShadow(); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShadowEvalContextCloseProvider(t *testing.T) {
|
||||
mock := new(MockEvalContext)
|
||||
real, shadow := NewShadowEvalContext(mock)
|
||||
|
||||
// Args, results
|
||||
name := "foo"
|
||||
mockResult := new(MockResourceProvider)
|
||||
|
||||
// Configure the mock
|
||||
mock.InitProviderProvider = mockResult
|
||||
|
||||
// Call the real func
|
||||
real.InitProvider(name)
|
||||
shadow.InitProvider(name)
|
||||
|
||||
// Get the provider twice
|
||||
if err := shadow.CloseProvider(name); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := shadow.CloseShadow(); err != nil {
|
||||
t.Fatalf("bad: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShadowEvalContextCloseProvider_doubleClose(t *testing.T) {
|
||||
mock := new(MockEvalContext)
|
||||
real, shadow := NewShadowEvalContext(mock)
|
||||
|
||||
// Args, results
|
||||
name := "foo"
|
||||
mockResult := new(MockResourceProvider)
|
||||
|
||||
// Configure the mock
|
||||
mock.InitProviderProvider = mockResult
|
||||
|
||||
// Call the real func
|
||||
real.InitProvider(name)
|
||||
shadow.InitProvider(name)
|
||||
|
||||
// Close the provider twice
|
||||
if err := shadow.CloseProvider(name); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if err := shadow.CloseProvider(name); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := shadow.CloseShadow(); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShadowEvalContextCloseProvider_noInitClose(t *testing.T) {
|
||||
mock := new(MockEvalContext)
|
||||
real, shadow := NewShadowEvalContext(mock)
|
||||
|
||||
// Args, results
|
||||
name := "foo"
|
||||
mockResult := new(MockResourceProvider)
|
||||
|
||||
// Configure the mock
|
||||
mock.InitProviderProvider = mockResult
|
||||
|
||||
// Call the real func
|
||||
real.InitProvider(name)
|
||||
|
||||
// Close the provider
|
||||
if err := shadow.CloseProvider(name); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := shadow.CloseShadow(); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShadowEvalContextCloseProvider_noCreate(t *testing.T) {
|
||||
mock := new(MockEvalContext)
|
||||
_, shadow := NewShadowEvalContext(mock)
|
||||
|
||||
// Args, results
|
||||
name := "foo"
|
||||
mockResult := new(MockResourceProvider)
|
||||
|
||||
// Configure the mock
|
||||
mock.InitProviderProvider = mockResult
|
||||
|
||||
// Close the provider
|
||||
if err := shadow.CloseProvider(name); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := shadow.CloseShadow(); err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue