From f988c98ff318dcfecb9d2db9511fe78e70b43e44 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Mon, 14 Dec 2015 10:39:53 -0500 Subject: [PATCH] Add some unit and integration tests Add a unit test and couple of integration tests for volume propagation. Signed-off-by: Vivek Goyal --- integration-cli/docker_cli_run_test.go | 112 ++++++++++++++++++++++++ volume/volume_propagation_linux_test.go | 65 ++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 volume/volume_propagation_linux_test.go diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 22ce9c1ae9..3e5f0cf6b8 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -19,6 +19,7 @@ import ( "time" "github.com/docker/docker/pkg/integration/checker" + "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/nat" "github.com/docker/docker/runconfig" "github.com/docker/libnetwork/resolvconf" @@ -3812,3 +3813,114 @@ func (s *DockerSuite) TestRunWithOomScoreAdjInvalidRange(c *check.C) { c.Fatalf("Expected output to contain %q, got %q instead", expected, out) } } + +func (s *DockerSuite) TestRunVolumesMountedAsShared(c *check.C) { + // Volume propagation is linux only. Also it creates directories for + // bind mounting, so needs to be same host. + testRequires(c, DaemonIsLinux, SameHostDaemon, NotUserNamespace) + + // Prepare a source directory to bind mount + tmpDir, err := ioutil.TempDir("", "volume-source") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + if err := os.Mkdir(path.Join(tmpDir, "mnt1"), 0755); err != nil { + c.Fatal(err) + } + + // Convert this directory into a shared mount point so that we do + // not rely on propagation properties of parent mount. + cmd := exec.Command("mount", "--bind", tmpDir, tmpDir) + if _, err = runCommand(cmd); err != nil { + c.Fatal(err) + } + + cmd = exec.Command("mount", "--make-private", "--make-shared", tmpDir) + if _, err = runCommand(cmd); err != nil { + c.Fatal(err) + } + + dockerCmd(c, "run", "--privileged", "-v", fmt.Sprintf("%s:/volume-dest:shared", tmpDir), "busybox", "mount", "--bind", "/volume-dest/mnt1", "/volume-dest/mnt1") + + // Make sure a bind mount under a shared volume propagated to host. + if mounted, _ := mount.Mounted(path.Join(tmpDir, "mnt1")); !mounted { + c.Fatalf("Bind mount under shared volume did not propagate to host") + } + + mount.Unmount(path.Join(tmpDir, "mnt1")) +} + +func (s *DockerSuite) TestRunVolumesMountedAsSlave(c *check.C) { + // Volume propagation is linux only. Also it creates directories for + // bind mounting, so needs to be same host. + testRequires(c, DaemonIsLinux, SameHostDaemon, NotUserNamespace) + + // Prepare a source directory to bind mount + tmpDir, err := ioutil.TempDir("", "volume-source") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + if err := os.Mkdir(path.Join(tmpDir, "mnt1"), 0755); err != nil { + c.Fatal(err) + } + + // Prepare a source directory with file in it. We will bind mount this + // direcotry and see if file shows up. + tmpDir2, err := ioutil.TempDir("", "volume-source2") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(tmpDir2) + + if err := ioutil.WriteFile(path.Join(tmpDir2, "slave-testfile"), []byte("Test"), 0644); err != nil { + c.Fatal(err) + } + + // Convert this directory into a shared mount point so that we do + // not rely on propagation properties of parent mount. + cmd := exec.Command("mount", "--bind", tmpDir, tmpDir) + if _, err = runCommand(cmd); err != nil { + c.Fatal(err) + } + + cmd = exec.Command("mount", "--make-private", "--make-shared", tmpDir) + if _, err = runCommand(cmd); err != nil { + c.Fatal(err) + } + + dockerCmd(c, "run", "-i", "-d", "--name", "parent", "-v", fmt.Sprintf("%s:/volume-dest:slave", tmpDir), "busybox", "top") + + // Bind mount tmpDir2/ onto tmpDir/mnt1. If mount propagates inside + // container then contents of tmpDir2/slave-testfile should become + // visible at "/volume-dest/mnt1/slave-testfile" + cmd = exec.Command("mount", "--bind", tmpDir2, path.Join(tmpDir, "mnt1")) + if _, err = runCommand(cmd); err != nil { + c.Fatal(err) + } + + out, _ := dockerCmd(c, "exec", "parent", "cat", "/volume-dest/mnt1/slave-testfile") + + mount.Unmount(path.Join(tmpDir, "mnt1")) + + if out != "Test" { + c.Fatalf("Bind mount under slave volume did not propagate to container") + } +} + +func (s *DockerSuite) TestRunNamedVolumesMountedAsShared(c *check.C) { + testRequires(c, DaemonIsLinux, NotUserNamespace) + out, exitcode, _ := dockerCmdWithError("run", "-v", "foo:/test:shared", "busybox", "touch", "/test/somefile") + + if exitcode == 0 { + c.Fatalf("expected non-zero exit code; received %d", exitcode) + } + + if expected := "Invalid volume specification"; !strings.Contains(out, expected) { + c.Fatalf(`Expected %q in output; got: %s`, expected, out) + } + +} diff --git a/volume/volume_propagation_linux_test.go b/volume/volume_propagation_linux_test.go new file mode 100644 index 0000000000..9cf82528bd --- /dev/null +++ b/volume/volume_propagation_linux_test.go @@ -0,0 +1,65 @@ +// +build linux + +package volume + +import ( + "strings" + "testing" +) + +func TestParseMountSpecPropagation(t *testing.T) { + var ( + valid []string + invalid map[string]string + ) + + valid = []string{ + "/hostPath:/containerPath:shared", + "/hostPath:/containerPath:rshared", + "/hostPath:/containerPath:slave", + "/hostPath:/containerPath:rslave", + "/hostPath:/containerPath:private", + "/hostPath:/containerPath:rprivate", + "/hostPath:/containerPath:ro,shared", + "/hostPath:/containerPath:ro,slave", + "/hostPath:/containerPath:ro,private", + "/hostPath:/containerPath:ro,z,shared", + "/hostPath:/containerPath:ro,Z,slave", + "/hostPath:/containerPath:Z,ro,slave", + "/hostPath:/containerPath:slave,Z,ro", + "/hostPath:/containerPath:Z,slave,ro", + "/hostPath:/containerPath:slave,ro,Z", + "/hostPath:/containerPath:rslave,ro,Z", + "/hostPath:/containerPath:ro,rshared,Z", + "/hostPath:/containerPath:ro,Z,rprivate", + } + invalid = map[string]string{ + "/path:/path:ro,rshared,rslave": `invalid mode: "ro,rshared,rslave"`, + "/path:/path:ro,z,rshared,rslave": `invalid mode: "ro,z,rshared,rslave"`, + "/path:shared": "Invalid volume specification", + "/path:slave": "Invalid volume specification", + "/path:private": "Invalid volume specification", + "name:/absolute-path:shared": "Invalid volume specification", + "name:/absolute-path:rshared": "Invalid volume specification", + "name:/absolute-path:slave": "Invalid volume specification", + "name:/absolute-path:rslave": "Invalid volume specification", + "name:/absolute-path:private": "Invalid volume specification", + "name:/absolute-path:rprivate": "Invalid volume specification", + } + + for _, path := range valid { + if _, err := ParseMountSpec(path, "local"); err != nil { + t.Fatalf("ParseMountSpec(`%q`) should succeed: error %q", path, err) + } + } + + for path, expectedError := range invalid { + if _, err := ParseMountSpec(path, "local"); err == nil { + t.Fatalf("ParseMountSpec(`%q`) should have failed validation. Err %v", path, err) + } else { + if !strings.Contains(err.Error(), expectedError) { + t.Fatalf("ParseMountSpec(`%q`) error should contain %q, got %v", path, expectedError, err.Error()) + } + } + } +}