From c593bf37225298f312533d078dc99a0184e523e3 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Mon, 16 Nov 2015 10:51:54 -0500 Subject: [PATCH] Remove unused depgraph package --- depgraph/dependency.go | 46 ---- depgraph/graph.go | 379 --------------------------------- depgraph/graph_test.go | 467 ----------------------------------------- depgraph/noun.go | 33 --- 4 files changed, 925 deletions(-) delete mode 100644 depgraph/dependency.go delete mode 100644 depgraph/graph.go delete mode 100644 depgraph/graph_test.go delete mode 100644 depgraph/noun.go diff --git a/depgraph/dependency.go b/depgraph/dependency.go deleted file mode 100644 index 947be6355..000000000 --- a/depgraph/dependency.go +++ /dev/null @@ -1,46 +0,0 @@ -package depgraph - -import ( - "fmt" - - "github.com/hashicorp/terraform/digraph" -) - -// Dependency is used to create a directed edge between two nouns. -// One noun may depend on another and provide version constraints -// that cannot be violated -type Dependency struct { - Name string - Meta interface{} - Constraints []Constraint - Source *Noun - Target *Noun -} - -// Constraint is used by dependencies to allow arbitrary constraints -// between nouns -type Constraint interface { - Satisfied(head, tail *Noun) (bool, error) -} - -// Head returns the source, or dependent noun -func (d *Dependency) Head() digraph.Node { - return d.Source -} - -// Tail returns the target, or depended upon noun -func (d *Dependency) Tail() digraph.Node { - return d.Target -} - -func (d *Dependency) GoString() string { - return fmt.Sprintf( - "*Dependency{Name: %s, Source: %s, Target: %s}", - d.Name, - d.Source.Name, - d.Target.Name) -} - -func (d *Dependency) String() string { - return d.Name -} diff --git a/depgraph/graph.go b/depgraph/graph.go deleted file mode 100644 index c190dcfa1..000000000 --- a/depgraph/graph.go +++ /dev/null @@ -1,379 +0,0 @@ -// The depgraph package is used to create and model a dependency graph -// of nouns. Each noun can represent a service, server, application, -// network switch, etc. Nouns can depend on other nouns, and provide -// versioning constraints. Nouns can also have various meta data that -// may be relevant to their construction or configuration. -package depgraph - -import ( - "bytes" - "fmt" - "sort" - "strings" - "sync" - - "github.com/hashicorp/terraform/digraph" -) - -// WalkFunc is the type used for the callback for Walk. -type WalkFunc func(*Noun) error - -// Graph is used to represent a dependency graph. -type Graph struct { - Name string - Meta interface{} - Nouns []*Noun - Root *Noun -} - -// ValidateError implements the Error interface but provides -// additional information on a validation error. -type ValidateError struct { - // If set, then the graph is missing a single root, on which - // there are no depdendencies - MissingRoot bool - - // Unreachable are nodes that could not be reached from - // the root noun. - Unreachable []*Noun - - // Cycles are groups of strongly connected nodes, which - // form a cycle. This is disallowed. - Cycles [][]*Noun -} - -func (v *ValidateError) Error() string { - var msgs []string - - if v.MissingRoot { - msgs = append(msgs, "The graph has no single root") - } - - for _, n := range v.Unreachable { - msgs = append(msgs, fmt.Sprintf( - "Unreachable node: %s", n.Name)) - } - - for _, c := range v.Cycles { - cycleNodes := make([]string, len(c)) - for i, n := range c { - cycleNodes[i] = n.Name - } - - msgs = append(msgs, fmt.Sprintf( - "Cycle: %s", strings.Join(cycleNodes, " -> "))) - } - - for i, m := range msgs { - msgs[i] = fmt.Sprintf("* %s", m) - } - - return fmt.Sprintf( - "The dependency graph is not valid:\n\n%s", - strings.Join(msgs, "\n")) -} - -// ConstraintError is used to return detailed violation -// information from CheckConstraints -type ConstraintError struct { - Violations []*Violation -} - -func (c *ConstraintError) Error() string { - return fmt.Sprintf("%d constraint violations", len(c.Violations)) -} - -// Violation is used to pass along information about -// a constraint violation -type Violation struct { - Source *Noun - Target *Noun - Dependency *Dependency - Constraint Constraint - Err error -} - -func (v *Violation) Error() string { - return fmt.Sprintf("Constraint %v between %v and %v violated: %v", - v.Constraint, v.Source, v.Target, v.Err) -} - -// CheckConstraints walks the graph and ensures that all -// user imposed constraints are satisfied. -func (g *Graph) CheckConstraints() error { - // Ensure we have a root - if g.Root == nil { - return fmt.Errorf("Graph must be validated before checking constraint violations") - } - - // Create a constraint error - cErr := &ConstraintError{} - - // Walk from the root - digraph.DepthFirstWalk(g.Root, func(n digraph.Node) bool { - noun := n.(*Noun) - for _, dep := range noun.Deps { - target := dep.Target - for _, constraint := range dep.Constraints { - ok, err := constraint.Satisfied(noun, target) - if ok { - continue - } - violation := &Violation{ - Source: noun, - Target: target, - Dependency: dep, - Constraint: constraint, - Err: err, - } - cErr.Violations = append(cErr.Violations, violation) - } - } - return true - }) - - if cErr.Violations != nil { - return cErr - } - return nil -} - -// Noun returns the noun with the given name, or nil if it cannot be found. -func (g *Graph) Noun(name string) *Noun { - for _, n := range g.Nouns { - if n.Name == name { - return n - } - } - - return nil -} - -// String generates a little ASCII string of the graph, useful in -// debugging output. -func (g *Graph) String() string { - var buf bytes.Buffer - - // Alphabetize the output based on the noun name - keys := make([]string, 0, len(g.Nouns)) - mapping := make(map[string]*Noun) - for _, n := range g.Nouns { - mapping[n.Name] = n - keys = append(keys, n.Name) - } - sort.Strings(keys) - - if g.Root != nil { - buf.WriteString(fmt.Sprintf("root: %s\n", g.Root.Name)) - } else { - buf.WriteString("root: \n") - } - for _, k := range keys { - n := mapping[k] - buf.WriteString(fmt.Sprintf("%s\n", n.Name)) - - // Alphabetize the dependency names - depKeys := make([]string, 0, len(n.Deps)) - depMapping := make(map[string]*Dependency) - for _, d := range n.Deps { - depMapping[d.Target.Name] = d - depKeys = append(depKeys, d.Target.Name) - } - sort.Strings(depKeys) - - for _, k := range depKeys { - dep := depMapping[k] - buf.WriteString(fmt.Sprintf( - " %s -> %s\n", - dep.Source, - dep.Target)) - } - } - - return buf.String() -} - -// Validate is used to ensure that a few properties of the graph are not violated: -// 1) There must be a single "root", or source on which nothing depends. -// 2) All nouns in the graph must be reachable from the root -// 3) The graph must be cycle free, meaning there are no cicular dependencies -func (g *Graph) Validate() error { - // Convert to node list - nodes := make([]digraph.Node, len(g.Nouns)) - for i, n := range g.Nouns { - nodes[i] = n - } - - // Create a validate erro - vErr := &ValidateError{} - - // Search for all the sources, if we have only 1, it must be the root - if sources := digraph.Sources(nodes); len(sources) != 1 { - vErr.MissingRoot = true - goto CHECK_CYCLES - } else { - g.Root = sources[0].(*Noun) - } - - // Check reachability - if unreached := digraph.Unreachable(g.Root, nodes); len(unreached) > 0 { - vErr.Unreachable = make([]*Noun, len(unreached)) - for i, u := range unreached { - vErr.Unreachable[i] = u.(*Noun) - } - } - -CHECK_CYCLES: - // Check for cycles - if cycles := digraph.StronglyConnectedComponents(nodes, true); len(cycles) > 0 { - vErr.Cycles = make([][]*Noun, len(cycles)) - for i, cycle := range cycles { - group := make([]*Noun, len(cycle)) - for j, n := range cycle { - group[j] = n.(*Noun) - } - vErr.Cycles[i] = group - } - } - - // Check for loops to yourself - for _, n := range g.Nouns { - for _, d := range n.Deps { - if d.Source == d.Target { - vErr.Cycles = append(vErr.Cycles, []*Noun{n}) - } - } - } - - // Return the detailed error - if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil { - return vErr - } - return nil -} - -// Walk will walk the tree depth-first (dependency first) and call -// the callback. -// -// The callbacks will be called in parallel, so if you need non-parallelism, -// then introduce a lock in your callback. -func (g *Graph) Walk(fn WalkFunc) error { - // Set so we don't callback for a single noun multiple times - var seenMapL sync.RWMutex - seenMap := make(map[*Noun]chan struct{}) - seenMap[g.Root] = make(chan struct{}) - - // Keep track of what nodes errored. - var errMapL sync.RWMutex - errMap := make(map[*Noun]struct{}) - - // Build the list of things to visit - tovisit := make([]*Noun, 1, len(g.Nouns)) - tovisit[0] = g.Root - - // Spawn off all our goroutines to walk the tree - errCh := make(chan error) - for len(tovisit) > 0 { - // Grab the current thing to use - n := len(tovisit) - current := tovisit[n-1] - tovisit = tovisit[:n-1] - - // Go through each dependency and run that first - for _, dep := range current.Deps { - if _, ok := seenMap[dep.Target]; !ok { - seenMapL.Lock() - seenMap[dep.Target] = make(chan struct{}) - seenMapL.Unlock() - tovisit = append(tovisit, dep.Target) - } - } - - // Spawn off a goroutine to execute our callback once - // all our dependencies are satisfied. - go func(current *Noun) { - seenMapL.RLock() - closeCh := seenMap[current] - seenMapL.RUnlock() - - defer close(closeCh) - - // Wait for all our dependencies - for _, dep := range current.Deps { - seenMapL.RLock() - ch := seenMap[dep.Target] - seenMapL.RUnlock() - - // Wait for the dep to be run - <-ch - - // Check if any dependencies errored. If so, - // then return right away, we won't walk it. - errMapL.RLock() - _, errOk := errMap[dep.Target] - errMapL.RUnlock() - if errOk { - return - } - } - - // Call our callback! - if err := fn(current); err != nil { - errMapL.Lock() - errMap[current] = struct{}{} - errMapL.Unlock() - - errCh <- err - } - }(current) - } - - // Aggregate channel that is closed when all goroutines finish - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - - for _, ch := range seenMap { - <-ch - } - }() - - // Wait for finish OR an error - select { - case <-doneCh: - return nil - case err := <-errCh: - // Drain the error channel - go func() { - for _ = range errCh { - // Nothing - } - }() - - // Wait for the goroutines to end - <-doneCh - close(errCh) - - return err - } -} - -// DependsOn returns the set of nouns that have a -// dependency on a given noun. This can be used to find -// the incoming edges to a noun. -func (g *Graph) DependsOn(n *Noun) []*Noun { - var incoming []*Noun -OUTER: - for _, other := range g.Nouns { - if other == n { - continue - } - for _, d := range other.Deps { - if d.Target == n { - incoming = append(incoming, other) - continue OUTER - } - } - } - return incoming -} diff --git a/depgraph/graph_test.go b/depgraph/graph_test.go deleted file mode 100644 index c883b1417..000000000 --- a/depgraph/graph_test.go +++ /dev/null @@ -1,467 +0,0 @@ -package depgraph - -import ( - "fmt" - "reflect" - "sort" - "strings" - "sync" - "testing" -) - -// ParseNouns is used to parse a string in the format of: -// a -> b ; edge name -// b -> c -// Into a series of nouns and dependencies -func ParseNouns(s string) map[string]*Noun { - lines := strings.Split(s, "\n") - nodes := make(map[string]*Noun) - for _, line := range lines { - var edgeName string - if idx := strings.Index(line, ";"); idx >= 0 { - edgeName = strings.Trim(line[idx+1:], " \t\r\n") - line = line[:idx] - } - parts := strings.SplitN(line, "->", 2) - if len(parts) != 2 { - continue - } - head_name := strings.Trim(parts[0], " \t\r\n") - tail_name := strings.Trim(parts[1], " \t\r\n") - head := nodes[head_name] - if head == nil { - head = &Noun{Name: head_name} - nodes[head_name] = head - } - tail := nodes[tail_name] - if tail == nil { - tail = &Noun{Name: tail_name} - nodes[tail_name] = tail - } - edge := &Dependency{ - Name: edgeName, - Source: head, - Target: tail, - } - head.Deps = append(head.Deps, edge) - } - return nodes -} - -func NounMapToList(m map[string]*Noun) []*Noun { - list := make([]*Noun, 0, len(m)) - for _, n := range m { - list = append(list, n) - } - return list -} - -func TestGraph_Noun(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -b -> e -c -> d -c -> e`) - - g := &Graph{ - Name: "Test", - Nouns: NounMapToList(nodes), - } - - n := g.Noun("a") - if n == nil { - t.Fatal("should not be nil") - } - if n.Name != "a" { - t.Fatalf("bad: %#v", n) - } -} - -func TestGraph_String(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -b -> e -c -> d -c -> e`) - - g := &Graph{ - Name: "Test", - Nouns: NounMapToList(nodes), - Root: nodes["a"], - } - actual := g.String() - - expected := ` -root: a -a - a -> b - a -> c -b - b -> d - b -> e -c - c -> d - c -> e -d -e -` - - actual = strings.TrimSpace(actual) - expected = strings.TrimSpace(expected) - if actual != expected { - t.Fatalf("bad:\n%s\n!=\n%s", actual, expected) - } -} - -func TestGraph_Validate(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -b -> e -c -> d -c -> e`) - list := NounMapToList(nodes) - - g := &Graph{Name: "Test", Nouns: list} - if err := g.Validate(); err != nil { - t.Fatalf("err: %v", err) - } -} - -func TestGraph_Validate_Cycle(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -d -> b`) - list := NounMapToList(nodes) - - g := &Graph{Name: "Test", Nouns: list} - err := g.Validate() - if err == nil { - t.Fatalf("expected err") - } - - vErr, ok := err.(*ValidateError) - if !ok { - t.Fatalf("expected validate error") - } - - if len(vErr.Cycles) != 1 { - t.Fatalf("expected cycles") - } - - cycle := vErr.Cycles[0] - cycleNodes := make([]string, len(cycle)) - for i, c := range cycle { - cycleNodes[i] = c.Name - } - sort.Strings(cycleNodes) - - if cycleNodes[0] != "b" { - t.Fatalf("bad: %v", cycle) - } - if cycleNodes[1] != "d" { - t.Fatalf("bad: %v", cycle) - } -} - -func TestGraph_Validate_MultiRoot(t *testing.T) { - nodes := ParseNouns(`a -> b -c -> d`) - list := NounMapToList(nodes) - - g := &Graph{Name: "Test", Nouns: list} - err := g.Validate() - if err == nil { - t.Fatalf("expected err") - } - - vErr, ok := err.(*ValidateError) - if !ok { - t.Fatalf("expected validate error") - } - - if !vErr.MissingRoot { - t.Fatalf("expected missing root") - } -} - -func TestGraph_Validate_NoRoot(t *testing.T) { - nodes := ParseNouns(`a -> b -b -> a`) - list := NounMapToList(nodes) - - g := &Graph{Name: "Test", Nouns: list} - err := g.Validate() - if err == nil { - t.Fatalf("expected err") - } - - vErr, ok := err.(*ValidateError) - if !ok { - t.Fatalf("expected validate error") - } - - if !vErr.MissingRoot { - t.Fatalf("expected missing root") - } -} - -func TestGraph_Validate_Unreachable(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -x -> x`) - list := NounMapToList(nodes) - - g := &Graph{Name: "Test", Nouns: list} - err := g.Validate() - if err == nil { - t.Fatalf("expected err") - } - - vErr, ok := err.(*ValidateError) - if !ok { - t.Fatalf("expected validate error") - } - - if len(vErr.Unreachable) != 1 { - t.Fatalf("expected unreachable") - } - - if vErr.Unreachable[0].Name != "x" { - t.Fatalf("bad: %v", vErr.Unreachable[0]) - } -} - -type VersionMeta int -type VersionConstraint struct { - Min int - Max int -} - -func (v *VersionConstraint) Satisfied(head, tail *Noun) (bool, error) { - vers := int(tail.Meta.(VersionMeta)) - if vers < v.Min { - return false, fmt.Errorf("version %d below minimum %d", - vers, v.Min) - } else if vers > v.Max { - return false, fmt.Errorf("version %d above maximum %d", - vers, v.Max) - } - return true, nil -} - -func (v *VersionConstraint) String() string { - return "version" -} - -func TestGraph_ConstraintViolation(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -b -> e -c -> d -c -> e`) - list := NounMapToList(nodes) - - // Add a version constraint - vers := &VersionConstraint{1, 3} - - // Introduce some constraints - depB := nodes["a"].Deps[0] - depB.Constraints = []Constraint{vers} - depC := nodes["a"].Deps[1] - depC.Constraints = []Constraint{vers} - - // Add some versions - nodes["b"].Meta = VersionMeta(0) - nodes["c"].Meta = VersionMeta(4) - - g := &Graph{Name: "Test", Nouns: list} - err := g.Validate() - if err != nil { - t.Fatalf("err: %v", err) - } - - err = g.CheckConstraints() - if err == nil { - t.Fatalf("Expected err") - } - - cErr, ok := err.(*ConstraintError) - if !ok { - t.Fatalf("expected constraint error") - } - - if len(cErr.Violations) != 2 { - t.Fatalf("expected 2 violations: %v", cErr) - } - - if cErr.Violations[0].Error() != "Constraint version between a and b violated: version 0 below minimum 1" { - t.Fatalf("err: %v", cErr.Violations[0]) - } - - if cErr.Violations[1].Error() != "Constraint version between a and c violated: version 4 above maximum 3" { - t.Fatalf("err: %v", cErr.Violations[1]) - } -} - -func TestGraph_Constraint_NoViolation(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -b -> e -c -> d -c -> e`) - list := NounMapToList(nodes) - - // Add a version constraint - vers := &VersionConstraint{1, 3} - - // Introduce some constraints - depB := nodes["a"].Deps[0] - depB.Constraints = []Constraint{vers} - depC := nodes["a"].Deps[1] - depC.Constraints = []Constraint{vers} - - // Add some versions - nodes["b"].Meta = VersionMeta(2) - nodes["c"].Meta = VersionMeta(3) - - g := &Graph{Name: "Test", Nouns: list} - err := g.Validate() - if err != nil { - t.Fatalf("err: %v", err) - } - - err = g.CheckConstraints() - if err != nil { - t.Fatalf("err: %v", err) - } -} - -func TestGraphWalk(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -b -> e -c -> d -c -> e`) - list := NounMapToList(nodes) - g := &Graph{Name: "Test", Nouns: list} - if err := g.Validate(); err != nil { - t.Fatalf("err: %s", err) - } - - var namesLock sync.Mutex - names := make([]string, 0, 0) - err := g.Walk(func(n *Noun) error { - namesLock.Lock() - defer namesLock.Unlock() - names = append(names, n.Name) - return nil - }) - if err != nil { - t.Fatalf("err: %s", err) - } - - expected := [][]string{ - {"e", "d", "c", "b", "a"}, - {"e", "d", "b", "c", "a"}, - {"d", "e", "c", "b", "a"}, - {"d", "e", "b", "c", "a"}, - } - found := false - for _, expect := range expected { - if reflect.DeepEqual(expect, names) { - found = true - break - } - } - if !found { - t.Fatalf("bad: %#v", names) - } -} - -func TestGraphWalk_error(t *testing.T) { - nodes := ParseNouns(`a -> b -b -> c -a -> d -a -> e -e -> f -f -> g -g -> h`) - list := NounMapToList(nodes) - g := &Graph{Name: "Test", Nouns: list} - if err := g.Validate(); err != nil { - t.Fatalf("err: %s", err) - } - - // We repeat this a lot because sometimes timing causes - // a false positive. - for i := 0; i < 100; i++ { - var lock sync.Mutex - var walked []string - err := g.Walk(func(n *Noun) error { - lock.Lock() - defer lock.Unlock() - - walked = append(walked, n.Name) - - if n.Name == "b" { - return fmt.Errorf("foo") - } - - return nil - }) - if err == nil { - t.Fatal("should error") - } - - sort.Strings(walked) - - expected := []string{"b", "c", "d", "e", "f", "g", "h"} - if !reflect.DeepEqual(walked, expected) { - t.Fatalf("bad: %#v", walked) - } - } -} - -func TestGraph_DependsOn(t *testing.T) { - nodes := ParseNouns(`a -> b -a -> c -b -> d -b -> e -c -> d -c -> e`) - - g := &Graph{ - Name: "Test", - Nouns: NounMapToList(nodes), - } - - dNoun := g.Noun("d") - incoming := g.DependsOn(dNoun) - - if len(incoming) != 2 { - t.Fatalf("bad: %#v", incoming) - } - - var hasB, hasC bool - for _, in := range incoming { - switch in.Name { - case "b": - hasB = true - case "c": - hasC = true - default: - t.Fatalf("Bad: %#v", in) - } - } - if !hasB || !hasC { - t.Fatalf("missing incoming edge") - } -} diff --git a/depgraph/noun.go b/depgraph/noun.go deleted file mode 100644 index 8f14adfe1..000000000 --- a/depgraph/noun.go +++ /dev/null @@ -1,33 +0,0 @@ -package depgraph - -import ( - "fmt" - - "github.com/hashicorp/terraform/digraph" -) - -// Nouns are the key structure of the dependency graph. They can -// be used to represent all objects in the graph. They are linked -// by depedencies. -type Noun struct { - Name string // Opaque name - Meta interface{} - Deps []*Dependency -} - -// Edges returns the out-going edges of a Noun -func (n *Noun) Edges() []digraph.Edge { - edges := make([]digraph.Edge, len(n.Deps)) - for idx, dep := range n.Deps { - edges[idx] = dep - } - return edges -} - -func (n *Noun) GoString() string { - return fmt.Sprintf("*%#v", *n) -} - -func (n *Noun) String() string { - return n.Name -}