Exposed by default feature in Consul Catalog

This commit is contained in:
Michael 2017-08-25 17:32:03 +02:00 committed by Traefiker
parent e0af17a17a
commit f16219f90a
8 changed files with 241 additions and 24 deletions

View file

@ -83,6 +83,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
// default CatalogProvider
var defaultConsulCatalog consul.CatalogProvider
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
defaultConsulCatalog.ExposedByDefault = true
defaultConsulCatalog.Constraints = types.Constraints{}
defaultConsulCatalog.Prefix = "traefik"
defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"

View file

@ -1481,6 +1481,13 @@ endpoint = "127.0.0.1:8500"
#
domain = "consul.localhost"
# Expose Consul catalog services by default in traefik
#
# Optional
# Default: true
#
exposedByDefault = false
# Prefix for Consul catalog tags
#
# Optional

View file

@ -117,3 +117,88 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSingleService(c *check.C) {
cmd, _ := s.cmdTraefik(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--consulCatalog",
"--consulCatalog.exposedByDefault=false",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx := s.composeProject.Container(c, "nginx")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestExposedByDefaultFalseSimpleServiceMultipleNode(c *check.C) {
cmd, _ := s.cmdTraefik(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--consulCatalog",
"--consulCatalog.exposedByDefault=false",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx := s.composeProject.Container(c, "nginx")
nginx2 := s.composeProject.Container(c, "nginx2")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{"traefik.enable=true"})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx2.NetworkSettings.IPAddress)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}
func (s *ConsulCatalogSuite) TestExposedByDefaultTrueSimpleServiceMultipleNode(c *check.C) {
cmd, _ := s.cmdTraefik(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--consulCatalog",
"--consulCatalog.exposedByDefault=true",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx := s.composeProject.Container(c, "nginx")
nginx2 := s.composeProject.Container(c, "nginx2")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
err = s.registerService("test", nginx2.NetworkSettings.IPAddress, 80, []string{})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx2.NetworkSettings.IPAddress)
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"
err = try.Request(req, 5*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}

View file

@ -13,5 +13,5 @@ consul:
- "8302/udp"
nginx:
image: nginx:alpine
ports:
- "8881:80"
nginx2:
image: nginx:alpine

View file

@ -31,6 +31,7 @@ type CatalogProvider struct {
provider.BaseProvider `mapstructure:",squash"`
Endpoint string `description:"Consul server endpoint"`
Domain string `description:"Default domain used"`
ExposedByDefault bool `description:"Expose Consul services by default"`
Prefix string `description:"Prefix used for Consul catalog tags"`
FrontEndRule string `description:"Frontend rule used for Consul services"`
client *api.Client
@ -209,12 +210,7 @@ func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
}
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
constraintTags := p.getConstraintTags(node.Service.Tags)
ok, failingConstraint := p.MatchConstraints(constraintTags)
if !ok && failingConstraint != nil {
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
}
return ok
return p.nodeFilter(service, node)
}, data).([]*api.ServiceEntry)
//Merge tags of nodes matching constraints, in a single slice.
@ -234,6 +230,32 @@ func (p *CatalogProvider) healthyNodes(service string) (catalogUpdate, error) {
}, nil
}
func (p *CatalogProvider) nodeFilter(service string, node *api.ServiceEntry) bool {
// Filter disabled application.
if !p.isServiceEnabled(node) {
log.Debugf("Filtering disabled Consul service %s", service)
return false
}
// Filter by constraints.
constraintTags := p.getConstraintTags(node.Service.Tags)
ok, failingConstraint := p.MatchConstraints(constraintTags)
if !ok && failingConstraint != nil {
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
return false
}
return true
}
func (p *CatalogProvider) isServiceEnabled(node *api.ServiceEntry) bool {
enable, err := strconv.ParseBool(p.getAttribute("enable", node.Service.Tags, strconv.FormatBool(p.ExposedByDefault)))
if err != nil {
log.Debugf("Invalid value for enable, set to %b", p.ExposedByDefault)
return p.ExposedByDefault
}
return enable
}
func (p *CatalogProvider) getPrefixedName(name string) string {
if len(p.Prefix) > 0 {
return p.Prefix + "." + name
@ -364,14 +386,9 @@ func (p *CatalogProvider) buildConfig(catalog []catalogUpdate) *types.Configurat
allNodes := []*api.ServiceEntry{}
services := []*serviceUpdate{}
for _, info := range catalog {
for _, node := range info.Nodes {
isEnabled := p.getAttribute("enable", node.Service.Tags, "true")
if isEnabled != "false" && len(info.Nodes) > 0 {
services = append(services, info.Service)
allNodes = append(allNodes, info.Nodes...)
break
}
if len(info.Nodes) > 0 {
services = append(services, info.Service)
allNodes = append(allNodes, info.Nodes...)
}
}
// Ensure a stable ordering of nodes so that identical configurations may be detected

View file

@ -311,6 +311,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
ExposedByDefault: false,
FrontEndRule: "Host:{{.ServiceName}}.{{.Domain}}",
frontEndRuleTemplate: template.New("consul catalog frontend rule"),
}
@ -330,7 +331,6 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
{
Service: &serviceUpdate{
ServiceName: "test",
Attributes: []string{},
},
},
},
@ -750,3 +750,106 @@ func TestConsulCatalogGetChangedKeys(t *testing.T) {
}
}
}
func TestConsulCatalogFilterEnabled(t *testing.T) {
cases := []struct {
desc string
exposedByDefault bool
node *api.ServiceEntry
expected bool
}{
{
desc: "exposed",
exposedByDefault: true,
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{""},
},
},
expected: true,
},
{
desc: "exposed and tolerated by valid label value",
exposedByDefault: true,
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{"", "traefik.enable=true"},
},
},
expected: true,
},
{
desc: "exposed and tolerated by invalid label value",
exposedByDefault: true,
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{"", "traefik.enable=bad"},
},
},
expected: true,
},
{
desc: "exposed but overridden by label",
exposedByDefault: true,
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{"", "traefik.enable=false"},
},
},
expected: false,
},
{
desc: "non-exposed",
exposedByDefault: false,
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{""},
},
},
expected: false,
},
{
desc: "non-exposed but overridden by label",
exposedByDefault: false,
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{"", "traefik.enable=true"},
},
},
expected: true,
},
}
for _, c := range cases {
c := c
t.Run(c.desc, func(t *testing.T) {
t.Parallel()
provider := &CatalogProvider{
Domain: "localhost",
Prefix: "traefik",
ExposedByDefault: c.exposedByDefault,
}
if provider.nodeFilter("test", c.node) != c.expected {
t.Errorf("got unexpected filtering = %t", !c.expected)
}
})
}
}

View file

@ -1,12 +1,10 @@
[backends]
{{range $index, $node := .Nodes}}
{{if ne (getAttribute "enable" $node.Service.Tags "true") "false"}}
[backends."backend-{{getBackend $node}}".servers."{{getBackendName $node $index}}"]
url = "{{getAttribute "protocol" $node.Service.Tags "http"}}://{{getBackendAddress $node}}:{{$node.Service.Port}}"
{{$weight := getAttribute "backend.weight" $node.Service.Tags "0"}}
{{with $weight}}
weight = {{$weight}}
{{end}}
[backends."backend-{{getBackend $node}}".servers."{{getBackendName $node $index}}"]
url = "{{getAttribute "protocol" $node.Service.Tags "http"}}://{{getBackendAddress $node}}:{{$node.Service.Port}}"
{{$weight := getAttribute "backend.weight" $node.Service.Tags "0"}}
{{with $weight}}
weight = {{$weight}}
{{end}}
{{end}}

View file

@ -935,6 +935,12 @@
#
# domain = "consul.localhost"
# Expose Consul catalog services by default in traefik
#
# Optional
#
# exposedByDefault = true
# Prefix for Consul catalog tags
#
# Optional