Skip to content

Commit

Permalink
Docker stats is not working when a container is using another contain…
Browse files Browse the repository at this point in the history
…er's network.

This fix tries to fix the issue in moby#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 moby#21848.

Signed-off-by: Yong Tang <[email protected]>
  • Loading branch information
yongtang committed Apr 9, 2016
1 parent 4ac59a1 commit faf2b6f
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 1 deletion.
21 changes: 20 additions & 1 deletion daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
39 changes: 39 additions & 0 deletions integration-cli/docker_api_stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

0 comments on commit faf2b6f

Please sign in to comment.