terraform: provider should be cached by path

This commit is contained in:
Mitchell Hashimoto 2015-02-08 17:58:02 -08:00
parent a41ec59510
commit b8bc3dc19b
7 changed files with 98 additions and 40 deletions

View File

@ -262,6 +262,27 @@ func TestContext2Validate_requiredVar(t *testing.T) {
} }
} }
func TestContext2Validate_resourceConfig_bad(t *testing.T) {
m := testModule(t, "validate-bad-rc")
p := testProvider("aws")
c := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate()
if len(w) > 0 {
t.Fatalf("bad: %#v", w)
}
if len(e) == 0 {
t.Fatalf("bad: %s", e)
}
}
func TestContext2Validate_selfRef(t *testing.T) { func TestContext2Validate_selfRef(t *testing.T) {
p := testProvider("aws") p := testProvider("aws")
m := testModule(t, "validate-self-ref") m := testModule(t, "validate-self-ref")
@ -406,27 +427,6 @@ func TestContextValidate_tainted(t *testing.T) {
} }
} }
func TestContextValidate_resourceConfig_bad(t *testing.T) {
m := testModule(t, "validate-bad-rc")
p := testProvider("aws")
c := testContext(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate()
if len(w) > 0 {
t.Fatalf("bad: %#v", w)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
}
}
func TestContextValidate_resourceConfig_good(t *testing.T) { func TestContextValidate_resourceConfig_good(t *testing.T) {
m := testModule(t, "validate-bad-rc") m := testModule(t, "validate-bad-rc")
p := testProvider("aws") p := testProvider("aws")

View File

@ -1,5 +1,9 @@
package terraform package terraform
import (
"log"
)
// EvalNode is the interface that must be implemented by graph nodes to // EvalNode is the interface that must be implemented by graph nodes to
// evaluate/execute. // evaluate/execute.
type EvalNode interface { type EvalNode interface {
@ -37,5 +41,11 @@ func Eval(n EvalNode, ctx EvalContext) (interface{}, error) {
args[i] = v args[i] = v
} }
return n.Eval(ctx, args) log.Printf("[DEBUG] eval: %T", n)
output, err := n.Eval(ctx, args)
if err != nil {
log.Printf("[ERROR] eval: %T, err: %s", n, err)
}
return output, err
} }

View File

@ -1,6 +1,8 @@
package terraform package terraform
import ( import (
"crypto/md5"
"encoding/hex"
"fmt" "fmt"
"sync" "sync"
@ -13,18 +15,25 @@ type BuiltinEvalContext struct {
PathValue []string PathValue []string
Interpolater *Interpolater Interpolater *Interpolater
Providers map[string]ResourceProviderFactory Providers map[string]ResourceProviderFactory
ProviderCache map[string]ResourceProvider
ProviderLock *sync.Mutex
providers map[string]ResourceProvider
once sync.Once once sync.Once
} }
func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) { func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) {
ctx.once.Do(ctx.init) ctx.once.Do(ctx.init)
// If we already initialized, it is an error
if p := ctx.Provider(n); p != nil { if p := ctx.Provider(n); p != nil {
return nil, fmt.Errorf("Provider '%s' already initialized", n) return nil, fmt.Errorf("Provider '%s' already initialized", n)
} }
// Warning: make sure to acquire these locks AFTER the call to Provider
// above, since it also acquires locks.
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
f, ok := ctx.Providers[n] f, ok := ctx.Providers[n]
if !ok { if !ok {
return nil, fmt.Errorf("Provider '%s' not found", n) return nil, fmt.Errorf("Provider '%s' not found", n)
@ -35,13 +44,17 @@ func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error)
return nil, err return nil, err
} }
ctx.providers[n] = p ctx.ProviderCache[ctx.pathCacheKey()] = p
return p, nil return p, nil
} }
func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider { func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider {
ctx.once.Do(ctx.init) ctx.once.Do(ctx.init)
return ctx.providers[n]
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
return ctx.ProviderCache[ctx.pathCacheKey()]
} }
func (ctx *BuiltinEvalContext) Interpolate( func (ctx *BuiltinEvalContext) Interpolate(
@ -77,8 +90,15 @@ func (ctx *BuiltinEvalContext) init() {
if ctx.Providers == nil { if ctx.Providers == nil {
ctx.Providers = make(map[string]ResourceProviderFactory) ctx.Providers = make(map[string]ResourceProviderFactory)
} }
}
// We always reset the things below since we only call this once and
// they can't be initialized externally. func (ctx *BuiltinEvalContext) pathCacheKey() string {
ctx.providers = make(map[string]ResourceProvider) hash := md5.New()
for _, p := range ctx.Path() {
if _, err := hash.Write([]byte(p)); err != nil {
panic(err)
}
}
return hex.EncodeToString(hash.Sum(nil))
} }

View File

