Browse code

Propagate unmount events to the external volume drivers.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2015/05/23 02:37:00
Showing 8 changed files
... ...
@@ -346,6 +346,8 @@ func (container *Container) cleanup() {
346 346
 	for _, eConfig := range container.execCommands.s {
347 347
 		container.daemon.unregisterExecCommand(eConfig)
348 348
 	}
349
+
350
+	container.UnmountVolumes(true)
349 351
 }
350 352
 
351 353
 func (container *Container) KillSig(sig int) error {
... ...
@@ -469,6 +471,7 @@ func (container *Container) Stop(seconds int) error {
469 469
 			return err
470 470
 		}
471 471
 	}
472
+
472 473
 	return nil
473 474
 }
474 475
 
... ...
@@ -564,16 +567,10 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
564 564
 	if err := container.Mount(); err != nil {
565 565
 		return nil, err
566 566
 	}
567
-	var paths []string
568
-	unmount := func() {
569
-		for _, p := range paths {
570
-			syscall.Unmount(p, 0)
571
-		}
572
-	}
573 567
 	defer func() {
574 568
 		if err != nil {
575 569
 			// unmount any volumes
576
-			unmount()
570
+			container.UnmountVolumes(true)
577 571
 			// unmount the container's rootfs
578 572
 			container.Unmount()
579 573
 		}
... ...
@@ -587,7 +584,6 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
587 587
 		if err != nil {
588 588
 			return nil, err
589 589
 		}
590
-		paths = append(paths, dest)
591 590
 		if err := mount.Mount(m.Source, dest, "bind", "rbind,ro"); err != nil {
592 591
 			return nil, err
593 592
 		}
... ...
@@ -618,7 +614,7 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
618 618
 	}
619 619
 	return ioutils.NewReadCloserWrapper(archive, func() error {
620 620
 			err := archive.Close()
621
-			unmount()
621
+			container.UnmountVolumes(true)
622 622
 			container.Unmount()
623 623
 			return err
624 624
 		}),
... ...
@@ -1090,3 +1086,48 @@ func (container *Container) shouldRestart() bool {
1090 1090
 	return container.hostConfig.RestartPolicy.Name == "always" ||
1091 1091
 		(container.hostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0)
1092 1092
 }
1093
+
1094
+func (container *Container) UnmountVolumes(forceSyscall bool) error {
1095
+	for _, m := range container.MountPoints {
1096
+		dest, err := container.GetResourcePath(m.Destination)
1097
+		if err != nil {
1098
+			return err
1099
+		}
1100
+
1101
+		if forceSyscall {
1102
+			syscall.Unmount(dest, 0)
1103
+		}
1104
+
1105
+		if m.Volume != nil {
1106
+			if err := m.Volume.Unmount(); err != nil {
1107
+				return err
1108
+			}
1109
+		}
1110
+	}
1111
+	return nil
1112
+}
1113
+
1114
+func (container *Container) copyImagePathContent(v volume.Volume, destination string) error {
1115
+	rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, destination), container.basefs)
1116
+	if err != nil {
1117
+		return err
1118
+	}
1119
+
1120
+	if _, err = ioutil.ReadDir(rootfs); err != nil {
1121
+		if os.IsNotExist(err) {
1122
+			return nil
1123
+		}
1124
+		return err
1125
+	}
1126
+
1127
+	path, err := v.Mount()
1128
+	if err != nil {
1129
+		return err
1130
+	}
1131
+
1132
+	if err := copyExistingContents(rootfs, path); err != nil {
1133
+		return err
1134
+	}
1135
+
1136
+	return v.Unmount()
1137
+}
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"github.com/docker/docker/image"
11 11
 	"github.com/docker/docker/pkg/parsers"
12 12
 	"github.com/docker/docker/pkg/stringid"
13
-	"github.com/docker/docker/pkg/symlink"
14 13
 	"github.com/docker/docker/runconfig"
15 14
 	"github.com/docker/libcontainer/label"
16 15
 )
... ...
@@ -120,21 +119,20 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
120 120
 		if err != nil {
121 121
 			return nil, nil, err
122 122
 		}
