From 5b92e7793dfebbd8e9b1700c2676cc5f56fad83e Mon Sep 17 00:00:00 2001 From: "Alex Ellis (OpenFaaS Ltd)" Date: Wed, 17 Jun 2020 14:33:58 +0100 Subject: [PATCH] Move graph logic into package Graph logic moves into depgraph package and makes internal fields inaccessible. Completes feedback from @LucasRoesler from previous PR where the dependency graph was added for 0.9.1 Signed-off-by: Alex Ellis (OpenFaaS Ltd) --- pkg/depends_on.go | 37 ----------------- pkg/{graph.go => depgraph/depgraph.go} | 40 ++++++++++++++++-- .../depgraph_test.go} | 2 +- pkg/deployment_order.go | 41 +++++++++++++++++++ ...ds_on_test.go => deployment_order_test.go} | 24 +++++------ pkg/supervisor.go | 2 +- 6 files changed, 91 insertions(+), 55 deletions(-) delete mode 100644 pkg/depends_on.go rename pkg/{graph.go => depgraph/depgraph.go} (58%) rename pkg/{graph_test.go => depgraph/depgraph_test.go} (97%) create mode 100644 pkg/deployment_order.go rename pkg/{depends_on_test.go => deployment_order_test.go} (86%) diff --git a/pkg/depends_on.go b/pkg/depends_on.go deleted file mode 100644 index 40be615..0000000 --- a/pkg/depends_on.go +++ /dev/null @@ -1,37 +0,0 @@ -package pkg - -import ( - "log" -) - -func buildInstallOrder(svcs []Service) []string { - graph := Graph{nodes: []*Node{}} - - nodeMap := map[string]*Node{} - for _, s := range svcs { - n := &Node{Name: s.Name} - nodeMap[s.Name] = n - graph.nodes = append(graph.nodes, n) - } - - for _, s := range svcs { - for _, d := range s.DependsOn { - nodeMap[s.Name].Edges = append(nodeMap[s.Name].Edges, nodeMap[d]) - } - } - - resolved := &Graph{} - unresolved := &Graph{} - for _, g := range graph.nodes { - resolve(g, resolved, unresolved) - } - - log.Printf("Start-up order:\n") - order := []string{} - for _, node := range resolved.nodes { - log.Printf("- %s\n", node.Name) - order = append(order, node.Name) - } - - return order -} diff --git a/pkg/graph.go b/pkg/depgraph/depgraph.go similarity index 58% rename from pkg/graph.go rename to pkg/depgraph/depgraph.go index 36cdd4c..ccd76f4 100644 --- a/pkg/graph.go +++ b/pkg/depgraph/depgraph.go @@ -1,4 +1,4 @@ -package pkg +package depgraph import "log" @@ -14,6 +14,17 @@ type Graph struct { nodes []*Node } +func NewDepgraph() *Graph { + return &Graph{ + nodes: []*Node{}, + } +} + +// Nodes returns the nodes within the graph +func (g *Graph) Nodes() []*Node { + return g.nodes +} + // Contains returns true if the target Node is found // in its list func (g *Graph) Contains(target *Node) bool { @@ -47,10 +58,31 @@ func (g *Graph) Remove(target *Node) { } } -// resolve finds the order of dependencies for a graph -// of nodes. -// Inspired by algorithm from +// Resolve retruns a list of node names in order of their dependencies. +// A use case may be for determining the correct order to install +// software packages, or to start services. +// Based upon the algorithm described by Ferry Boender in the following article // https://www.electricmonk.nl/log/2008/08/07/dependency-resolving-algorithm/ +func (g *Graph) Resolve() []string { + resolved := &Graph{} + unresolved := &Graph{} + for _, node := range g.nodes { + resolve(node, resolved, unresolved) + } + + order := []string{} + + for _, node := range resolved.Nodes() { + order = append(order, node.Name) + } + + return order +} + +// resolve mutates the resolved graph for a given starting +// node. The unresolved graph is used to detect a circular graph +// error and will throw a panic. This can be caught with a resolve +// in a go routine. func resolve(node *Node, resolved, unresolved *Graph) { unresolved.Add(node) diff --git a/pkg/graph_test.go b/pkg/depgraph/depgraph_test.go similarity index 97% rename from pkg/graph_test.go rename to pkg/depgraph/depgraph_test.go index b72a354..e144b9b 100644 --- a/pkg/graph_test.go +++ b/pkg/depgraph/depgraph_test.go @@ -1,4 +1,4 @@ -package pkg +package depgraph import "testing" diff --git a/pkg/deployment_order.go b/pkg/deployment_order.go new file mode 100644 index 0000000..2d11b71 --- /dev/null +++ b/pkg/deployment_order.go @@ -0,0 +1,41 @@ +package pkg + +import ( + "log" + + "github.com/openfaas/faasd/pkg/depgraph" +) + +func buildDeploymentOrder(svcs []Service) []string { + + graph := buildServiceGraph(svcs) + + order := graph.Resolve() + + log.Printf("Start-up order:\n") + for _, node := range order { + log.Printf("- %s\n", node) + } + + return order +} + +func buildServiceGraph(svcs []Service) *depgraph.Graph { + graph := depgraph.NewDepgraph() + + nodeMap := map[string]*depgraph.Node{} + for _, s := range svcs { + n := &depgraph.Node{Name: s.Name} + nodeMap[s.Name] = n + graph.Add(n) + + } + + for _, s := range svcs { + for _, d := range s.DependsOn { + nodeMap[s.Name].Edges = append(nodeMap[s.Name].Edges, nodeMap[d]) + } + } + + return graph +} diff --git a/pkg/depends_on_test.go b/pkg/deployment_order_test.go similarity index 86% rename from pkg/depends_on_test.go rename to pkg/deployment_order_test.go index 88fa6c0..34fe4ac 100644 --- a/pkg/depends_on_test.go +++ b/pkg/deployment_order_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func Test_buildInstallOrder_ARequiresB(t *testing.T) { +func Test_buildDeploymentOrder_ARequiresB(t *testing.T) { svcs := []Service{ { Name: "A", @@ -17,7 +17,7 @@ func Test_buildInstallOrder_ARequiresB(t *testing.T) { }, } - order := buildInstallOrder(svcs) + order := buildDeploymentOrder(svcs) if len(order) < len(svcs) { t.Fatalf("length of order too short: %d", len(order)) @@ -30,7 +30,7 @@ func Test_buildInstallOrder_ARequiresB(t *testing.T) { } } -func Test_buildInstallOrder_ARequiresBAndC(t *testing.T) { +func Test_buildDeploymentOrder_ARequiresBAndC(t *testing.T) { svcs := []Service{ { Name: "A", @@ -46,7 +46,7 @@ func Test_buildInstallOrder_ARequiresBAndC(t *testing.T) { }, } - order := buildInstallOrder(svcs) + order := buildDeploymentOrder(svcs) if len(order) < len(svcs) { t.Fatalf("length of order too short: %d", len(order)) @@ -65,7 +65,7 @@ func Test_buildInstallOrder_ARequiresBAndC(t *testing.T) { } -func Test_buildInstallOrder_ARequiresBRequiresC(t *testing.T) { +func Test_buildDeploymentOrder_ARequiresBRequiresC(t *testing.T) { svcs := []Service{ { Name: "A", @@ -81,7 +81,7 @@ func Test_buildInstallOrder_ARequiresBRequiresC(t *testing.T) { }, } - order := buildInstallOrder(svcs) + order := buildDeploymentOrder(svcs) if len(order) < len(svcs) { t.Fatalf("length of order too short: %d", len(order)) @@ -104,7 +104,7 @@ func Test_buildInstallOrder_ARequiresBRequiresC(t *testing.T) { } } -func Test_buildInstallOrderCircularARequiresBRequiresA(t *testing.T) { +func Test_buildDeploymentOrderCircularARequiresBRequiresA(t *testing.T) { svcs := []Service{ { Name: "A", @@ -118,12 +118,12 @@ func Test_buildInstallOrderCircularARequiresBRequiresA(t *testing.T) { defer func() { recover() }() - buildInstallOrder(svcs) + buildDeploymentOrder(svcs) t.Fatalf("did not panic as expected") } -func Test_buildInstallOrderComposeFile(t *testing.T) { +func Test_buildDeploymentOrderComposeFile(t *testing.T) { // svcs := []Service{} file, err := LoadComposeFileWithArch("../", "docker-compose.yaml", func() (string, string) { return "x86_64", "Linux" @@ -145,7 +145,7 @@ func Test_buildInstallOrderComposeFile(t *testing.T) { } } - order := buildInstallOrder(svcs) + order := buildDeploymentOrder(svcs) if len(order) < len(svcs) { t.Fatalf("length of order too short: %d", len(order)) @@ -167,7 +167,7 @@ func Test_buildInstallOrderComposeFile(t *testing.T) { } } -func Test_buildInstallOrderOpenFaaS(t *testing.T) { +func Test_buildDeploymentOrderOpenFaaS(t *testing.T) { svcs := []Service{ { Name: "queue-worker", @@ -191,7 +191,7 @@ func Test_buildInstallOrderOpenFaaS(t *testing.T) { }, } - order := buildInstallOrder(svcs) + order := buildDeploymentOrder(svcs) if len(order) < len(svcs) { t.Fatalf("length of order too short: %d", len(order)) diff --git a/pkg/supervisor.go b/pkg/supervisor.go index 86c00ea..7608680 100644 --- a/pkg/supervisor.go +++ b/pkg/supervisor.go @@ -112,7 +112,7 @@ func (s *Supervisor) Start(svcs []Service) error { } } - order := buildInstallOrder(svcs) + order := buildDeploymentOrder(svcs) for _, key := range order {