terraform: working on the resource provider shadow, not working yet

This commit is contained in:
Mitchell Hashimoto 2016-09-26 10:23:25 -07:00
parent 35f13f9c52
commit 1df3bbdc37
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
4 changed files with 356 additions and 10 deletions

51
helper/shadow/value.go Normal file
View File

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

View File

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

View File

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

View File

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