1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Fix duplicate mount point for --volumes-from in docker run

This fix tries to fix the issue raised in 21845. The issue with 21845
is that if multiple `--volumes-from` with the same destination has been
specified, then one volume will be overridden by the other. This will mess
up with volumes reference and prevent the overridden volume from
being removed at the end.

Issue 21845 was observed with `docker-compose` though it is possible to
emulate the same behavior with `docker` alone:
```
$ cat Dockerfile
FROM busybox
VOLUME ["/tmp/data"]
$ docker build -t vimage .
$ docker run --name=data1 vimage true
$ docker run --name=data2 vimage true
$ docker run --name=app --volumes-from=data1 --volumes-from=data2 -d busybox top
$ docker rm -f -v $(docker ps -aq)
$ docker volume ls
$ docker volume rm ...
```
NOTE: Second case:
```
$ cat Dockerfile
FROM busybox
VOLUME ["/tmp/data"]
$ docker build -t vimage .
$ docker run --name=data1 vimage true
$ docker run --name=data2 vimage true
$ docker run --name=app --volumes-from=data1 --volumes-from=data2 -v /tmp/data:/tmp/data -d busybox top
$ docker rm -f -v $(docker ps -aq)
$ docker volume ls
$ docker volume rm ...
```
NOTE: Third case: Combination of --volumes-from and `HostConfig.Mounts` (API only)

This fix tries to address the issue by return an error if duplicate
mount points was used with `--volumes-from`.

An integration test has been added.

This fix fixes 21845.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
(cherry picked from commit 9526e5c6ae)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
This commit is contained in:
Yong Tang 2017-01-27 14:12:45 -08:00 committed by Victor Vieux
parent 4730409857
commit 3599289e2c
2 changed files with 172 additions and 1 deletions

View file

@ -85,6 +85,15 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
} }
}() }()
dereferenceIfExists := func(destination string) {
if v, ok := mountPoints[destination]; ok {
logrus.Debugf("Duplicate mount point '%s'", destination)
if v.Volume != nil {
daemon.volumes.Dereference(v.Volume, container.ID)
}
}
}
// 1. Read already configured mount points. // 1. Read already configured mount points.
for destination, point := range container.MountPoints { for destination, point := range container.MountPoints {
mountPoints[destination] = point mountPoints[destination] = point
@ -121,7 +130,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
} }
cp.Volume = v cp.Volume = v
} }
dereferenceIfExists(cp.Destination)
mountPoints[cp.Destination] = cp mountPoints[cp.Destination] = cp
} }
} }
@ -155,6 +164,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
} }
binds[bind.Destination] = true binds[bind.Destination] = true
dereferenceIfExists(bind.Destination)
mountPoints[bind.Destination] = bind mountPoints[bind.Destination] = bind
} }
@ -199,6 +209,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
} }
binds[mp.Destination] = true binds[mp.Destination] = true
dereferenceIfExists(mp.Destination)
mountPoints[mp.Destination] = mp mountPoints[mp.Destination] = mp
} }

