Update some docker provider test

- Split the file into smaller ones (docker, swarm and service tests)
- Use some builder to reduce a little bit the noise for creating containers

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2017-04-15 16:46:44 +02:00
parent 3f293ee25b
commit b04ba36682
7 changed files with 1797 additions and 2565 deletions

View file

@ -0,0 +1,179 @@
package docker
import (
docker "github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/swarm"
"github.com/docker/go-connections/nat"
)
func containerJSON(ops ...func(*docker.ContainerJSON)) docker.ContainerJSON {
c := &docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "fake",
HostConfig: &container.HostConfig{},
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{},
},
}
for _, op := range ops {
op(c)
}
return *c
}
func name(name string) func(*docker.ContainerJSON) {
return func(c *docker.ContainerJSON) {
c.ContainerJSONBase.Name = name
}
}
func networkMode(mode string) func(*docker.ContainerJSON) {
return func(c *docker.ContainerJSON) {
c.ContainerJSONBase.HostConfig.NetworkMode = container.NetworkMode(mode)
}
}
func labels(labels map[string]string) func(*docker.ContainerJSON) {
return func(c *docker.ContainerJSON) {
c.Config.Labels = labels
}
}
func ports(portMap nat.PortMap) func(*docker.ContainerJSON) {
return func(c *docker.ContainerJSON) {
c.NetworkSettings.NetworkSettingsBase.Ports = portMap
}
}
func withNetwork(name string, ops ...func(*network.EndpointSettings)) func(*docker.ContainerJSON) {
return func(c *docker.ContainerJSON) {
if c.NetworkSettings.Networks == nil {
c.NetworkSettings.Networks = map[string]*network.EndpointSettings{}
}
c.NetworkSettings.Networks[name] = &network.EndpointSettings{}
for _, op := range ops {
op(c.NetworkSettings.Networks[name])
}
}
}
func ipv4(ip string) func(*network.EndpointSettings) {
return func(s *network.EndpointSettings) {
s.IPAddress = ip
}
}
func swarmTask(id string, ops ...func(*swarm.Task)) swarm.Task {
task := &swarm.Task{
ID: id,
}
for _, op := range ops {
op(task)
}
return *task
}
func taskSlot(slot int) func(*swarm.Task) {
return func(task *swarm.Task) {
task.Slot = slot
}
}
func taskStatus(ops ...func(*swarm.TaskStatus)) func(*swarm.Task) {
return func(task *swarm.Task) {
status := &swarm.TaskStatus{}
for _, op := range ops {
op(status)
}
task.Status = *status
}
}
func taskState(state swarm.TaskState) func(*swarm.TaskStatus) {
return func(status *swarm.TaskStatus) {
status.State = state
}
}
func swarmService(ops ...func(*swarm.Service)) swarm.Service {
service := &swarm.Service{
ID: "serviceID",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: "defaultServiceName",
},
},
}
for _, op := range ops {
op(service)
}
return *service
}
func serviceName(name string) func(service *swarm.Service) {
return func(service *swarm.Service) {
service.Spec.Annotations.Name = name
}
}
func serviceLabels(labels map[string]string) func(service *swarm.Service) {
return func(service *swarm.Service) {
service.Spec.Annotations.Labels = labels
}
}
func withEndpoint(ops ...func(*swarm.Endpoint)) func(*swarm.Service) {
return func(service *swarm.Service) {
endpoint := &swarm.Endpoint{}
for _, op := range ops {
op(endpoint)
}
service.Endpoint = *endpoint
}
}
func virtualIP(networkID, addr string) func(*swarm.Endpoint) {
return func(endpoint *swarm.Endpoint) {
if endpoint.VirtualIPs == nil {
endpoint.VirtualIPs = []swarm.EndpointVirtualIP{}
}
endpoint.VirtualIPs = append(endpoint.VirtualIPs, swarm.EndpointVirtualIP{
NetworkID: networkID,
Addr: addr,
})
}
}
func withEndpointSpec(ops ...func(*swarm.EndpointSpec)) func(*swarm.Service) {
return func(service *swarm.Service) {
endpointSpec := &swarm.EndpointSpec{}
for _, op := range ops {
op(endpointSpec)
}
service.Spec.EndpointSpec = endpointSpec
}
}
func modeDNSSR(spec *swarm.EndpointSpec) {
spec.Mode = swarm.ResolutionModeDNSRR
}
func modeVIP(spec *swarm.EndpointSpec) {
spec.Mode = swarm.ResolutionModeVIP
}