123
-		if stat, err := os.Stat(path); err == nil && !stat.IsDir() {
123
+
124
+		stat, err := os.Stat(path)
125
+		if err == nil && !stat.IsDir() {
124 126
 			return nil, nil, fmt.Errorf("cannot mount volume over existing file, file exists %s", path)
125 127
 		}
128
+
126 129
 		v, err := createVolume(name, config.VolumeDriver)
127 130
 		if err != nil {
128 131
 			return nil, nil, err
129 132
 		}
130
-		rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, destination), container.basefs)
131
-		if err != nil {
132
-			return nil, nil, err
133
-		}
134
-		if path, err = v.Mount(); err != nil {
133
+
134
+		if err := container.copyImagePathContent(v, destination); err != nil {
135 135
 			return nil, nil, err
136 136
 		}
137
-		copyExistingContents(rootfs, path)
138 137
 
139 138
 		container.addMountPointWithVolume(destination, v, true)
140 139
 	}
... ...
@@ -70,6 +70,7 @@ func (daemon *Daemon) ContainerRm(name string, config *ContainerRmConfig) error
70 70
 			}
71 71
 		}
72 72
 		container.LogEvent("destroy")
73
+
73 74
 		if config.RemoveVolume {
74 75
 			container.removeMountPoints()
75 76
 		}
... ...
@@ -440,14 +440,6 @@ func (s *DockerSuite) TestRunCreateVolumesInSymlinkDir(c *check.C) {
440 440
 	}
441 441
 }
442 442
 
443
-// Regression test for #4830
444
-func (s *DockerSuite) TestRunWithRelativePath(c *check.C) {
445
-	runCmd := exec.Command(dockerBinary, "run", "-v", "tmp:/other-tmp", "busybox", "true")
446
-	if _, _, _, err := runCommandWithStdoutStderr(runCmd); err == nil {
447
-		c.Fatalf("relative path should result in an error")
448
-	}
449
-}
450
-
451 443
 func (s *DockerSuite) TestRunVolumesMountedAsReadonly(c *check.C) {
452 444
 	cmd := exec.Command(dockerBinary, "run", "-v", "/test:/test:ro", "busybox", "touch", "/test/somefile")
453 445
 	if code, err := runCommand(cmd); err == nil || code == 0 {
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"net/http"
11 11
 	"net/http/httptest"
12 12
 	"os"
13
-	"os/exec"
14 13
 	"path/filepath"
15 14
 	"strings"
16 15
 
... ...
@@ -18,25 +17,40 @@ import (
18 18
 )
19 19
 
20 20
 func init() {
21
-	check.Suite(&ExternalVolumeSuite{
21
+	check.Suite(&DockerExternalVolumeSuite{
22 22
 		ds: &DockerSuite{},
23 23
 	})
24 24
 }
25 25
 
26
-type ExternalVolumeSuite struct {
26
+type eventCounter struct {
27
+	activations int
28
+	creations   int
29
+	removals    int
30
+	mounts      int
31
+	unmounts    int
32
+	paths       int
33
+}
34
+
35
+type DockerExternalVolumeSuite struct {
27 36
 	server *httptest.Server
28 37
 	ds     *DockerSuite
38
+	d      *Daemon
39
+	ec     *eventCounter
29 40
 }
30 41
 
31
-func (s *ExternalVolumeSuite) SetUpTest(c *check.C) {
42
+func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
43
+	s.d = NewDaemon(c)
32 44
 	s.ds.SetUpTest(c)
45
+	s.ec = &eventCounter{}
46
+
33 47
 }
34 48
 
35
-func (s *ExternalVolumeSuite) TearDownTest(c *check.C) {
49
+func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {
50
+	s.d.Stop()
36 51
 	s.ds.TearDownTest(c)
37 52
 }
38 53
 
39
-func (s *ExternalVolumeSuite) SetUpSuite(c *check.C) {
54
+func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
40 55
 	mux := http.NewServeMux()
41 56
 	s.server = httptest.NewServer(mux)
42 57
 
... ...
@@ -44,26 +58,30 @@ func (s *ExternalVolumeSuite) SetUpSuite(c *check.C) {
44 44
 		name string
45 45
 	}
46 46
 
47
-	hostVolumePath := func(name string) string {
48
-		return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
49
-	}
50
-
51 47
 	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
48
+		s.ec.activations++
49
+
52 50
 		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
53 51
 		fmt.Fprintln(w, `{"Implements": ["VolumeDriver"]}`)
54 52
 	})
55 53
 
56 54
 	mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
55
+		s.ec.creations++
56
+
57 57
 		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
58 58
 		fmt.Fprintln(w, `{}`)
59 59
 	})
60 60
 
61 61
 	mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
62
+		s.ec.removals++
63
+
62 64
 		w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
63 65
 		fmt.Fprintln(w, `{}`)
64 66
 	})
65 67
 
66 68
 	mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
69
+		s.ec.paths++
70
+
67 71
 		var pr pluginRequest
68 72
 		if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
69 73
 			http.Error(w, err.Error(), 500)
... ...
@@ -76,6 +94,8 @@ func (s *ExternalVolumeSuite) SetUpSuite(c *check.C) {
76 76
 	})
