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.
This commit is contained in:
parent
8e561d9f95
commit
70305266dc
2 changed files with 226 additions and 1 deletions
|
@ -2,6 +2,7 @@ package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
@ -41,6 +42,35 @@ type catalogUpdate struct {
|
||||||
Nodes []*api.ServiceEntry
|
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 {
|
func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string {
|
||||||
watchCh := make(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 {
|
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 {
|
for _, tag := range node.Service.Tags {
|
||||||
serviceName += "--" + normalize(tag)
|
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 {
|
templateObjects := struct {
|
||||||
Services []*serviceUpdate
|
Services []*serviceUpdate
|
||||||
|
|
|
@ -2,6 +2,7 @@ package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containous/traefik/types"
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue