From 70305266dc19313e167e84732c651b09dbb1bf89 Mon Sep 17 00:00:00 2001 From: David Keijser Date: Mon, 20 Jun 2016 19:13:22 +0200 Subject: [PATCH] Sort nodes before creating consul catalog config The watch of consul can return for various reasons and not of all of them require a reload of the config. The order of nodes provided by consul is not stable so to ensure a identical config is generated for an identical server set the nodes needs to be sorted before creating the config. --- provider/consul_catalog.go | 34 +++++- provider/consul_catalog_test.go | 193 ++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) diff --git a/provider/consul_catalog.go b/provider/consul_catalog.go index cce6db185..8622b66e0 100644 --- a/provider/consul_catalog.go +++ b/provider/consul_catalog.go @@ -2,6 +2,7 @@ package provider import ( "errors" + "sort" "strconv" "strings" "text/template" @@ -41,6 +42,35 @@ type catalogUpdate struct { Nodes []*api.ServiceEntry } +type nodeSorter []*api.ServiceEntry + +func (a nodeSorter) Len() int { + return len(a) +} + +func (a nodeSorter) Swap(i int, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a nodeSorter) Less(i int, j int) bool { + lentr := a[i] + rentr := a[j] + + ls := strings.ToLower(lentr.Service.Service) + lr := strings.ToLower(rentr.Service.Service) + + if ls != lr { + return ls < lr + } + if lentr.Service.Address != rentr.Service.Address { + return lentr.Service.Address < rentr.Service.Address + } + if lentr.Node.Address != rentr.Node.Address { + return lentr.Node.Address < rentr.Node.Address + } + return lentr.Service.Port < rentr.Service.Port +} + func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string { watchCh := make(chan map[string][]string) @@ -139,7 +169,7 @@ func (provider *ConsulCatalog) getBackendAddress(node *api.ServiceEntry) string } func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string { - serviceName := node.Service.Service + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port) + serviceName := strings.ToLower(node.Service.Service) + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port) for _, tag := range node.Service.Tags { serviceName += "--" + normalize(tag) @@ -200,6 +230,8 @@ func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Confi } } + // Ensure a stable ordering of nodes so that identical configurations may be detected + sort.Sort(nodeSorter(allNodes)) templateObjects := struct { Services []*serviceUpdate diff --git a/provider/consul_catalog_test.go b/provider/consul_catalog_test.go index 1c4e49763..f83e353af 100644 --- a/provider/consul_catalog_test.go +++ b/provider/consul_catalog_test.go @@ -2,6 +2,7 @@ package provider import ( "reflect" + "sort" "testing" "github.com/containous/traefik/types" @@ -274,3 +275,195 @@ func TestConsulCatalogBuildConfig(t *testing.T) { } } } + +func TestConsulCatalogNodeSorter(t *testing.T) { + cases := []struct { + nodes []*api.ServiceEntry + expected []*api.ServiceEntry + }{ + { + nodes: []*api.ServiceEntry{}, + expected: []*api.ServiceEntry{}, + }, + { + nodes: []*api.ServiceEntry{ + { + Service: &api.AgentService{ + Service: "foo", + Address: "127.0.0.1", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.1", + }, + }, + }, + expected: []*api.ServiceEntry{ + { + Service: &api.AgentService{ + Service: "foo", + Address: "127.0.0.1", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.1", + }, + }, + }, + }, + { + nodes: []*api.ServiceEntry{ + { + Service: &api.AgentService{ + Service: "foo", + Address: "127.0.0.2", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.2", + }, + }, + { + Service: &api.AgentService{ + Service: "bar", + Address: "127.0.0.2", + Port: 81, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.2", + }, + }, + { + Service: &api.AgentService{ + Service: "foo", + Address: "127.0.0.1", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.1", + }, + }, + { + Service: &api.AgentService{ + Service: "bar", + Address: "127.0.0.2", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.2", + }, + }, + }, + expected: []*api.ServiceEntry{ + { + Service: &api.AgentService{ + Service: "bar", + Address: "127.0.0.2", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.2", + }, + }, + { + Service: &api.AgentService{ + Service: "bar", + Address: "127.0.0.2", + Port: 81, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.2", + }, + }, + { + Service: &api.AgentService{ + Service: "foo", + Address: "127.0.0.1", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.1", + }, + }, + { + Service: &api.AgentService{ + Service: "foo", + Address: "127.0.0.2", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.2", + }, + }, + }, + }, + { + nodes: []*api.ServiceEntry{ + { + Service: &api.AgentService{ + Service: "foo", + Address: "", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.2", + }, + }, + { + Service: &api.AgentService{ + Service: "foo", + Address: "", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.1", + }, + }, + }, + expected: []*api.ServiceEntry{ + { + Service: &api.AgentService{ + Service: "foo", + Address: "", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.1", + }, + }, + { + Service: &api.AgentService{ + Service: "foo", + Address: "", + Port: 80, + }, + Node: &api.Node{ + Node: "localhost", + Address: "127.0.0.2", + }, + }, + }, + }, + } + + for _, c := range cases { + sort.Sort(nodeSorter(c.nodes)) + actual := c.nodes + if !reflect.DeepEqual(actual, c.expected) { + t.Fatalf("expected %q, got %q", c.expected, actual) + } + } +}