77 77
 
78 78
 	mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
79
+		s.ec.mounts++
80
+
79 81
 		var pr pluginRequest
80 82
 		if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
81 83
 			http.Error(w, err.Error(), 500)
... ...
@@ -94,7 +114,9 @@ func (s *ExternalVolumeSuite) SetUpSuite(c *check.C) {
94 94
 		fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
95 95
 	})
96 96
 
97
-	mux.HandleFunc("/VolumeDriver.Umount", func(w http.ResponseWriter, r *http.Request) {
97
+	mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
98
+		s.ec.unmounts++
99
+
98 100
 		var pr pluginRequest
99 101
 		if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
100 102
 			http.Error(w, err.Error(), 500)
... ...
@@ -118,7 +140,7 @@ func (s *ExternalVolumeSuite) SetUpSuite(c *check.C) {
118 118
 	}
119 119
 }
120 120
 
121
-func (s *ExternalVolumeSuite) TearDownSuite(c *check.C) {
121
+func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
122 122
 	s.server.Close()
123 123
 
124 124
 	if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
... ...
@@ -126,14 +148,102 @@ func (s *ExternalVolumeSuite) TearDownSuite(c *check.C) {
126 126
 	}
127 127
 }
128 128
 
129
-func (s *ExternalVolumeSuite) TestStartExternalVolumeDriver(c *check.C) {
130
-	runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
131
-	out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
132
-	if err != nil && exitCode != 0 {
133
-		c.Fatal(out, stderr, err)
129
+func (s *DockerExternalVolumeSuite) TestStartExternalNamedVolumeDriver(c *check.C) {
130
+	if err := s.d.StartWithBusybox(); err != nil {
131
+		c.Fatal(err)
132
+	}
133
+
134
+	out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
135
+	if err != nil {
136
+		c.Fatal(err)
134 137
 	}
135 138
 
136 139
 	if !strings.Contains(out, s.server.URL) {
137 140
 		c.Fatalf("External volume mount failed. Output: %s\n", out)
138 141
 	}
142
+
143
+	p := hostVolumePath("external-volume-test")
144
+	_, err = os.Lstat(p)
145
+	if err == nil {
146
+		c.Fatalf("Expected error checking volume path in host: %s\n", p)
147
+	}
148
+
149
+	if !os.IsNotExist(err) {
150
+		c.Fatalf("Expected volume path in host to not exist: %s, %v\n", p, err)
151
+	}
152
+
153
+	c.Assert(s.ec.activations, check.Equals, 1)
154
+	c.Assert(s.ec.creations, check.Equals, 1)
155
+	c.Assert(s.ec.removals, check.Equals, 1)
156
+	c.Assert(s.ec.mounts, check.Equals, 1)
157
+	c.Assert(s.ec.unmounts, check.Equals, 1)
158
+}
159
+
160
+func (s *DockerExternalVolumeSuite) TestStartExternalVolumeUnnamedDriver(c *check.C) {
161
+	if err := s.d.StartWithBusybox(); err != nil {
162
+		c.Fatal(err)
163
+	}
164
+
165
+	out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
166
+	if err != nil {
167
+		c.Fatal(err)
168
+	}
169
+
170
+	if !strings.Contains(out, s.server.URL) {
171
+		c.Fatalf("External volume mount failed. Output: %s\n", out)
172
+	}
173
+
174
+	c.Assert(s.ec.activations, check.Equals, 1)
175
+	c.Assert(s.ec.creations, check.Equals, 1)
176
+	c.Assert(s.ec.removals, check.Equals, 1)
177
+	c.Assert(s.ec.mounts, check.Equals, 1)
178
+	c.Assert(s.ec.unmounts, check.Equals, 1)
179
+}
180
+
181
+func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverVolumesFrom(c *check.C) {
182
+	if err := s.d.StartWithBusybox(); err != nil {
183
+		c.Fatal(err)
184
+	}
185
+
186
+	if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
187
+		c.Fatal(err)
188
+	}
189
+
190
+	if _, err := s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp"); err != nil {
191
+		c.Fatal(err)
192
+	}
193
+
194
+	if _, err := s.d.Cmd("rm", "-f", "vol-test1"); err != nil {
195
+		c.Fatal(err)
196
+	}
197
+
198
+	c.Assert(s.ec.activations, check.Equals, 1)
199
+	c.Assert(s.ec.creations, check.Equals, 2)
200
+	c.Assert(s.ec.removals, check.Equals, 1)
201
+	c.Assert(s.ec.mounts, check.Equals, 2)
202
+	c.Assert(s.ec.unmounts, check.Equals, 2)
203
+}
204
+
205
+func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverDeleteContainer(c *check.C) {
206
+	if err := s.d.StartWithBusybox(); err != nil {
207
+		c.Fatal(err)
208
+	}
209
+
210
+	if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
211
+		c.Fatal(err)
212
+	}
213
+
214
+	if _, err := s.d.Cmd("rm", "-fv", "vol-test1"); err != nil {
215
+		c.Fatal(err)
216
+	}
217
+
218
+	c.Assert(s.ec.activations, check.Equals, 1)
219
+	c.Assert(s.ec.creations, check.Equals, 1)
220
+	c.Assert(s.ec.removals, check.Equals, 1)
221
+	c.Assert(s.ec.mounts, check.Equals, 1)
222
+	c.Assert(s.ec.unmounts, check.Equals, 1)
223
+}
224
+
225
+func hostVolumePath(name string) string {
226
+	return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
139 227
 }