View file

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -425,3 +426,162 @@ func (s *DockerSuite) TestVolumeCliInspectWithVolumeOpts(c *check.C) {
c.Assert(strings.TrimSpace(out), checker.Contains, fmt.Sprintf("%s:%s", k2, v2)) c.Assert(strings.TrimSpace(out), checker.Contains, fmt.Sprintf("%s:%s", k2, v2))
c.Assert(strings.TrimSpace(out), checker.Contains, fmt.Sprintf("%s:%s", k3, v3)) c.Assert(strings.TrimSpace(out), checker.Contains, fmt.Sprintf("%s:%s", k3, v3))
} }
// Test case (1) for 21845: duplicate targets for --volumes-from
func (s *DockerSuite) TestDuplicateMountpointsForVolumesFrom(c *check.C) {
testRequires(c, DaemonIsLinux)
image := "vimage"
_, err := buildImage(
image,
`
FROM busybox
VOLUME ["/tmp/data"]
`,
true)
c.Assert(err, check.IsNil)
dockerCmd(c, "run", "--name=data1", image, "true")
dockerCmd(c, "run", "--name=data2", image, "true")
out, _ := dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data1")
data1 := strings.TrimSpace(out)
c.Assert(data1, checker.Not(checker.Equals), "")
out, _ = dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data2")
data2 := strings.TrimSpace(out)
c.Assert(data2, checker.Not(checker.Equals), "")
// Both volume should exist
out, _ = dockerCmd(c, "volume", "ls", "-q")
c.Assert(strings.TrimSpace(out), checker.Contains, data1)
c.Assert(strings.TrimSpace(out), checker.Contains, data2)
out, _, err = dockerCmdWithError("run", "--name=app", "--volumes-from=data1", "--volumes-from=data2", "-d", "busybox", "top")
c.Assert(err, checker.IsNil, check.Commentf("Out: %s", out))
// Only the second volume will be referenced, this is backward compatible
out, _ = dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "app")
c.Assert(strings.TrimSpace(out), checker.Equals, data2)
dockerCmd(c, "rm", "-f", "-v", "app")
dockerCmd(c, "rm", "-f", "-v", "data1")
dockerCmd(c, "rm", "-f", "-v", "data2")
// Both volume should not exist
out, _ = dockerCmd(c, "volume", "ls", "-q")
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data1)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data2)
}
// Test case (2) for 21845: duplicate targets for --volumes-from and -v (bind)
func (s *DockerSuite) TestDuplicateMountpointsForVolumesFromAndBind(c *check.C) {
testRequires(c, DaemonIsLinux)
image := "vimage"
_, err := buildImage(image,
`
FROM busybox
VOLUME ["/tmp/data"]
`,
true)
c.Assert(err, check.IsNil)
dockerCmd(c, "run", "--name=data1", image, "true")
dockerCmd(c, "run", "--name=data2", image, "true")
out, _ := dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data1")
data1 := strings.TrimSpace(out)
c.Assert(data1, checker.Not(checker.Equals), "")
out, _ = dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data2")
data2 := strings.TrimSpace(out)
c.Assert(data2, checker.Not(checker.Equals), "")
// Both volume should exist
out, _ = dockerCmd(c, "volume", "ls", "-q")
c.Assert(strings.TrimSpace(out), checker.Contains, data1)
c.Assert(strings.TrimSpace(out), checker.Contains, data2)
out, _, err = dockerCmdWithError("run", "--name=app", "--volumes-from=data1", "--volumes-from=data2", "-v", "/tmp/data:/tmp/data", "-d", "busybox", "top")
c.Assert(err, checker.IsNil, check.Commentf("Out: %s", out))
// No volume will be referenced (mount is /tmp/data), this is backward compatible
out, _ = dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "app")
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data1)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data2)
dockerCmd(c, "rm", "-f", "-v", "app")
dockerCmd(c, "rm", "-f", "-v", "data1")
dockerCmd(c, "rm", "-f", "-v", "data2")
// Both volume should not exist
out, _ = dockerCmd(c, "volume", "ls", "-q")
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data1)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data2)
}
// Test case (3) for 21845: duplicate targets for --volumes-from and `Mounts` (API only)
func (s *DockerSuite) TestDuplicateMountpointsForVolumesFromAndMounts(c *check.C) {
testRequires(c, DaemonIsLinux)
image := "vimage"
_, err := buildImage(image,
`
FROM busybox
VOLUME ["/tmp/data"]
`,
true)
c.Assert(err, check.IsNil)
dockerCmd(c, "run", "--name=data1", image, "true")
dockerCmd(c, "run", "--name=data2", image, "true")
out, _ := dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data1")
data1 := strings.TrimSpace(out)
c.Assert(data1, checker.Not(checker.Equals), "")
out, _ = dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data2")
data2 := strings.TrimSpace(out)
c.Assert(data2, checker.Not(checker.Equals), "")
// Both volume should exist
out, _ = dockerCmd(c, "volume", "ls", "-q")
c.Assert(strings.TrimSpace(out), checker.Contains, data1)
c.Assert(strings.TrimSpace(out), checker.Contains, data2)
// Mounts is available in API
status, body, err := sockRequest("POST", "/containers/create?name=app", map[string]interface{}{
"Image": "busybox",
"Cmd": []string{"top"},
"HostConfig": map[string]interface{}{
"VolumesFrom": []string{
"data1",
"data2",
},
"Mounts": []map[string]interface{}{
{
"Type": "bind",
"Source": "/tmp/data",
"Target": "/tmp/data",
},
}},
})
c.Assert(err, checker.IsNil, check.Commentf(string(body)))
c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(body)))
// No volume will be referenced (mount is /tmp/data), this is backward compatible
out, _ = dockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "app")
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data1)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data2)
dockerCmd(c, "rm", "-f", "-v", "app")
dockerCmd(c, "rm", "-f", "-v", "data1")
dockerCmd(c, "rm", "-f", "-v", "data2")
// Both volume should not exist
out, _ = dockerCmd(c, "volume", "ls", "-q")
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data1)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), data2)
}