From faf2b6f7aaca7f9ef400e227921b8125590fc9e5 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 9 Apr 2016 06:57:04 +0000 Subject: [PATCH] Docker stats is not working when a container is using another container's network. This fix tries to fix the issue in #21848 where `docker stats` will not correctly display the container stats in case the container reuse another container's network stack. The issue is that when `stats` is performed, the daemon will check for container network setting's `SandboxID`. Unfortunately, for containers that reuse another container's network stack (`NetworkMode.IsConnected()`), SandboxID is not assigned. Therefore, the daemon thinks the id is invalid and remote API will never return. This fix tries to resolve the SandboxID by iterating through connected containers and identify the appropriate SandboxID. A test case for `stats` remote API has been added to check if `stats` will return within the timeout. This fix fixes #21848. Signed-off-by: Yong Tang --- daemon/daemon.go | 21 ++++++++++++- integration-cli/docker_api_stats_test.go | 39 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index a8c5fb9ace..7daf46b193 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1421,8 +1421,27 @@ func (daemon *Daemon) GetContainerStats(container *container.Container) (*types. return stats, nil } +// Resolve Network SandboxID in case the container reuse another container's network stack +func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error) { + curr := c + for curr.HostConfig.NetworkMode.IsContainer() { + containerID := curr.HostConfig.NetworkMode.ConnectedContainer() + connected, err := daemon.GetContainer(containerID) + if err != nil { + return "", fmt.Errorf("Could not get container for %s", containerID) + } + curr = connected + } + return curr.NetworkSettings.SandboxID, nil +} + func (daemon *Daemon) getNetworkStats(c *container.Container) (map[string]types.NetworkStats, error) { - sb, err := daemon.netController.SandboxByID(c.NetworkSettings.SandboxID) + sandboxID, err := daemon.getNetworkSandboxID(c) + if err != nil { + return nil, err + } + + sb, err := daemon.netController.SandboxByID(sandboxID) if err != nil { return nil, err } diff --git a/integration-cli/docker_api_stats_test.go b/integration-cli/docker_api_stats_test.go index 7c3f8d3916..7639d95048 100644 --- a/integration-cli/docker_api_stats_test.go +++ b/integration-cli/docker_api_stats_test.go @@ -255,3 +255,42 @@ func (s *DockerSuite) TestApiStatsContainerGetMemoryLimit(c *check.C) { body.Close() c.Assert(fmt.Sprintf("%d", v.MemoryStats.Limit), checker.Equals, fmt.Sprintf("%d", info.MemTotal)) } + +func (s *DockerSuite) TestApiStatsNoStreamConnectedContainers(c *check.C) { + testRequires(c, DaemonIsLinux) + + out1, _ := runSleepingContainer(c) + id1 := strings.TrimSpace(out1) + c.Assert(waitRun(id1), checker.IsNil) + + out2, _ := runSleepingContainer(c, "--net", "container:"+id1) + id2 := strings.TrimSpace(out2) + c.Assert(waitRun(id2), checker.IsNil) + + ch := make(chan error) + go func() { + resp, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id2), nil, "") + defer body.Close() + if err != nil { + ch <- err + } + if resp.StatusCode != http.StatusOK { + ch <- fmt.Errorf("Invalid StatusCode %v", resp.StatusCode) + } + if resp.Header.Get("Content-Type") != "application/json" { + ch <- fmt.Errorf("Invalid 'Content-Type' %v", resp.Header.Get("Content-Type")) + } + var v *types.Stats + if err := json.NewDecoder(body).Decode(&v); err != nil { + ch <- err + } + ch <- nil + }() + + select { + case err := <-ch: + c.Assert(err, checker.IsNil, check.Commentf("Error in stats remote API: %v", err)) + case <-time.After(15 * time.Second): + c.Fatalf("Stats did not return after timeout") + } +}