Add a unit test and couple of integration tests for volume propagation.
Signed-off-by: Vivek Goyal <vgoyal@redhat.com>
| ... | ... |
@@ -19,6 +19,7 @@ import ( |
| 19 | 19 |
"time" |
| 20 | 20 |
|
| 21 | 21 |
"github.com/docker/docker/pkg/integration/checker" |
| 22 |
+ "github.com/docker/docker/pkg/mount" |
|
| 22 | 23 |
"github.com/docker/docker/pkg/nat" |
| 23 | 24 |
"github.com/docker/docker/runconfig" |
| 24 | 25 |
"github.com/docker/libnetwork/resolvconf" |
| ... | ... |
@@ -3812,3 +3813,114 @@ func (s *DockerSuite) TestRunWithOomScoreAdjInvalidRange(c *check.C) {
|
| 3812 | 3812 |
c.Fatalf("Expected output to contain %q, got %q instead", expected, out)
|
| 3813 | 3813 |
} |
| 3814 | 3814 |
} |
| 3815 |
+ |
|
| 3816 |
+func (s *DockerSuite) TestRunVolumesMountedAsShared(c *check.C) {
|
|
| 3817 |
+ // Volume propagation is linux only. Also it creates directories for |
|
| 3818 |
+ // bind mounting, so needs to be same host. |
|
| 3819 |
+ testRequires(c, DaemonIsLinux, SameHostDaemon, NotUserNamespace) |
|
| 3820 |
+ |
|
| 3821 |
+ // Prepare a source directory to bind mount |
|
| 3822 |
+ tmpDir, err := ioutil.TempDir("", "volume-source")
|
|
| 3823 |
+ if err != nil {
|
|
| 3824 |
+ c.Fatal(err) |
|
| 3825 |
+ } |
|
| 3826 |
+ defer os.RemoveAll(tmpDir) |
|
| 3827 |
+ |
|
| 3828 |
+ if err := os.Mkdir(path.Join(tmpDir, "mnt1"), 0755); err != nil {
|
|
| 3829 |
+ c.Fatal(err) |
|
| 3830 |
+ } |
|
| 3831 |
+ |
|
| 3832 |
+ // Convert this directory into a shared mount point so that we do |
|
| 3833 |
+ // not rely on propagation properties of parent mount. |
|
| 3834 |
+ cmd := exec.Command("mount", "--bind", tmpDir, tmpDir)
|
|
| 3835 |
+ if _, err = runCommand(cmd); err != nil {
|
|
| 3836 |
+ c.Fatal(err) |
|
| 3837 |
+ } |
|
| 3838 |
+ |
|
| 3839 |
+ cmd = exec.Command("mount", "--make-private", "--make-shared", tmpDir)
|
|
| 3840 |
+ if _, err = runCommand(cmd); err != nil {
|
|
| 3841 |
+ c.Fatal(err) |
|
| 3842 |
+ } |
|
| 3843 |
+ |
|
| 3844 |
+ dockerCmd(c, "run", "--privileged", "-v", fmt.Sprintf("%s:/volume-dest:shared", tmpDir), "busybox", "mount", "--bind", "/volume-dest/mnt1", "/volume-dest/mnt1")
|
|
| 3845 |
+ |
|
| 3846 |
+ // Make sure a bind mount under a shared volume propagated to host. |
|
| 3847 |
+ if mounted, _ := mount.Mounted(path.Join(tmpDir, "mnt1")); !mounted {
|
|
| 3848 |
+ c.Fatalf("Bind mount under shared volume did not propagate to host")
|
|
| 3849 |
+ } |
|
| 3850 |
+ |
|
| 3851 |
+ mount.Unmount(path.Join(tmpDir, "mnt1")) |
|
| 3852 |
+} |
|
| 3853 |
+ |
|
| 3854 |
+func (s *DockerSuite) TestRunVolumesMountedAsSlave(c *check.C) {
|
|
| 3855 |
+ // Volume propagation is linux only. Also it creates directories for |
|
| 3856 |
+ // bind mounting, so needs to be same host. |
|
| 3857 |
+ testRequires(c, DaemonIsLinux, SameHostDaemon, NotUserNamespace) |
|
| 3858 |
+ |
|
| 3859 |
+ // Prepare a source directory to bind mount |
|
| 3860 |
+ tmpDir, err := ioutil.TempDir("", "volume-source")
|
|
| 3861 |
+ if err != nil {
|
|
| 3862 |
+ c.Fatal(err) |
|
| 3863 |
+ } |
|
| 3864 |
+ defer os.RemoveAll(tmpDir) |
|
| 3865 |
+ |
|
| 3866 |
+ if err := os.Mkdir(path.Join(tmpDir, "mnt1"), 0755); err != nil {
|
|
| 3867 |
+ c.Fatal(err) |
|
| 3868 |
+ } |
|
| 3869 |
+ |
|
| 3870 |
+ // Prepare a source directory with file in it. We will bind mount this |
|
| 3871 |
+ // direcotry and see if file shows up. |
|
| 3872 |
+ tmpDir2, err := ioutil.TempDir("", "volume-source2")
|
|
| 3873 |
+ if err != nil {
|
|
| 3874 |
+ c.Fatal(err) |
|
| 3875 |
+ } |
|
| 3876 |
+ defer os.RemoveAll(tmpDir2) |
|
| 3877 |
+ |
|
| 3878 |
+ if err := ioutil.WriteFile(path.Join(tmpDir2, "slave-testfile"), []byte("Test"), 0644); err != nil {
|
|
| 3879 |
+ c.Fatal(err) |
|
| 3880 |
+ } |
|
| 3881 |
+ |
|
| 3882 |
+ // Convert this directory into a shared mount point so that we do |
|
| 3883 |
+ // not rely on propagation properties of parent mount. |
|
| 3884 |
+ cmd := exec.Command("mount", "--bind", tmpDir, tmpDir)
|
|
| 3885 |
+ if _, err = runCommand(cmd); err != nil {
|
|
| 3886 |
+ c.Fatal(err) |
|
| 3887 |
+ } |
|
| 3888 |
+ |
|
| 3889 |
+ cmd = exec.Command("mount", "--make-private", "--make-shared", tmpDir)
|
|
| 3890 |
+ if _, err = runCommand(cmd); err != nil {
|
|
| 3891 |
+ c.Fatal(err) |
|
| 3892 |
+ } |
|
| 3893 |
+ |
|
| 3894 |
+ dockerCmd(c, "run", "-i", "-d", "--name", "parent", "-v", fmt.Sprintf("%s:/volume-dest:slave", tmpDir), "busybox", "top")
|
|
| 3895 |
+ |
|
| 3896 |
+ // Bind mount tmpDir2/ onto tmpDir/mnt1. If mount propagates inside |
|
| 3897 |
+ // container then contents of tmpDir2/slave-testfile should become |
|
| 3898 |
+ // visible at "/volume-dest/mnt1/slave-testfile" |
|
| 3899 |
+ cmd = exec.Command("mount", "--bind", tmpDir2, path.Join(tmpDir, "mnt1"))
|
|
| 3900 |
+ if _, err = runCommand(cmd); err != nil {
|
|
| 3901 |
+ c.Fatal(err) |
|
| 3902 |
+ } |
|
| 3903 |
+ |
|
| 3904 |
+ out, _ := dockerCmd(c, "exec", "parent", "cat", "/volume-dest/mnt1/slave-testfile") |
|
| 3905 |
+ |
|
| 3906 |
+ mount.Unmount(path.Join(tmpDir, "mnt1")) |
|
| 3907 |
+ |
|
| 3908 |
+ if out != "Test" {
|
|
| 3909 |
+ c.Fatalf("Bind mount under slave volume did not propagate to container")
|
|
| 3910 |
+ } |
|
| 3911 |
+} |
|
| 3912 |
+ |
|
| 3913 |
+func (s *DockerSuite) TestRunNamedVolumesMountedAsShared(c *check.C) {
|
|
| 3914 |
+ testRequires(c, DaemonIsLinux, NotUserNamespace) |
|
| 3915 |
+ out, exitcode, _ := dockerCmdWithError("run", "-v", "foo:/test:shared", "busybox", "touch", "/test/somefile")
|
|
| 3916 |
+ |
|
| 3917 |
+ if exitcode == 0 {
|
|
| 3918 |
+ c.Fatalf("expected non-zero exit code; received %d", exitcode)
|
|
| 3919 |
+ } |
|
| 3920 |
+ |
|
| 3921 |
+ if expected := "Invalid volume specification"; !strings.Contains(out, expected) {
|
|
| 3922 |
+ c.Fatalf(`Expected %q in output; got: %s`, expected, out) |
|
| 3923 |
+ } |
|
| 3924 |
+ |
|
| 3925 |
+} |
| 3815 | 3926 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,65 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package volume |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "strings" |
|
| 6 |
+ "testing" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestParseMountSpecPropagation(t *testing.T) {
|
|
| 10 |
+ var ( |
|
| 11 |
+ valid []string |
|
| 12 |
+ invalid map[string]string |
|
| 13 |
+ ) |
|
| 14 |
+ |
|
| 15 |
+ valid = []string{
|
|
| 16 |
+ "/hostPath:/containerPath:shared", |
|
| 17 |
+ "/hostPath:/containerPath:rshared", |
|
| 18 |
+ "/hostPath:/containerPath:slave", |
|
| 19 |
+ "/hostPath:/containerPath:rslave", |
|
| 20 |
+ "/hostPath:/containerPath:private", |
|
| 21 |
+ "/hostPath:/containerPath:rprivate", |
|
| 22 |
+ "/hostPath:/containerPath:ro,shared", |
|
| 23 |
+ "/hostPath:/containerPath:ro,slave", |
|
| 24 |
+ "/hostPath:/containerPath:ro,private", |
|
| 25 |
+ "/hostPath:/containerPath:ro,z,shared", |
|
| 26 |
+ "/hostPath:/containerPath:ro,Z,slave", |
|
| 27 |
+ "/hostPath:/containerPath:Z,ro,slave", |
|
| 28 |
+ "/hostPath:/containerPath:slave,Z,ro", |
|
| 29 |
+ "/hostPath:/containerPath:Z,slave,ro", |
|
| 30 |
+ "/hostPath:/containerPath:slave,ro,Z", |
|
| 31 |
+ "/hostPath:/containerPath:rslave,ro,Z", |
|
| 32 |
+ "/hostPath:/containerPath:ro,rshared,Z", |
|
| 33 |
+ "/hostPath:/containerPath:ro,Z,rprivate", |
|
| 34 |
+ } |
|
| 35 |
+ invalid = map[string]string{
|
|
| 36 |
+ "/path:/path:ro,rshared,rslave": `invalid mode: "ro,rshared,rslave"`, |
|
| 37 |
+ "/path:/path:ro,z,rshared,rslave": `invalid mode: "ro,z,rshared,rslave"`, |
|
| 38 |
+ "/path:shared": "Invalid volume specification", |
|
| 39 |
+ "/path:slave": "Invalid volume specification", |
|
| 40 |
+ "/path:private": "Invalid volume specification", |
|
| 41 |
+ "name:/absolute-path:shared": "Invalid volume specification", |
|
| 42 |
+ "name:/absolute-path:rshared": "Invalid volume specification", |
|
| 43 |
+ "name:/absolute-path:slave": "Invalid volume specification", |
|
| 44 |
+ "name:/absolute-path:rslave": "Invalid volume specification", |
|
| 45 |
+ "name:/absolute-path:private": "Invalid volume specification", |
|
| 46 |
+ "name:/absolute-path:rprivate": "Invalid volume specification", |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ for _, path := range valid {
|
|
| 50 |
+ if _, err := ParseMountSpec(path, "local"); err != nil {
|
|
| 51 |
+ t.Fatalf("ParseMountSpec(`%q`) should succeed: error %q", path, err)
|
|
| 52 |
+ } |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ for path, expectedError := range invalid {
|
|
| 56 |
+ if _, err := ParseMountSpec(path, "local"); err == nil {
|
|
| 57 |
+ t.Fatalf("ParseMountSpec(`%q`) should have failed validation. Err %v", path, err)
|
|
| 58 |
+ } else {
|
|
| 59 |
+ if !strings.Contains(err.Error(), expectedError) {
|
|
| 60 |
+ t.Fatalf("ParseMountSpec(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
|
|
| 61 |
+ } |
|
| 62 |
+ } |
|
| 63 |
+ } |
|
| 64 |
+} |