... ...
@@ -16,7 +16,10 @@ func (a *volumeDriverAdapter) Create(name string) (volume.Volume, error) {
16 16
 	if err != nil {
17 17
 		return nil, err
18 18
 	}
19
-	return &volumeAdapter{a.proxy, name, a.name}, nil
19
+	return &volumeAdapter{
20
+		proxy:      a.proxy,
21
+		name:       name,
22
+		driverName: a.name}, nil
20 23
 }
21 24
 
22 25
 func (a *volumeDriverAdapter) Remove(v volume.Volume) error {
... ...
@@ -27,6 +30,7 @@ type volumeAdapter struct {
27 27
 	proxy      *volumeDriverProxy
28 28
 	name       string
29 29
 	driverName string
30
+	eMount     string // ephemeral host volume path
30 31
 }
31 32
 
32 33
 func (a *volumeAdapter) Name() string {
... ...
@@ -38,12 +42,17 @@ func (a *volumeAdapter) DriverName() string {
38 38
 }
39 39
 
40 40
 func (a *volumeAdapter) Path() string {
41
+	if len(a.eMount) > 0 {
42
+		return a.eMount
43
+	}
41 44
 	m, _ := a.proxy.Path(a.name)
42 45
 	return m
43 46
 }
44 47
 
45 48
 func (a *volumeAdapter) Mount() (string, error) {
46
-	return a.proxy.Mount(a.name)
49
+	var err error
50
+	a.eMount, err = a.proxy.Mount(a.name)
51
+	return a.eMount, err
47 52
 }
48 53
 
49 54
 func (a *volumeAdapter) Unmount() error {
... ...
@@ -1,9 +1,9 @@
1 1
 package volumedrivers
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"sync"
5 6
 
6
-	"github.com/Sirupsen/logrus"
7 7
 	"github.com/docker/docker/pkg/plugins"
8 8
 	"github.com/docker/docker/volume"
9 9
 )
... ...
@@ -52,9 +52,9 @@ func Lookup(name string) (volume.Driver, error) {
52 52
 	}
53 53
 	pl, err := plugins.Get(name, "VolumeDriver")
54 54
 	if err != nil {
55
-		logrus.Errorf("Error: %v", err)
56
-		return nil, err
55
+		return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
57 56
 	}
57
+
58 58
 	d := NewVolumeDriver(name, pl.Client)
59 59
 	drivers.extensions[name] = d
60 60
 	return d, nil
... ...
@@ -1,5 +1,7 @@
1 1
 package volumedrivers
2 2
 
3
+import "fmt"
4
+
3 5
 // currently created by hand. generation tool would generate this like:
4 6
 // $ rpc-gen volume/drivers/api.go VolumeDriver > volume/drivers/proxy.go
5 7
 
... ...
@@ -21,9 +23,9 @@ func (pp *volumeDriverProxy) Create(name string) error {
21 21
 	var ret volumeDriverResponse
22 22
 	err := pp.c.Call("VolumeDriver.Create", args, &ret)
23 23
 	if err != nil {
24
-		return err
24
+		return pp.fmtError(name, err)
25 25
 	}
26
-	return ret.Err
26
+	return pp.fmtError(name, ret.Err)
27 27
 }
28 28
 
29 29
 func (pp *volumeDriverProxy) Remove(name string) error {
... ...
@@ -31,27 +33,27 @@ func (pp *volumeDriverProxy) Remove(name string) error {
31 31
 	var ret volumeDriverResponse
32 32
 	err := pp.c.Call("VolumeDriver.Remove", args, &ret)
33 33
 	if err != nil {
34
-		return err
34
+		return pp.fmtError(name, err)
35 35
 	}
36
-	return ret.Err
36
+	return pp.fmtError(name, ret.Err)
37 37
 }
38 38
 
39 39
 func (pp *volumeDriverProxy) Path(name string) (string, error) {
40 40
 	args := volumeDriverRequest{name}
41 41
 	var ret volumeDriverResponse
42 42
 	if err := pp.c.Call("VolumeDriver.Path", args, &ret); err != nil {
43
-		return "", err
43
+		return "", pp.fmtError(name, err)
44 44
 	}
45
-	return ret.Mountpoint, ret.Err
45
+	return ret.Mountpoint, pp.fmtError(name, ret.Err)
46 46
 }
47 47
 
48 48
 func (pp *volumeDriverProxy) Mount(name string) (string, error) {
49 49
 	args := volumeDriverRequest{name}
50 50
 	var ret volumeDriverResponse
51 51
 	if err := pp.c.Call("VolumeDriver.Mount", args, &ret); err != nil {
52
-		return "", err
52
+		return "", pp.fmtError(name, err)
53 53
 	}
54
-	return ret.Mountpoint, ret.Err
54
+	return ret.Mountpoint, pp.fmtError(name, ret.Err)
55 55
 }
56 56
 
57 57
 func (pp *volumeDriverProxy) Unmount(name string) error {
... ...
@@ -59,7 +61,14 @@ func (pp *volumeDriverProxy) Unmount(name string) error {
59 59
 	var ret volumeDriverResponse
60 60
 	err := pp.c.Call("VolumeDriver.Unmount", args, &ret)
61 61
 	if err != nil {
62
-		return err
62
+		return pp.fmtError(name, err)
63
+	}
64
+	return pp.fmtError(name, ret.Err)
65
+}
66
+
67
+func (pp *volumeDriverProxy) fmtError(name string, err error) error {
68
+	if err == nil {
69
+		return nil
63 70
 	}
64
-	return ret.Err
71
+	return fmt.Errorf("External volume driver request failed for %s: %v", name, err)
65 72
 }