terraform: working on the resource provider shadow, not working yet
This commit is contained in:
parent
35f13f9c52
commit
1df3bbdc37
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue