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 <yong.tang.github@outlook.com>
Upstream-commit: faf2b6f7aaca7f9ef400e227921b8125590fc9e5
Component: engine
This commit is contained in:
Yong Tang
2016-04-09 06:57:04 +00:00
parent 329133f6f0
commit 7511f16a58
2 changed files with 59 additions and 1 deletions

View File

@ -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
}

View File

@ -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")
}
}