diff --git a/provider/docker/builder_test.go b/provider/docker/builder_test.go index 7e8a4ba5d..b8679d7d3 100644 --- a/provider/docker/builder_test.go +++ b/provider/docker/builder_test.go @@ -95,6 +95,25 @@ func taskSlot(slot int) func(*swarm.Task) { } } +func taskNetworkAttachment(id string, name string, driver string, addresses []string) func(*swarm.Task) { + return func(task *swarm.Task) { + task.NetworksAttachments = append(task.NetworksAttachments, swarm.NetworkAttachment{ + Network: swarm.Network{ + ID: id, + Spec: swarm.NetworkSpec{ + Annotations: swarm.Annotations{ + Name: name, + }, + DriverConfiguration: &swarm.Driver{ + Name: driver, + }, + }, + }, + Addresses: addresses, + }) + } +} + func taskStatus(ops ...func(*swarm.TaskStatus)) func(*swarm.Task) { return func(task *swarm.Task) { status := &swarm.TaskStatus{} @@ -113,6 +132,14 @@ func taskState(state swarm.TaskState) func(*swarm.TaskStatus) { } } +func taskContainerStatus(id string) func(*swarm.TaskStatus) { + return func(status *swarm.TaskStatus) { + status.ContainerStatus = swarm.ContainerStatus{ + ContainerID: id, + } + } +} + func swarmService(ops ...func(*swarm.Service)) swarm.Service { service := &swarm.Service{ ID: "serviceID", diff --git a/provider/docker/config_container_swarm_test.go b/provider/docker/config_container_swarm_test.go index 81594a496..6e4be3f1f 100644 --- a/provider/docker/config_container_swarm_test.go +++ b/provider/docker/config_container_swarm_test.go @@ -1,7 +1,6 @@ package docker import ( - "reflect" "strconv" "testing" "time" @@ -473,70 +472,6 @@ func TestSwarmTraefikFilter(t *testing.T) { } } -func TestSwarmTaskParsing(t *testing.T) { - testCases := []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, test := range testCases { - test := test - t.Run(strconv.Itoa(caseID), func(t *testing.T) { - t.Parallel() - dData := parseService(test.service, test.networks) - - for _, task := range test.tasks { - taskDockerData := parseTasks(task, dData, map[string]*docker.NetworkResource{}, test.isGlobalSVC) - if !reflect.DeepEqual(taskDockerData.Name, test.expectedNames[task.ID]) { - t.Errorf("expect %v, got %v", test.expectedNames[task.ID], taskDockerData.Name) - } - } - }) - } -} - func TestSwarmGetFuncStringLabel(t *testing.T) { testCases := []struct { service swarm.Service diff --git a/provider/docker/docker.go b/provider/docker/docker.go index cc8954264..588a77362 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -260,21 +260,29 @@ func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) var containersInspected []dockerData // get inspect containers for _, container := range containerList { - containerInspected, err := dockerClient.ContainerInspect(ctx, container.ID) - if err != nil { - log.Warnf("Failed to inspect container %s, error: %s", container.ID, err) - } else { - // This condition is here to avoid to have empty IP https://github.com/containous/traefik/issues/2459 - // We register only container which are running - if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running { - dData := parseContainer(containerInspected) - containersInspected = append(containersInspected, dData) - } + dData := inspectContainers(ctx, dockerClient, container.ID) + if len(dData.Name) > 0 { + containersInspected = append(containersInspected, dData) } } return containersInspected, nil } +func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData { + dData := dockerData{} + containerInspected, err := dockerClient.ContainerInspect(ctx, containerID) + if err != nil { + log.Warnf("Failed to inspect container %s, error: %s", containerID, err) + } else { + // This condition is here to avoid to have empty IP https://github.com/containous/traefik/issues/2459 + // We register only container which are running + if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running { + dData = parseContainer(containerInspected) + } + } + return dData +} + func parseContainer(container dockertypes.ContainerJSON) dockerData { dData := dockerData{ NetworkSettings: networkSettings{}, @@ -388,13 +396,17 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes for _, virtualIP := range service.Endpoint.VirtualIPs { networkService := networkMap[virtualIP.NetworkID] if networkService != nil { - ip, _, _ := net.ParseCIDR(virtualIP.Addr) - network := &networkData{ - Name: networkService.Name, - ID: virtualIP.NetworkID, - Addr: ip.String(), + if len(virtualIP.Addr) > 0 { + ip, _, _ := net.ParseCIDR(virtualIP.Addr) + network := &networkData{ + Name: networkService.Name, + ID: virtualIP.NetworkID, + Addr: ip.String(), + } + dData.NetworkSettings.Networks[network.Name] = network + } else { + log.Debugf("No virtual IPs found in network %s", virtualIP.NetworkID) } - dData.NetworkSettings.Networks[network.Name] = network } else { log.Debugf("Network not found, id: %s", virtualIP.NetworkID) } @@ -421,12 +433,15 @@ func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID str continue } dData := parseTasks(task, serviceDockerData, networkMap, isGlobalSvc) - dockerDataList = append(dockerDataList, dData) + if len(dData.NetworkSettings.Networks) > 0 { + dockerDataList = append(dockerDataList, dData) + } } return dockerDataList, err } -func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool) dockerData { +func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, + networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool) dockerData { dData := dockerData{ ServiceName: serviceDockerData.Name, Name: serviceDockerData.Name + "." + strconv.Itoa(task.Slot), @@ -442,15 +457,19 @@ func parseTasks(task swarmtypes.Task, serviceDockerData dockerData, networkMap m dData.NetworkSettings.Networks = make(map[string]*networkData) for _, virtualIP := range task.NetworksAttachments { if networkService, present := networkMap[virtualIP.Network.ID]; present { - // Not sure about this next loop - when would a task have multiple IP's for the same network? - for _, addr := range virtualIP.Addresses { - ip, _, _ := net.ParseCIDR(addr) - network := &networkData{ - ID: virtualIP.Network.ID, - Name: networkService.Name, - Addr: ip.String(), + if len(virtualIP.Addresses) > 0 { + // Not sure about this next loop - when would a task have multiple IP's for the same network? + for _, addr := range virtualIP.Addresses { + ip, _, _ := net.ParseCIDR(addr) + network := &networkData{ + ID: virtualIP.Network.ID, + Name: networkService.Name, + Addr: ip.String(), + } + dData.NetworkSettings.Networks[network.Name] = network } - dData.NetworkSettings.Networks[network.Name] = network + } else { + log.Debugf("No IP addresses found for network %s", virtualIP.Network.ID) } } } diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index 242788133..fe5c2f68a 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -16,14 +16,19 @@ import ( type fakeTasksClient struct { dockerclient.APIClient - tasks []swarm.Task - err error + tasks []swarm.Task + container dockertypes.ContainerJSON + err error } func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) { return c.tasks, c.err } +func (c *fakeTasksClient) ContainerInspect(ctx context.Context, container string) (dockertypes.ContainerJSON, error) { + return c.container, c.err +} + func TestListTasks(t *testing.T) { testCases := []struct { service swarm.Service @@ -35,11 +40,30 @@ func TestListTasks(t *testing.T) { { 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))), + swarmTask("id1", + taskSlot(1), + taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.1"}), + taskStatus(taskState(swarm.TaskStateRunning)), + ), + swarmTask("id2", + taskSlot(2), + taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.2"}), + taskStatus(taskState(swarm.TaskStatePending)), + ), + swarmTask("id3", + taskSlot(3), + taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.3"}), + ), + swarmTask("id4", + taskSlot(4), + taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.4"}), + taskStatus(taskState(swarm.TaskStateRunning)), + ), + swarmTask("id5", + taskSlot(5), + taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.5"}), + taskStatus(taskState(swarm.TaskStateFailed)), + ), }, isGlobalSVC: false, expectedTasks: []string{ @@ -60,7 +84,7 @@ func TestListTasks(t *testing.T) { t.Parallel() dockerData := parseService(test.service, test.networks) dockerClient := &fakeTasksClient{tasks: test.tasks} - taskDockerData, _ := listTasks(context.Background(), dockerClient, test.service.ID, dockerData, map[string]*docker.NetworkResource{}, test.isGlobalSVC) + taskDockerData, _ := listTasks(context.Background(), dockerClient, test.service.ID, dockerData, test.networks, test.isGlobalSVC) if len(test.expectedTasks) != len(taskDockerData) { t.Errorf("expected tasks %v, got %v", spew.Sdump(test.expectedTasks), spew.Sdump(taskDockerData)) @@ -203,8 +227,14 @@ func TestListServices(t *testing.T) { withEndpointSpec(modeDNSSR)), }, tasks: []swarm.Task{ - swarmTask("id1", taskStatus(taskState(swarm.TaskStateRunning))), - swarmTask("id2", taskStatus(taskState(swarm.TaskStateRunning))), + swarmTask("id1", + taskNetworkAttachment("yk6l57rfwizjzxxzftn4amaot", "network_name", "overlay", []string{"127.0.0.1"}), + taskStatus(taskState(swarm.TaskStateRunning)), + ), + swarmTask("id2", + taskNetworkAttachment("yk6l57rfwizjzxxzftn4amaot", "network_name", "overlay", []string{"127.0.0.1"}), + taskStatus(taskState(swarm.TaskStateRunning)), + ), }, dockerVersion: "1.30", networks: []dockertypes.NetworkResource{ @@ -252,3 +282,116 @@ func TestListServices(t *testing.T) { }) } } + +func TestSwarmTaskParsing(t *testing.T) { + testCases := []struct { + service swarm.Service + tasks []swarm.Task + isGlobalSVC bool + expected map[string]dockerData + 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, + expected: map[string]dockerData{ + "id1": { + Name: "container.1", + }, + "id2": { + Name: "container.2", + }, + "id3": { + Name: "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, + expected: map[string]dockerData{ + "id1": { + Name: "container.id1", + }, + "id2": { + Name: "container.id2", + }, + "id3": { + Name: "container.id3", + }, + }, + networks: map[string]*docker.NetworkResource{ + "1": { + Name: "foo", + }, + }, + }, + { + service: swarmService( + serviceName("container"), + withEndpointSpec(modeVIP), + withEndpoint( + virtualIP("1", ""), + ), + ), + tasks: []swarm.Task{ + swarmTask( + "id1", + taskNetworkAttachment("1", "vlan", "macvlan", []string{"127.0.0.1"}), + taskStatus( + taskState(swarm.TaskStateRunning), + taskContainerStatus("c1"), + ), + ), + }, + isGlobalSVC: true, + expected: map[string]dockerData{ + "id1": { + Name: "container.id1", + NetworkSettings: networkSettings{ + Networks: map[string]*networkData{ + "vlan": { + Name: "vlan", + Addr: "10.11.12.13", + }, + }, + }, + }, + }, + networks: map[string]*docker.NetworkResource{ + "1": { + Name: "vlan", + }, + }, + }, + } + + for caseID, test := range testCases { + test := test + t.Run(strconv.Itoa(caseID), func(t *testing.T) { + t.Parallel() + dData := parseService(test.service, test.networks) + + for _, task := range test.tasks { + taskDockerData := parseTasks(task, dData, test.networks, test.isGlobalSVC) + expected := test.expected[task.ID] + assert.Equal(t, expected.Name, taskDockerData.Name) + } + }) + } +}