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:
Mitchell Hashimoto 2016-10-08 16:54:20 +08:00
parent a014b098b0
commit d30cfef4d2
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
2 changed files with 0 additions and 569 deletions

View File

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

View File

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