View file

@ -41,7 +41,7 @@ const (
var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the Provider p.
// Provider holds configurations of the Provider.
type Provider struct {
provider.BaseProvider `mapstructure:",squash"`
Endpoint string `description:"Provider server endpoint. Can be a tcp or a unix socket endpoint"`

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,469 @@
package docker
import (
"reflect"
"strconv"
"testing"
"github.com/containous/traefik/types"
docker "github.com/docker/engine-api/types"
"github.com/docker/go-connections/nat"
)
func TestDockerGetServiceProtocol(t *testing.T) {
provider := &Provider{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(),
expected: "http",
},
{
container: containerJSON(labels(map[string]string{
"traefik.protocol": "https",
})),
expected: "https",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.protocol": "https",
})),
expected: "https",
},
}
for containerID, e := range containers {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(e.container)
actual := provider.getServiceProtocol(dockerData, "myservice")
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestDockerGetServiceWeight(t *testing.T) {
provider := &Provider{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(),
expected: "0",
},
{
container: containerJSON(labels(map[string]string{
"traefik.weight": "200",
})),
expected: "200",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.weight": "31337",
})),
expected: "31337",
},
}
for containerID, e := range containers {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(e.container)
actual := provider.getServiceWeight(dockerData, "myservice")
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestDockerGetServicePort(t *testing.T) {
provider := &Provider{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(),
expected: "",
},
{
container: containerJSON(labels(map[string]string{
"traefik.port": "2500",
})),
expected: "2500",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.port": "1234",
})),
expected: "1234",
},
}
for containerID, e := range containers {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(e.container)
actual := provider.getServicePort(dockerData, "myservice")
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestDockerGetServiceFrontendRule(t *testing.T) {
provider := &Provider{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "Host:foo.",
},
{
container: containerJSON(labels(map[string]string{
"traefik.frontend.rule": "Path:/helloworld",
})),
expected: "Path:/helloworld",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.rule": "Path:/mycustomservicepath",
})),
expected: "Path:/mycustomservicepath",
},
}
for containerID, e := range containers {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(e.container)
actual := provider.getServiceFrontendRule(dockerData, "myservice")
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestDockerGetServiceBackend(t *testing.T) {
provider := &Provider{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(name("foo")),
expected: "foo-myservice",
},
{
container: containerJSON(labels(map[string]string{
"traefik.backend": "another-backend",
})),
expected: "another-backend-myservice",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.backend": "custom-backend",
})),
expected: "custom-backend",
},
}
for containerID, e := range containers {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(e.container)
actual := provider.getServiceBackend(dockerData, "myservice")
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestDockerGetServicePriority(t *testing.T) {
provider := &Provider{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(),
expected: "0",
},
{
container: containerJSON(labels(map[string]string{
"traefik.frontend.priority": "33",
})),
expected: "33",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.priority": "2503",
})),
expected: "2503",
},
}
for containerID, e := range containers {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(e.container)
actual := provider.getServicePriority(dockerData, "myservice")
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestDockerGetServicePassHostHeader(t *testing.T) {
provider := &Provider{}
containers := []struct {
container docker.ContainerJSON
expected string
}{
{
container: containerJSON(),
expected: "true",
},
{
container: containerJSON(labels(map[string]string{
"traefik.frontend.passHostHeader": "false",
})),
expected: "false",
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.passHostHeader": "false",
})),
expected: "false",
},
}
for containerID, e := range containers {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(e.container)
actual := provider.getServicePassHostHeader(dockerData, "myservice")
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestDockerGetServiceEntryPoints(t *testing.T) {
provider := &Provider{}
containers := []struct {
container docker.ContainerJSON
expected []string
}{
{
container: containerJSON(),
expected: []string{},
},
{
container: containerJSON(labels(map[string]string{
"traefik.frontend.entryPoints": "http,https",
})),
expected: []string{"http", "https"},
},
{
container: containerJSON(labels(map[string]string{
"traefik.myservice.frontend.entryPoints": "http,https",
})),
expected: []string{"http", "https"},
},
}
for containerID, e := range containers {
e := e
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(e.container)
actual := provider.getServiceEntryPoints(dockerData, "myservice")
if !reflect.DeepEqual(actual, e.expected) {
t.Fatalf("expected %q, got %q for container %q", e.expected, actual, dockerData.Name)
}
})
}
}
func TestDockerLoadDockerServiceConfig(t *testing.T) {
cases := []struct {
containers []docker.ContainerJSON
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
containers: []docker.ContainerJSON{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
containers: []docker.ContainerJSON{
containerJSON(
name("foo"),
labels(map[string]string{
"traefik.service.port": "2503",
"traefik.service.frontend.entryPoints": "http,https",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-foo-service": {
Backend: "backend-foo-service",
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
Routes: map[string]types.Route{
"service-service": {
Rule: "Host:foo.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foo-service": {
Servers: map[string]types.Server{
"service": {
URL: "http://127.0.0.1:2503",
Weight: 0,
},
},
CircuitBreaker: nil,
},
},
},
{
containers: []docker.ContainerJSON{
containerJSON(
name("test1"),
labels(map[string]string{
"traefik.service.port": "2503",
"traefik.service.protocol": "https",
"traefik.service.weight": "80",
"traefik.service.frontend.backend": "foobar",
"traefik.service.frontend.passHostHeader": "false",
"traefik.service.frontend.rule": "Path:/mypath",
"traefik.service.frontend.priority": "5000",
"traefik.service.frontend.entryPoints": "http,https,ws",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
containerJSON(
name("test2"),
labels(map[string]string{
"traefik.anotherservice.port": "8079",
"traefik.anotherservice.weight": "33",
"traefik.anotherservice.frontend.rule": "Path:/anotherpath",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-foobar": {
Backend: "backend-foobar",
PassHostHeader: false,
Priority: 5000,
EntryPoints: []string{"http", "https", "ws"},
Routes: map[string]types.Route{
"service-service": {
Rule: "Path:/mypath",
},
},
},
"frontend-test2-anotherservice": {
Backend: "backend-test2-anotherservice",
PassHostHeader: true,
EntryPoints: []string{},
Routes: map[string]types.Route{
"service-anotherservice": {
Rule: "Path:/anotherpath",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"service": {
URL: "https://127.0.0.1:2503",
Weight: 80,
},
},
CircuitBreaker: nil,
},
"backend-test2-anotherservice": {
Servers: map[string]types.Server{
"service": {
URL: "http://127.0.0.1:8079",
Weight: 33,
},
},
CircuitBreaker: nil,
},
},
},
}
provider := &Provider{
Domain: "docker.localhost",
ExposedByDefault: true,
}
for caseID, c := range cases {
c := c
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel()
var dockerDataList []dockerData
for _, container := range c.containers {
dockerData := parseContainer(container)
dockerDataList = append(dockerDataList, dockerData)
}
actualConfig := provider.loadDockerConfig(dockerDataList)
// Compare backends
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
}
})
}
}

View file

@ -0,0 +1,888 @@
package docker
import (
"reflect"
"strconv"
"strings"
"testing"
"github.com/containous/traefik/types"
"github.com/davecgh/go-spew/spew"
dockerclient "github.com/docker/engine-api/client"
docker "github.com/docker/engine-api/types"
dockertypes "github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/swarm"
"golang.org/x/net/context"
)
func TestSwarmGetFrontendName(t *testing.T) {
provider := &Provider{
Domain: "docker.localhost",
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("foo")),
expected: "Host-foo-docker-localhost",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.frontend.rule": "Headers:User-Agent,bat/0.1.0",
})),
expected: "Headers-User-Agent-bat-0-1-0",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.frontend.rule": "Host:foo.bar",
})),
expected: "Host-foo-bar",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.frontend.rule": "Path:/test",
})),
expected: "Path-test",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(
serviceName("test"),
serviceLabels(map[string]string{
"traefik.frontend.rule": "PathPrefix:/test2",
}),
),
expected: "PathPrefix-test2",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getFrontendName(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetFrontendRule(t *testing.T) {
provider := &Provider{
Domain: "docker.localhost",
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("foo")),
expected: "Host:foo.docker.localhost",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceName("bar")),
expected: "Host:bar.docker.localhost",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.frontend.rule": "Host:foo.bar",
})),
expected: "Host:foo.bar",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.frontend.rule": "Path:/test",
})),
expected: "Path:/test",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getFrontendRule(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetBackend(t *testing.T) {
provider := &Provider{
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("foo")),
expected: "foo",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceName("bar")),
expected: "bar",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.backend": "foobar",
})),
expected: "foobar",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getBackend(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetIPAddress(t *testing.T) {
provider := &Provider{
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(withEndpointSpec(modeDNSSR)),
expected: "",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "10.11.12.13/24")),
),
expected: "10.11.12.13",
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
{
service: swarmService(
serviceLabels(map[string]string{
"traefik.docker.network": "barnet",
}),
withEndpointSpec(modeVIP),
withEndpoint(
virtualIP("1", "10.11.12.13/24"),
virtualIP("2", "10.11.12.99/24"),
),
),
expected: "10.11.12.99",
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foonet",
},
"2": {
Name: "barnet",
},
},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getIPAddress(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetPort(t *testing.T) {
provider := &Provider{
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(
serviceLabels(map[string]string{
"traefik.port": "8080",
}),
withEndpointSpec(modeDNSSR),
),
expected: "8080",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getPort(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetWeight(t *testing.T) {
provider := &Provider{
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(),
expected: "0",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.weight": "10",
})),
expected: "10",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getWeight(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetDomain(t *testing.T) {
provider := &Provider{
Domain: "docker.localhost",
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("foo")),
expected: "docker.localhost",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.domain": "foo.bar",
})),
expected: "foo.bar",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getDomain(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetProtocol(t *testing.T) {
provider := &Provider{
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(),
expected: "http",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.protocol": "https",
})),
expected: "https",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getProtocol(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetPassHostHeader(t *testing.T) {
provider := &Provider{
SwarmMode: true,
}
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(),
expected: "true",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.frontend.passHostHeader": "false",
})),
expected: "false",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
actual := provider.getPassHostHeader(dockerData)
if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual)
}
})
}
}
func TestSwarmGetLabel(t *testing.T) {
services := []struct {
service swarm.Service
expected string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(),
expected: "Label not found:",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"foo": "bar",
})),
expected: "",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
label, err := getLabel(dockerData, "foo")
if e.expected != "" {
if err == nil || !strings.Contains(err.Error(), e.expected) {
t.Errorf("expected an error with %q, got %v", e.expected, err)
}
} else {
if label != "bar" {
t.Errorf("expected label 'bar', got %s", label)
}
}
})
}
}
func TestSwarmGetLabels(t *testing.T) {
services := []struct {
service swarm.Service
expectedLabels map[string]string
expectedError string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(),
expectedLabels: map[string]string{},
expectedError: "Label not found:",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"foo": "fooz",
})),
expectedLabels: map[string]string{
"foo": "fooz",
},
expectedError: "Label not found: bar",
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"foo": "fooz",
"bar": "barz",
})),
expectedLabels: map[string]string{
"foo": "fooz",
"bar": "barz",
},
expectedError: "",
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
labels, err := getLabels(dockerData, []string{"foo", "bar"})
if !reflect.DeepEqual(labels, e.expectedLabels) {
t.Errorf("expect %v, got %v", e.expectedLabels, labels)
}
if e.expectedError != "" {
if err == nil || !strings.Contains(err.Error(), e.expectedError) {
t.Errorf("expected an error with %q, got %v", e.expectedError, err)
}
}
})
}
}
func TestSwarmTraefikFilter(t *testing.T) {
provider := &Provider{
SwarmMode: true,
}
services := []struct {
service swarm.Service
exposedByDefault bool
expected bool
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(),
exposedByDefault: true,
expected: false,
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.enable": "false",
"traefik.port": "80",
})),
exposedByDefault: true,
expected: false,
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.frontend.rule": "Host:foo.bar",
"traefik.port": "80",
})),
exposedByDefault: true,
expected: true,
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.port": "80",
})),
exposedByDefault: true,
expected: true,
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.enable": "true",
"traefik.port": "80",
})),
exposedByDefault: true,
expected: true,
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.enable": "anything",
"traefik.port": "80",
})),
exposedByDefault: true,
expected: true,
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.frontend.rule": "Host:foo.bar",
"traefik.port": "80",
})),
exposedByDefault: true,
expected: true,
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.port": "80",
})),
exposedByDefault: false,
expected: false,
networks: map[string]*docker.NetworkResource{},
},
{
service: swarmService(serviceLabels(map[string]string{
"traefik.enable": "true",
"traefik.port": "80",
})),
exposedByDefault: false,
expected: true,
networks: map[string]*docker.NetworkResource{},
},
}
for serviceID, e := range services {
e := e
t.Run(strconv.Itoa(serviceID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
provider.ExposedByDefault = e.exposedByDefault
actual := provider.containerFilter(dockerData)
if actual != e.expected {
t.Errorf("expected %v for %+v, got %+v", e.expected, e, actual)
}
})
}
}
func TestSwarmLoadDockerConfig(t *testing.T) {
cases := []struct {
services []swarm.Service
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
networks map[string]*docker.NetworkResource
}{
{
services: []swarm.Service{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
networks: map[string]*docker.NetworkResource{},
},
{
services: []swarm.Service{
swarmService(
serviceName("test"),
serviceLabels(map[string]string{
"traefik.port": "80",
}),
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test-docker-localhost": {
Backend: "backend-test",
PassHostHeader: true,
EntryPoints: []string{},
Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost": {
Rule: "Host:test.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"server-test": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
{
services: []swarm.Service{
swarmService(
serviceName("test1"),
serviceLabels(map[string]string{
"traefik.port": "80",
"traefik.backend": "foobar",
"traefik.frontend.entryPoints": "http,https",
}),
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")),
),
swarmService(
serviceName("test2"),
serviceLabels(map[string]string{
"traefik.port": "80",
"traefik.backend": "foobar",
}),
withEndpointSpec(modeVIP),
withEndpoint(virtualIP("1", "127.0.0.1/24")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test1-docker-localhost": {
Backend: "backend-foobar",
PassHostHeader: true,
EntryPoints: []string{"http", "https"},
Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost": {
Rule: "Host:test1.docker.localhost",
},
},
},
"frontend-Host-test2-docker-localhost": {
Backend: "backend-foobar",
PassHostHeader: true,
EntryPoints: []string{},
Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost": {
Rule: "Host:test2.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-test1": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
"server-test2": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
}
provider := &Provider{
Domain: "docker.localhost",
ExposedByDefault: true,
SwarmMode: true,
}
for caseID, c := range cases {
c := c
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel()
var dockerDataList []dockerData
for _, service := range c.services {
dockerData := parseService(service, c.networks)
dockerDataList = append(dockerDataList, dockerData)
}
actualConfig := provider.loadDockerConfig(dockerDataList)
// Compare backends
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
t.Errorf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
t.Errorf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
}
})
}
}
func TestSwarmTaskParsing(t *testing.T) {
cases := []struct {
service swarm.Service
tasks []swarm.Task
isGlobalSVC bool
expectedNames map[string]string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("container")),
tasks: []swarm.Task{
swarmTask("id1", taskSlot(1)),
swarmTask("id2", taskSlot(2)),
swarmTask("id3", taskSlot(3)),
},
isGlobalSVC: false,
expectedNames: map[string]string{
"id1": "container.1",
"id2": "container.2",
"id3": "container.3",
},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
{
service: swarmService(serviceName("container")),
tasks: []swarm.Task{
swarmTask("id1"),
swarmTask("id2"),
swarmTask("id3"),
},
isGlobalSVC: true,
expectedNames: map[string]string{
"id1": "container.id1",
"id2": "container.id2",
"id3": "container.id3",
},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
}
for caseID, e := range cases {
e := e
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
for _, task := range e.tasks {
taskDockerData := parseTasks(task, dockerData, map[string]*docker.NetworkResource{}, e.isGlobalSVC)
if !reflect.DeepEqual(taskDockerData.Name, e.expectedNames[task.ID]) {
t.Errorf("expect %v, got %v", e.expectedNames[task.ID], taskDockerData.Name)
}
}
})
}
}
type fakeTasksClient struct {
dockerclient.APIClient
tasks []swarm.Task
err error
}
func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) {
return c.tasks, c.err
}
func TestListTasks(t *testing.T) {
cases := []struct {
service swarm.Service
tasks []swarm.Task
isGlobalSVC bool
expectedTasks []string
networks map[string]*docker.NetworkResource
}{
{
service: swarmService(serviceName("container")),
tasks: []swarm.Task{
swarmTask("id1", taskSlot(1), taskStatus(taskState(swarm.TaskStateRunning))),
swarmTask("id2", taskSlot(2), taskStatus(taskState(swarm.TaskStatePending))),
swarmTask("id3", taskSlot(3)),
swarmTask("id4", taskSlot(4), taskStatus(taskState(swarm.TaskStateRunning))),
swarmTask("id5", taskSlot(5), taskStatus(taskState(swarm.TaskStateFailed))),
},
isGlobalSVC: false,
expectedTasks: []string{
"container.1",
"container.4",
},
networks: map[string]*docker.NetworkResource{
"1": {
Name: "foo",
},
},
},
}
for caseID, e := range cases {
e := e
t.Run(strconv.Itoa(caseID), func(t *testing.T) {
t.Parallel()
dockerData := parseService(e.service, e.networks)
dockerClient := &fakeTasksClient{tasks: e.tasks}
taskDockerData, _ := listTasks(context.Background(), dockerClient, e.service.ID, dockerData, map[string]*docker.NetworkResource{}, e.isGlobalSVC)
if len(e.expectedTasks) != len(taskDockerData) {
t.Errorf("expected tasks %v, got %v", spew.Sdump(e.expectedTasks), spew.Sdump(taskDockerData))
}
for i, taskID := range e.expectedTasks {
if taskDockerData[i].Name != taskID {
t.Errorf("expect task id %v, got %v", taskID, taskDockerData[i].Name)
}
}
})
}
}

View file

@ -51,6 +51,7 @@ func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint)
return true, nil
}
// GetConfiguration return the provider configuration using templating
func (p *BaseProvider) GetConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
var (
buf []byte
@ -60,7 +61,7 @@ func (p *BaseProvider) GetConfiguration(defaultTemplateFile string, funcMap temp
var defaultFuncMap = template.FuncMap{
"replace": replace,
"tolower": strings.ToLower,
"Normalize": Normalize,
"normalize": Normalize,
"split": split,
"contains": contains,
}
@ -112,6 +113,7 @@ func split(sep, s string) []string {
return strings.Split(s, sep)
}
// Normalize transform a string that work with the rest of traefik
func Normalize(name string) string {
fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)

View file

@ -345,7 +345,7 @@ func TestDefaultFuncMap(t *testing.T) {
weight = 1
[frontends]
[frontends.{{Normalize "frontend/1"}}]
[frontends.{{normalize "frontend/1"}}]
{{ $backend := "backend1/test/value" | split "/" }}
{{ $backendid := index $backend 1 }}
{{ if "backend1" | contains "backend" }}