@ -96,7 +96,7 @@ func (n *EvalValidateProvider) Type() EvalType {
type EvalValidateResource struct { type EvalValidateResource struct {
Provider EvalNode Provider EvalNode
Config EvalNode Config EvalNode
ProviderType string ResourceType string
} }
func (n *EvalValidateResource) Args() ([]EvalNode, []EvalType) { func (n *EvalValidateResource) Args() ([]EvalNode, []EvalType) {
@ -108,10 +108,20 @@ func (n *EvalValidateResource) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) { ctx EvalContext, args []interface{}) (interface{}, error) {
// TODO: test // TODO: test
//provider := args[0].(ResourceProvider) provider := args[0].(ResourceProvider)
config := args[1].(*ResourceConfig)
warns, errs := provider.ValidateResource(n.ResourceType, config)
if len(warns) == 0 && len(errs) == 0 {
return nil, nil return nil, nil
} }
return nil, &EvalValidateError{
Warnings: warns,
Errors: errs,
}
}
func (n *EvalValidateResource) Type() EvalType { func (n *EvalValidateResource) Type() EvalType {
return EvalTypeNull return EvalTypeNull
} }

View File

@ -2,6 +2,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log"
"sync" "sync"
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
@ -134,6 +135,8 @@ func (g *Graph) walk(walker GraphWalker) error {
// Walk the graph. // Walk the graph.
var walkFn dag.WalkFunc var walkFn dag.WalkFunc
walkFn = func(v dag.Vertex) (rerr error) { walkFn = func(v dag.Vertex) (rerr error) {
log.Printf("[DEBUG] vertex %s: walking", dag.VertexName(v))
walker.EnterVertex(v) walker.EnterVertex(v)
defer func() { walker.ExitVertex(v, rerr) }() defer func() { walker.ExitVertex(v, rerr) }()
@ -147,6 +150,7 @@ func (g *Graph) walk(walker GraphWalker) error {
// Allow the walker to change our tree if needed. Eval, // Allow the walker to change our tree if needed. Eval,
// then callback with the output. // then callback with the output.
log.Printf("[DEBUG] vertex %s: evaluating", dag.VertexName(v))
tree = walker.EnterEvalTree(v, tree) tree = walker.EnterEvalTree(v, tree)
output, err := Eval(tree, ctx) output, err := Eval(tree, ctx)
walker.ExitEvalTree(v, output, err) walker.ExitEvalTree(v, output, err)
@ -154,6 +158,9 @@ func (g *Graph) walk(walker GraphWalker) error {
// If the node is dynamically expanded, then expand it // If the node is dynamically expanded, then expand it
if ev, ok := v.(GraphNodeDynamicExpandable); ok { if ev, ok := v.(GraphNodeDynamicExpandable); ok {
log.Printf(
"[DEBUG] vertex %s: expanding dynamic subgraph",
dag.VertexName(v))
g, err := ev.DynamicExpand(ctx) g, err := ev.DynamicExpand(ctx)
if err != nil { if err != nil {
rerr = err rerr = err

View File

@ -23,12 +23,19 @@ type ContextGraphWalker struct {
ValidationErrors []error ValidationErrors []error
errorLock sync.Mutex errorLock sync.Mutex
once sync.Once
providerCache map[string]ResourceProvider
providerLock sync.Mutex
} }
func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext { func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
w.once.Do(w.init)
return &BuiltinEvalContext{ return &BuiltinEvalContext{
PathValue: g.Path, PathValue: g.Path,
Providers: w.Context.providers, Providers: w.Context.providers,
ProviderCache: w.providerCache,
ProviderLock: &w.providerLock,
Interpolater: &Interpolater{ Interpolater: &Interpolater{
Operation: w.Operation, Operation: w.Operation,
Module: w.Context.module, Module: w.Context.module,
@ -62,3 +69,7 @@ func (w *ContextGraphWalker) ExitEvalTree(
w.ValidationWarnings = append(w.ValidationWarnings, verr.Warnings...) w.ValidationWarnings = append(w.ValidationWarnings, verr.Warnings...)
w.ValidationErrors = append(w.ValidationErrors, verr.Errors...) w.ValidationErrors = append(w.ValidationErrors, verr.Errors...)
} }
func (w *ContextGraphWalker) init() {
w.providerCache = make(map[string]ResourceProvider, 5)
}

View File

@ -83,7 +83,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
&EvalValidateResource{ &EvalValidateResource{
Provider: &EvalGetProvider{Name: n.ProvidedBy()}, Provider: &EvalGetProvider{Name: n.ProvidedBy()},
Config: &EvalInterpolate{Config: n.Resource.RawConfig}, Config: &EvalInterpolate{Config: n.Resource.RawConfig},
ProviderType: n.ProvidedBy(), ResourceType: n.Resource.Type,
}, },
}, },
} }