Replace DebugGraphs with the Graph's methods

Now that the Graph can serialize itself, and log transformations,
there's no need for DebugGraph
This commit is contained in:
James Bardin 2016-11-10 16:18:18 -05:00
parent 82b1a2abc2
commit 6f9744292a
8 changed files with 91 additions and 163 deletions

View File

@ -697,11 +697,9 @@ func (c *Context) walk(
log.Printf("[DEBUG] Starting graph walk: %s", operation.String()) log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
dg, _ := NewDebugGraph("walk", graph, nil)
walker := &ContextGraphWalker{ walker := &ContextGraphWalker{
Context: realCtx, Context: realCtx,
Operation: operation, Operation: operation,
DebugGraph: dg,
} }
// Watch for a stop so we can call the provider Stop() API. // Watch for a stop so we can call the provider Stop() API.
@ -728,20 +726,13 @@ func (c *Context) walk(
// If we have a shadow graph, wait for that to complete. // If we have a shadow graph, wait for that to complete.
if shadowCloser != nil { if shadowCloser != nil {
// create a debug graph for this walk
dg, err := NewDebugGraph("walk-shadow", shadow, nil)
if err != nil {
log.Printf("[ERROR] %v", err)
}
// Build the graph walker for the shadow. We also wrap this in // Build the graph walker for the shadow. We also wrap this in
// a panicwrap so that panics are captured. For the shadow graph, // a panicwrap so that panics are captured. For the shadow graph,
// we just want panics to be normal errors rather than to crash // we just want panics to be normal errors rather than to crash
// Terraform. // Terraform.
shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{ shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{
Context: shadowCtx, Context: shadowCtx,
Operation: operation, Operation: operation,
DebugGraph: dg,
}) })
// Kick off the shadow walk. This will block on any operations // Kick off the shadow walk. This will block on any operations

View File

@ -166,6 +166,41 @@ func (d *debugInfo) Close() error {
return nil return nil
} }
// debug buffer is an io.WriteCloser that will write itself to the debug
// archive when closed.
type debugBuffer struct {
debugInfo *debugInfo
name string
buf bytes.Buffer
}
func (b *debugBuffer) Write(d []byte) (int, error) {
return b.buf.Write(d)
}
func (b *debugBuffer) Close() error {
return b.debugInfo.WriteFile(b.name, b.buf.Bytes())
}
// ioutils only has a noop ReadCloser
type nopWriteCloser struct{}
func (nopWriteCloser) Write([]byte) (int, error) { return 0, nil }
func (nopWriteCloser) Close() error { return nil }
// NewFileWriter returns an io.WriteClose that will be buffered and written to
// the debug archive when closed.
func (d *debugInfo) NewFileWriter(name string) io.WriteCloser {
if d == nil {
return nopWriteCloser{}
}
return &debugBuffer{
debugInfo: d,
name: name,
}
}
type syncer interface { type syncer interface {
Sync() error Sync() error
} }
@ -192,15 +227,10 @@ func (d *debugInfo) flush() {
// WriteGraph takes a DebugGraph and writes both the DebugGraph as a dot file // WriteGraph takes a DebugGraph and writes both the DebugGraph as a dot file
// in the debug archive, and extracts any logs that the DebugGraph collected // in the debug archive, and extracts any logs that the DebugGraph collected
// and writes them to a log file in the archive. // and writes them to a log file in the archive.
func (d *debugInfo) WriteGraph(dg *DebugGraph) error { func (d *debugInfo) WriteGraph(name string, g *Graph) error {
if d == nil { if d == nil || g == nil {
return nil return nil
} }
if dg == nil {
return nil
}
d.Lock() d.Lock()
defer d.Unlock() defer d.Unlock()
@ -209,12 +239,10 @@ func (d *debugInfo) WriteGraph(dg *DebugGraph) error {
// sync'ed. // sync'ed.
defer d.flush() defer d.flush()
d.writeFile(dg.Name, dg.LogBytes()) dotPath := fmt.Sprintf("%s/graphs/%d-%s-%s.dot", d.name, d.step, d.phase, name)
dotPath := fmt.Sprintf("%s/graphs/%d-%s-%s.dot", d.name, d.step, d.phase, dg.Name)
d.step++ d.step++
dotBytes := dg.DotBytes() dotBytes := g.Dot(nil)
hdr := &tar.Header{ hdr := &tar.Header{
Name: dotPath, Name: dotPath,
Mode: 0644, Mode: 0644,

View File

@ -16,7 +16,7 @@ func TestDebugInfo_nil(t *testing.T) {
var d *debugInfo var d *debugInfo
d.SetPhase("none") d.SetPhase("none")
d.WriteGraph(nil) d.WriteGraph("", nil)
d.WriteFile("none", nil) d.WriteFile("none", nil)
d.Close() d.Close()
} }
@ -122,6 +122,7 @@ func TestDebug_plan(t *testing.T) {
files := 0 files := 0
graphs := 0 graphs := 0
json := 0
for { for {
hdr, err := tr.Next() hdr, err := tr.Next()
if err == io.EOF { if err == io.EOF {
@ -139,6 +140,10 @@ func TestDebug_plan(t *testing.T) {
if strings.HasSuffix(hdr.Name, ".dot") { if strings.HasSuffix(hdr.Name, ".dot") {
graphs++ graphs++
} }
if strings.HasSuffix(hdr.Name, "graph.json") {
json++
}
} }
} }
@ -146,13 +151,13 @@ func TestDebug_plan(t *testing.T) {
t.Fatal("no files with data found") t.Fatal("no files with data found")
} }
/* if graphs == 0 {
TODO: once @jbardin finishes the dot refactor, uncomment this. This t.Fatal("no no-empty graphs found")
won't pass since the new graph doesn't implement the dot nodes. }
if graphs == 0 {
t.Fatal("no no-empty graphs found") if json == 0 {
} t.Fatal("no json graphs")
*/ }
} }
// verify that no hooks panic on nil input // verify that no hooks panic on nil input

View File

@ -40,6 +40,11 @@ type Graph struct {
// edges. // edges.
dependableMap map[string]dag.Vertex dependableMap map[string]dag.Vertex
// debugName is a name for reference in the debug output. This is usually
// to indicate what topmost builder was, and if this graph is a shadow or
// not.
debugName string
once sync.Once once sync.Once
} }
@ -201,7 +206,6 @@ func (g *Graph) Dependable(n string) dag.Vertex {
// will be walked with full parallelism, so the walker should expect // will be walked with full parallelism, so the walker should expect
// to be called in concurrently. // to be called in concurrently.
func (g *Graph) Walk(walker GraphWalker) error { func (g *Graph) Walk(walker GraphWalker) error {
defer dbug.WriteGraph(walker.Debug())
return g.walk(walker) return g.walk(walker)
} }
@ -229,6 +233,15 @@ func (g *Graph) walk(walker GraphWalker) error {
panicwrap = nil // just to be sure panicwrap = nil // just to be sure
} }
debugName := "walk-graph.json"
if g.debugName != "" {
debugName = g.debugName + "-" + debugName
}
debugBuf := dbug.NewFileWriter(debugName)
g.SetDebugWriter(debugBuf)
defer debugBuf.Close()
// 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) {
@ -258,10 +271,7 @@ func (g *Graph) walk(walker GraphWalker) error {
}() }()
walker.EnterVertex(v) walker.EnterVertex(v)
defer func() { defer walker.ExitVertex(v, rerr)
walker.Debug().DebugNode(v)
walker.ExitVertex(v, rerr)
}()
// vertexCtx is the context that we use when evaluating. This // vertexCtx is the context that we use when evaluating. This
// is normally the context of our graph but can be overridden // is normally the context of our graph but can be overridden
@ -283,7 +293,10 @@ 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.%s': evaluating", path, dag.VertexName(v)) log.Printf("[DEBUG] vertex '%s.%s': evaluating", path, dag.VertexName(v))
walker.Debug().Printf("[DEBUG] vertex %T(%s.%s): evaluating\n", v, path, dag.VertexName(v))
// TODO: replace these debug calls with Graph native methods
//walker.Debug().Printf("[DEBUG] vertex %T(%s.%s): evaluating\n", v, path, dag.VertexName(v))
tree = walker.EnterEvalTree(v, tree) tree = walker.EnterEvalTree(v, tree)
output, err := Eval(tree, vertexCtx) output, err := Eval(tree, vertexCtx)
if rerr = walker.ExitEvalTree(v, output, err); rerr != nil { if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
@ -297,7 +310,7 @@ func (g *Graph) walk(walker GraphWalker) error {
"[DEBUG] vertex '%s.%s': expanding/walking dynamic subgraph", "[DEBUG] vertex '%s.%s': expanding/walking dynamic subgraph",
path, path,
dag.VertexName(v)) dag.VertexName(v))
walker.Debug().Printf("[DEBUG] vertex %T(%s.%s): expanding\n", v, path, dag.VertexName(v)) //walker.Debug().Printf("[DEBUG] vertex %T(%s.%s): expanding\n", v, path, dag.VertexName(v))
g, err := ev.DynamicExpand(vertexCtx) g, err := ev.DynamicExpand(vertexCtx)
if err != nil { if err != nil {
rerr = err rerr = err
@ -318,8 +331,8 @@ func (g *Graph) walk(walker GraphWalker) error {
path, path,
dag.VertexName(v)) dag.VertexName(v))
walker.Debug().Printf( //walker.Debug().Printf(
"[DEBUG] vertex %T(%s.%s): subgraph\n", v, path, dag.VertexName(v)) // "[DEBUG] vertex %T(%s.%s): subgraph\n", v, path, dag.VertexName(v))
if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil { if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil {
return return

View File

@ -29,6 +29,15 @@ type BasicGraphBuilder struct {
func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) { func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
g := &Graph{Path: path} g := &Graph{Path: path}
debugName := "build-graph.json"
if b.Name != "" {
debugName = b.Name + "-" + debugName
}
debugBuf := dbug.NewFileWriter(debugName)
g.SetDebugWriter(debugBuf)
defer debugBuf.Close()
for _, step := range b.Steps { for _, step := range b.Steps {
if step == nil { if step == nil {
continue continue
@ -52,8 +61,8 @@ func (b *BasicGraphBuilder) Build(path []string) (*Graph, error) {
"[TRACE] Graph after step %T:\n\n%s", "[TRACE] Graph after step %T:\n\n%s",
step, g.StringWithNodeTypes()) step, g.StringWithNodeTypes())
dg, _ := NewDebugGraph(debugName, g, nil) // TODO: replace entirely with the json logs
dbug.WriteGraph(dg) dbug.WriteGraph(debugName, g)
if err != nil { if err != nil {
return g, err return g, err

View File

@ -1,111 +0,0 @@
package terraform
import (
"bytes"
"fmt"
"sync"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/terraform/dag"
"github.com/mitchellh/copystructure"
)
// The NodeDebug method outputs debug information to annotate the graphs
// stored in the DebugInfo
type GraphNodeDebugger interface {
NodeDebug() string
}
type GraphNodeDebugOrigin interface {
DotOrigin() bool
}
type DebugGraph struct {
sync.Mutex
Name string
ord int
buf bytes.Buffer
Graph *Graph
dotOpts *dag.DotOpts
}
// DebugGraph holds a dot representation of the Terraform graph, and can be
// written out to the DebugInfo log with DebugInfo.WriteGraph. A DebugGraph can
// log data to it's internal buffer via the Printf and Write methods, which
// will be also be written out to the DebugInfo archive.
func NewDebugGraph(name string, g *Graph, opts *dag.DotOpts) (*DebugGraph, error) {
dg := &DebugGraph{
Name: name,
Graph: g,
dotOpts: opts,
}
dbug.WriteFile(dg.Name, g.Dot(opts))
return dg, nil
}
// Printf to the internal buffer
func (dg *DebugGraph) Printf(f string, args ...interface{}) (int, error) {
if dg == nil {
return 0, nil
}
dg.Lock()
defer dg.Unlock()
return fmt.Fprintf(&dg.buf, f, args...)
}
// Write to the internal buffer
func (dg *DebugGraph) Write(b []byte) (int, error) {
if dg == nil {
return 0, nil
}
dg.Lock()
defer dg.Unlock()
return dg.buf.Write(b)
}
func (dg *DebugGraph) LogBytes() []byte {
if dg == nil {
return nil
}
dg.Lock()
defer dg.Unlock()
return dg.buf.Bytes()
}
func (dg *DebugGraph) DotBytes() []byte {
if dg == nil {
return nil
}
dg.Lock()
defer dg.Unlock()
return dg.Graph.Dot(dg.dotOpts)
}
func (dg *DebugGraph) DebugNode(v interface{}) {
if dg == nil {
return
}
dg.Lock()
defer dg.Unlock()
// record the ordinal value for each node
ord := dg.ord
dg.ord++
name := dag.VertexName(v)
vCopy, _ := copystructure.Config{Lock: true}.Copy(v)
// record as much of the node data structure as we can
spew.Fdump(&dg.buf, vCopy)
dg.buf.WriteString(fmt.Sprintf("%d visited %s\n", ord, name))
// if the node provides debug output, insert it into the graph, and log it
if nd, ok := v.(GraphNodeDebugger); ok {
out := nd.NodeDebug()
dg.buf.WriteString(fmt.Sprintf("NodeDebug (%s):'%s'\n", name, out))
}
}

View File

@ -13,7 +13,6 @@ type GraphWalker interface {
ExitVertex(dag.Vertex, error) ExitVertex(dag.Vertex, error)
EnterEvalTree(dag.Vertex, EvalNode) EvalNode EnterEvalTree(dag.Vertex, EvalNode) EvalNode
ExitEvalTree(dag.Vertex, interface{}, error) error ExitEvalTree(dag.Vertex, interface{}, error) error
Debug() *DebugGraph
} }
// GrpahWalkerPanicwrapper can be optionally implemented to catch panics // GrpahWalkerPanicwrapper can be optionally implemented to catch panics
@ -59,4 +58,3 @@ func (NullGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { return
func (NullGraphWalker) ExitEvalTree(dag.Vertex, interface{}, error) error { func (NullGraphWalker) ExitEvalTree(dag.Vertex, interface{}, error) error {
return nil return nil
} }
func (NullGraphWalker) Debug() *DebugGraph { return nil }

View File

@ -15,9 +15,8 @@ type ContextGraphWalker struct {
NullGraphWalker NullGraphWalker
// Configurable values // Configurable values
Context *Context Context *Context
Operation walkOperation Operation walkOperation
DebugGraph *DebugGraph
// Outputs, do not set these. Do not read these while the graph // Outputs, do not set these. Do not read these while the graph
// is being walked. // is being walked.
@ -145,10 +144,6 @@ func (w *ContextGraphWalker) ExitEvalTree(
return nil return nil
} }
func (w *ContextGraphWalker) Debug() *DebugGraph {
return w.DebugGraph
}
func (w *ContextGraphWalker) init() { func (w *ContextGraphWalker) init() {
w.contexts = make(map[string]*BuiltinEvalContext, 5) w.contexts = make(map[string]*BuiltinEvalContext, 5)
w.providerCache = make(map[string]ResourceProvider, 5) w.providerCache = make(map[string]ResourceProvider, 5)