From a99673122e297cf86cd734836a1201c4e061965e Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 27 Nov 2019 16:24:06 +0100 Subject: [PATCH] Service registered with same id on Consul Catalog --- integration/consul_catalog_test.go | 124 ++++++++++++++---- .../resources/compose/consul_catalog.yml | 10 +- pkg/provider/consulcatalog/config.go | 2 +- pkg/provider/consulcatalog/config_test.go | 115 ++++++++++++++++ pkg/provider/consulcatalog/consul_catalog.go | 2 + 5 files changed, 224 insertions(+), 29 deletions(-) diff --git a/integration/consul_catalog_test.go b/integration/consul_catalog_test.go index 173b36aa1..70905356b 100644 --- a/integration/consul_catalog_test.go +++ b/integration/consul_catalog_test.go @@ -15,8 +15,10 @@ import ( type ConsulCatalogSuite struct { BaseSuite - consulClient *api.Client - consulAddress string + consulClient *api.Client + consulAgentClient *api.Client + consulAddress string + consulAgentAddress string } func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { @@ -32,6 +34,13 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) { // Wait for consul to elect itself leader err = s.waitToElectConsulLeader() c.Assert(err, checker.IsNil) + + s.consulAgentAddress = "http://" + s.composeProject.Container(c, "consul-agent").NetworkSettings.IPAddress + ":8500" + clientAgent, err := api.NewClient(&api.Config{ + Address: s.consulAgentAddress, + }) + c.Check(err, check.IsNil) + s.consulAgentClient = clientAgent } func (s *ConsulCatalogSuite) waitToElectConsulLeader() error { @@ -53,13 +62,17 @@ func (s *ConsulCatalogSuite) TearDownSuite(c *check.C) { } } -func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tags []string) error { +func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tags []string, onAgent bool) error { iPort, err := strconv.Atoi(port) if err != nil { return err } + client := s.consulClient + if onAgent { + client = s.consulAgentClient + } - return s.consulClient.Agent().ServiceRegister(&api.AgentServiceRegistration{ + return client.Agent().ServiceRegister(&api.AgentServiceRegistration{ ID: id, Name: name, Address: address, @@ -68,16 +81,20 @@ func (s *ConsulCatalogSuite) registerService(id, name, address, port string, tag }) } -func (s *ConsulCatalogSuite) deregisterService(id string) error { - return s.consulClient.Agent().ServiceDeregister(id) +func (s *ConsulCatalogSuite) deregisterService(id string, onAgent bool) error { + client := s.consulClient + if onAgent { + client = s.consulAgentClient + } + return client.Agent().ServiceDeregister(id) } func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *check.C) { - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) - err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) + err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) - err = s.registerService("whoami3", "whoami", s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) + err = s.registerService("whoami3", "whoami", s.composeProject.Container(c, "whoami3").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) tempObjects := struct { @@ -102,11 +119,11 @@ func (s *ConsulCatalogSuite) TestWithNotExposedByDefaultAndDefaultsSettings(c *c err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami2") + err = s.deregisterService("whoami2", false) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami3") + err = s.deregisterService("whoami3", false) c.Assert(err, checker.IsNil) } @@ -118,7 +135,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) { "traefik.http.services.service1.loadBalancer.server.url=http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, } - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false) c.Assert(err, checker.IsNil) tempObjects := struct { @@ -139,7 +156,7 @@ func (s *ConsulCatalogSuite) TestByLabels(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/whoami", 2*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2", "Hostname: whoami3")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) } @@ -155,7 +172,7 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) defer os.Remove(file) - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) cmd, display := s.traefikCmd(withConfigFile(file)) @@ -171,7 +188,7 @@ func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) { err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) } @@ -187,7 +204,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) { file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) defer os.Remove(file) - err := s.registerService("whoami1", "whoami", "", "80", []string{"traefik.enable=true"}) + err := s.registerService("whoami1", "whoami", "", "80", []string{"traefik.enable=true"}, false) c.Assert(err, checker.IsNil) cmd, display := s.traefikCmd(withConfigFile(file)) @@ -202,7 +219,7 @@ func (s *ConsulCatalogSuite) TestRegisterServiceWithoutIP(c *check.C) { err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("whoami@consulcatalog", "\"http://127.0.0.1:80\": \"UP\"")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) } @@ -219,7 +236,7 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) { file := s.adaptFile(c, "fixtures/consul_catalog/simple.toml", tempObjects) defer os.Remove(file) - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", nil) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", nil, false) c.Assert(err, checker.IsNil) // Start traefik @@ -236,7 +253,7 @@ func (s *ConsulCatalogSuite) TestDefaultConsulService(c *check.C) { err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) } @@ -259,7 +276,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) { "traefik.tcp.Services.Super.Loadbalancer.server.port=8080", } - err := s.registerService("whoamitcp", "whoamitcp", s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress, "8080", labels) + err := s.registerService("whoamitcp", "whoamitcp", s.composeProject.Container(c, "whoamitcp").NetworkSettings.IPAddress, "8080", labels, false) c.Assert(err, checker.IsNil) // Start traefik @@ -277,7 +294,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithTCPLabels(c *check.C) { c.Assert(who, checker.Contains, "whoamitcp") - err = s.deregisterService("whoamitcp") + err = s.deregisterService("whoamitcp", false) c.Assert(err, checker.IsNil) } @@ -297,14 +314,14 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { labels := []string{ "traefik.http.Routers.Super.Rule=Host(`my.super.host`)", } - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false) c.Assert(err, checker.IsNil) // Start another container by replacing a '.' by a '-' labels = []string{ "traefik.http.Routers.SuperHost.Rule=Host(`my-super.host`)", } - err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels) + err = s.registerService("whoami2", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels, false) c.Assert(err, checker.IsNil) // Start traefik @@ -328,10 +345,63 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithLabels(c *check.C) { err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami2")) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami1") + err = s.deregisterService("whoami1", false) c.Assert(err, checker.IsNil) - err = s.deregisterService("whoami2") + err = s.deregisterService("whoami2", false) + c.Assert(err, checker.IsNil) +} + +func (s *ConsulCatalogSuite) TestSameServiceIDOnDifferentConsulAgent(c *check.C) { + tempObjects := struct { + ConsulAddress string + DefaultRule string + }{ + ConsulAddress: s.consulAddress, + DefaultRule: "Host(`{{ normalize .Name }}.consul.localhost`)", + } + + file := s.adaptFile(c, "fixtures/consul_catalog/default_not_exposed.toml", tempObjects) + defer os.Remove(file) + + // Start a container with some labels + labels := []string{ + "traefik.enable=true", + "traefik.http.Routers.Super.service=whoami", + "traefik.http.Routers.Super.Rule=Host(`my.super.host`)", + } + err := s.registerService("whoami", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false) + c.Assert(err, checker.IsNil) + + err = s.registerService("whoami", "whoami", s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress, "80", labels, true) + c.Assert(err, checker.IsNil) + + // Start traefik + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) + err = cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "my.super.host" + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), try.BodyContainsOr("Hostname: whoami1", "Hostname: whoami2")) + c.Assert(err, checker.IsNil) + + req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil) + c.Assert(err, checker.IsNil) + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(200), + try.BodyContainsOr(s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, + s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress)) + c.Assert(err, checker.IsNil) + + err = s.deregisterService("whoami1", false) + c.Assert(err, checker.IsNil) + + err = s.deregisterService("whoami2", true) c.Assert(err, checker.IsNil) } @@ -351,7 +421,7 @@ func (s *ConsulCatalogSuite) TestConsulServiceWithOneMissingLabels(c *check.C) { labels := []string{ "traefik.random.value=my.super.host", } - err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels) + err := s.registerService("whoami1", "whoami", s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress, "80", labels, false) c.Assert(err, checker.IsNil) // Start traefik diff --git a/integration/resources/compose/consul_catalog.yml b/integration/resources/compose/consul_catalog.yml index 9299d95f3..852dfd69c 100644 --- a/integration/resources/compose/consul_catalog.yml +++ b/integration/resources/compose/consul_catalog.yml @@ -1,7 +1,15 @@ consul: - image: consul:1.6.1 + image: consul:1.6.2 ports: - 8500:8500 + command: "agent -server -bootstrap -ui -client 0.0.0.0" +consul-agent: + image: consul:1.6.2 + ports: + - 8501:8500 + command: "agent -retry-join consul -client 0.0.0.0" + links: + - consul whoami1: image: containous/whoami:v1.3.0 hostname: whoami1 diff --git a/pkg/provider/consulcatalog/config.go b/pkg/provider/consulcatalog/config.go index 61a360490..0d2a476fb 100644 --- a/pkg/provider/consulcatalog/config.go +++ b/pkg/provider/consulcatalog/config.go @@ -18,7 +18,7 @@ func (p *Provider) buildConfiguration(ctx context.Context, items []itemData) *dy configurations := make(map[string]*dynamic.Configuration) for _, item := range items { - svcName := item.Name + "-" + item.ID + svcName := item.Node + "-" + item.Name + "-" + item.ID ctxSvc := log.With(ctx, log.Str("serviceName", svcName)) if !p.keepContainer(ctxSvc, item) { diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go index a794bd90e..14bb347e1 100644 --- a/pkg/provider/consulcatalog/config_test.go +++ b/pkg/provider/consulcatalog/config_test.go @@ -25,6 +25,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "id", + Node: "Node1", Name: "Test", Address: "127.0.0.1", Port: "80", @@ -66,6 +67,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "id", + Node: "Node1", Name: "Test", Address: "127.0.0.1", Port: "80", @@ -109,6 +111,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -145,6 +148,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -181,6 +185,7 @@ func TestDefaultRule(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -257,6 +262,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -297,6 +303,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -305,6 +312,7 @@ func Test_buildConfiguration(t *testing.T) { }, { ID: "Test2", + Node: "Node1", Name: "Test2", Labels: map[string]string{}, Address: "127.0.0.2", @@ -359,6 +367,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "1", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", @@ -367,6 +376,110 @@ func Test_buildConfiguration(t *testing.T) { }, { ID: "2", + Node: "Node1", + Name: "Test", + Labels: map[string]string{}, + Address: "127.0.0.2", + Port: "80", + Status: api.HealthPassing, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1:80", + }, + { + URL: "http://127.0.0.2:80", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + }, + }, + }, + { + desc: "two containers with same service name & id no label on same node", + items: []itemData{ + { + ID: "1", + Node: "Node1", + Name: "Test", + Labels: map[string]string{}, + Address: "127.0.0.1", + Port: "80", + Status: api.HealthPassing, + }, + { + ID: "1", + Node: "Node1", + Name: "Test", + Labels: map[string]string{}, + Address: "127.0.0.2", + Port: "80", + Status: api.HealthPassing, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.wtf`)", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.2:80", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + }, + }, + }, + { + desc: "two containers with same service name & id no label on different nodes", + items: []itemData{ + { + ID: "1", + Node: "Node1", + Name: "Test", + Labels: map[string]string{}, + Address: "127.0.0.1", + Port: "80", + Status: api.HealthPassing, + }, + { + ID: "1", + Node: "Node2", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.2", @@ -1320,6 +1433,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.2", @@ -1393,6 +1507,7 @@ func Test_buildConfiguration(t *testing.T) { items: []itemData{ { ID: "Test", + Node: "Node1", Name: "Test", Labels: map[string]string{}, Address: "127.0.0.1", diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go index d4b3029d5..a176ab0b4 100644 --- a/pkg/provider/consulcatalog/consul_catalog.go +++ b/pkg/provider/consulcatalog/consul_catalog.go @@ -24,6 +24,7 @@ var _ provider.Provider = (*Provider)(nil) type itemData struct { ID string + Node string Name string Address string Port string @@ -164,6 +165,7 @@ func (p *Provider) getConsulServicesData(ctx context.Context) ([]itemData, error item := itemData{ ID: consulService.ServiceID, + Node: consulService.Node, Name: consulService.ServiceName, Address: address, Port: strconv.Itoa(consulService.ServicePort),