Browse code

Allow volume drivers to provide a `Status` field

The `Status` field is a `map[string]interface{}` which allows the driver to pass
back low-level details about the underlying volume.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2016/03/08 05:44:43
Showing 10 changed files
... ...
@@ -206,6 +206,7 @@ func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
206 206
 	}
207 207
 	apiV := volumeToAPIType(v)
208 208
 	apiV.Mountpoint = v.Path()
209
+	apiV.Status = v.Status()
209 210
 	return apiV, nil
210 211
 }
211 212
 
... ...
@@ -15,6 +15,21 @@ external storage systems, such as Amazon EBS, and enable data volumes to persist
15 15
 beyond the lifetime of a single Engine host. See the [plugin
16 16
 documentation](plugins.md) for more information.
17 17
 
18
+## Changelog
19
+
20
+### 1.12.0
21
+
22
+- Add `Status` field to `VolumeDriver.Get` response ([#21006](https://github.com/docker/docker/pull/21006#))
23
+
24
+### 1.10.0
25
+
26
+- Add `VolumeDriver.Get` which gets the details about the volume ([#16534](https://github.com/docker/docker/pull/16534))
27
+- Add `VolumeDriver.List` which lists all volumes owned by the driver ([#16534](https://github.com/docker/docker/pull/16534))
28
+
29
+### 1.8.0
30
+
31
+- Initial support for volume driver plugins ([#14659](https://github.com/docker/docker/pull/14659))
32
+
18 33
 ## Command-line changes
19 34
 
20 35
 A volume plugin makes use of the `-v`and `--volume-driver` flag on the `docker run` command.  The `-v` flag accepts a volume name and the `--volume-driver` flag a driver type, for example:
... ...
@@ -183,6 +198,7 @@ Get the volume info.
183 183
   "Volume": {
184 184
     "Name": "volume_name",
185 185
     "Mountpoint": "/path/to/directory/on/host",
186
+    "Status": {}
186 187
   },
187 188
   "Err": ""
188 189
 }
... ...
@@ -2792,7 +2792,8 @@ Create a volume
2792 2792
     {
2793 2793
       "Name": "tardis",
2794 2794
       "Driver": "local",
2795
-      "Mountpoint": "/var/lib/docker/volumes/tardis"
2795
+      "Mountpoint": "/var/lib/docker/volumes/tardis",
2796
+      "Status": null
2796 2797
     }
2797 2798
 
2798 2799
 Status Codes:
... ...
@@ -32,7 +32,8 @@ Example output:
32 32
       {
33 33
           "Name": "85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d",
34 34
           "Driver": "local",
35
-          "Mountpoint": "/var/lib/docker/volumes/85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d/_data"
35
+          "Mountpoint": "/var/lib/docker/volumes/85bffb0677236974f93955d8ecc4df55ef5070117b0e53333cc1b443777be24d/_data",
36
+          "Status": null
36 37
       }
37 38
     ]
38 39
 
... ...
@@ -44,4 +45,4 @@ Example output:
44 44
 * [volume create](volume_create.md)
45 45
 * [volume ls](volume_ls.md)
46 46
 * [volume rm](volume_rm.md)
47
-* [Understand Data Volumes](../../userguide/containers/dockervolumes.md)
48 47
\ No newline at end of file
48
+* [Understand Data Volumes](../../userguide/containers/dockervolumes.md)
49 49
new file mode 100644
... ...
@@ -0,0 +1,478 @@
0
+// +build !windows
1
+
2
+package main
3
+
4
+import (
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"io/ioutil"
9
+	"net/http"
10
+	"net/http/httptest"
11
+	"os"
12
+	"os/exec"
13
+	"path/filepath"
14
+	"strings"
15
+	"time"
16
+
17
+	"github.com/docker/docker/pkg/integration/checker"
18
+	"github.com/docker/engine-api/types"
19
+	"github.com/go-check/check"
20
+)
21
+
22
+func init() {
23
+	check.Suite(&DockerExternalVolumeSuite{
24
+		ds: &DockerSuite{},
25
+	})
26
+}
27
+
28
+type eventCounter struct {
29
+	activations int
30
+	creations   int
31
+	removals    int
32
+	mounts      int
33
+	unmounts    int
34
+	paths       int
35
+	lists       int
36
+	gets        int
37
+}
38
+
39
+type DockerExternalVolumeSuite struct {
40
+	server *httptest.Server
41
+	ds     *DockerSuite
42
+	d      *Daemon
43
+	ec     *eventCounter
44
+}
45
+
46
+func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
47
+	s.d = NewDaemon(c)
48
+	s.ec = &eventCounter{}
49
+}
50
+
51
+func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {
52
+	s.d.Stop()
53
+	s.ds.TearDownTest(c)
54
+}
55
+
56
+func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
57
+	mux := http.NewServeMux()
58
+	s.server = httptest.NewServer(mux)
59
+
60
+	type pluginRequest struct {
61
+		Name string
62
+		Opts map[string]string
63
+	}
64
+
65
+	type pluginResp struct {
66
+		Mountpoint string `json:",omitempty"`
67
+		Err        string `json:",omitempty"`
68
+	}
69
+
70
+	type vol struct {
71
+		Name       string
72
+		Mountpoint string
73
+		Ninja      bool // hack used to trigger an null volume return on `Get`
74
+		Status     map[string]interface{}
75
+	}
76
+	var volList []vol
77
+
78
+	read := func(b io.ReadCloser) (pluginRequest, error) {
79
+		defer b.Close()
80
+		var pr pluginRequest
81
+		if err := json.NewDecoder(b).Decode(&pr); err != nil {
82
+			return pr, err
83
+		}
84
+		return pr, nil
85
+	}
86
+
87
+	send := func(w http.ResponseWriter, data interface{}) {
88
+		switch t := data.(type) {
89
+		case error:
90
+			http.Error(w, t.Error(), 500)
91
+		case string:
92
+			w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
93
+			fmt.Fprintln(w, t)
94
+		default:
95
+			w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
96
+			json.NewEncoder(w).Encode(&data)
97
+		}
98
+	}
99
+
100
+	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
101
+		s.ec.activations++
102
+		send(w, `{"Implements": ["VolumeDriver"]}`)
103
+	})
104
+
105
+	mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
106
+		s.ec.creations++
107
+		pr, err := read(r.Body)
108
+		if err != nil {
109
+			send(w, err)
110
+			return
111
+		}
112
+		_, isNinja := pr.Opts["ninja"]
113
+		status := map[string]interface{}{"Hello": "world"}
114
+		volList = append(volList, vol{Name: pr.Name, Ninja: isNinja, Status: status})
115
+		send(w, nil)
116
+	})
117
+
118
+	mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
119
+		s.ec.lists++
120
+		vols := []vol{}
121
+		for _, v := range volList {
122
+			if v.Ninja {
123
+				continue
124
+			}
125
+			vols = append(vols, v)
126
+		}
127
+		send(w, map[string][]vol{"Volumes": vols})
128
+	})
129
+
130
+	mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) {
131
+		s.ec.gets++
132
+		pr, err := read(r.Body)
133
+		if err != nil {
134
+			send(w, err)
135
+			return
136
+		}
137
+
138
+		for _, v := range volList {
139
+			if v.Name == pr.Name {
140
+				if v.Ninja {
141
+					send(w, map[string]vol{})
142
+					return
143
+				}
144
+
145
+				v.Mountpoint = hostVolumePath(pr.Name)
146
+				send(w, map[string]vol{"Volume": v})
147
+				return
148
+			}
149
+		}
150
+		send(w, `{"Err": "no such volume"}`)
151
+	})
152
+
153
+	mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
154
+		s.ec.removals++
155
+		pr, err := read(r.Body)
156
+		if err != nil {
157
+			send(w, err)
158
+			return
159
+		}
160
+
161
+		for i, v := range volList {
162
+			if v.Name == pr.Name {
163
+				if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil {
164
+					send(w, &pluginResp{Err: err.Error()})
165
+					return
166
+				}
167
+				volList = append(volList[:i], volList[i+1:]...)
168
+				break
169
+			}
170
+		}
171
+		send(w, nil)
172
+	})
173
+
174
+	mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
175
+		s.ec.paths++
176
+
177
+		pr, err := read(r.Body)
178
+		if err != nil {
179
+			send(w, err)
180
+			return
181
+		}
182
+		p := hostVolumePath(pr.Name)
183
+		send(w, &pluginResp{Mountpoint: p})
184
+	})
185
+
186
+	mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
187
+		s.ec.mounts++
188
+
189
+		pr, err := read(r.Body)
190
+		if err != nil {
191
+			send(w, err)
192
+			return
193
+		}
194
+
195
+		p := hostVolumePath(pr.Name)
196
+		if err := os.MkdirAll(p, 0755); err != nil {
197
+			send(w, &pluginResp{Err: err.Error()})
198
+			return
199
+		}
200
+
201
+		if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil {
202
+			send(w, err)
203
+			return
204
+		}
205
+
206
+		send(w, &pluginResp{Mountpoint: p})
207
+	})
208
+
209
+	mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
210
+		s.ec.unmounts++
211
+
212
+		_, err := read(r.Body)
213
+		if err != nil {
214
+			send(w, err)
215
+			return
216
+		}
217
+
218
+		send(w, nil)
219
+	})
220
+
221
+	err := os.MkdirAll("/etc/docker/plugins", 0755)
222
+	c.Assert(err, checker.IsNil)
223
+
224
+	err = ioutil.WriteFile("/etc/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644)
225
+	c.Assert(err, checker.IsNil)
226
+}
227
+
228
+func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
229
+	s.server.Close()
230
+
231
+	err := os.RemoveAll("/etc/docker/plugins")
232
+	c.Assert(err, checker.IsNil)
233
+}
234
+
235
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) {
236
+	err := s.d.StartWithBusybox()
237
+	c.Assert(err, checker.IsNil)
238
+
239
+	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")
240
+	c.Assert(err, checker.IsNil, check.Commentf(out))
241
+	c.Assert(out, checker.Contains, s.server.URL)
242
+
243
+	_, err = s.d.Cmd("volume", "rm", "external-volume-test")
244
+	c.Assert(err, checker.IsNil)
245
+
246
+	p := hostVolumePath("external-volume-test")
247
+	_, err = os.Lstat(p)
248
+	c.Assert(err, checker.NotNil)
249
+	c.Assert(os.IsNotExist(err), checker.True, check.Commentf("Expected volume path in host to not exist: %s, %v\n", p, err))
250
+
251
+	c.Assert(s.ec.activations, checker.Equals, 1)
252
+	c.Assert(s.ec.creations, checker.Equals, 1)
253
+	c.Assert(s.ec.removals, checker.Equals, 1)
254
+	c.Assert(s.ec.mounts, checker.Equals, 1)
255
+	c.Assert(s.ec.unmounts, checker.Equals, 1)
256
+}
257
+
258
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnnamed(c *check.C) {
259
+	err := s.d.StartWithBusybox()
260
+	c.Assert(err, checker.IsNil)
261
+
262
+	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")
263
+	c.Assert(err, checker.IsNil, check.Commentf(out))
264
+	c.Assert(out, checker.Contains, s.server.URL)
265
+
266
+	c.Assert(s.ec.activations, checker.Equals, 1)
267
+	c.Assert(s.ec.creations, checker.Equals, 1)
268
+	c.Assert(s.ec.removals, checker.Equals, 1)
269
+	c.Assert(s.ec.mounts, checker.Equals, 1)
270
+	c.Assert(s.ec.unmounts, checker.Equals, 1)
271
+}
272
+
273
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverVolumesFrom(c *check.C) {
274
+	err := s.d.StartWithBusybox()
275
+	c.Assert(err, checker.IsNil)
276
+
277
+	out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest")
278
+	c.Assert(err, checker.IsNil, check.Commentf(out))
279
+
280
+	out, err = s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp")
281
+	c.Assert(err, checker.IsNil, check.Commentf(out))
282
+
283
+	out, err = s.d.Cmd("rm", "-fv", "vol-test1")
284
+	c.Assert(err, checker.IsNil, check.Commentf(out))
285
+
286
+	c.Assert(s.ec.activations, checker.Equals, 1)
287
+	c.Assert(s.ec.creations, checker.Equals, 1)
288
+	c.Assert(s.ec.removals, checker.Equals, 1)
289
+	c.Assert(s.ec.mounts, checker.Equals, 2)
290
+	c.Assert(s.ec.unmounts, checker.Equals, 2)
291
+}
292
+
293
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverDeleteContainer(c *check.C) {
294
+	err := s.d.StartWithBusybox()
295
+	c.Assert(err, checker.IsNil)
296
+
297
+	out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest")
298
+	c.Assert(err, checker.IsNil, check.Commentf(out))
299
+
300
+	out, err = s.d.Cmd("rm", "-fv", "vol-test1")
301
+	c.Assert(err, checker.IsNil, check.Commentf(out))
302
+
303
+	c.Assert(s.ec.activations, checker.Equals, 1)
304
+	c.Assert(s.ec.creations, checker.Equals, 1)
305
+	c.Assert(s.ec.removals, checker.Equals, 1)
306
+	c.Assert(s.ec.mounts, checker.Equals, 1)
307
+	c.Assert(s.ec.unmounts, checker.Equals, 1)
308
+}
309
+
310
+func hostVolumePath(name string) string {
311
+	return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
312
+}
313
+
314
+// Make sure a request to use a down driver doesn't block other requests
315
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *check.C) {
316
+	specPath := "/etc/docker/plugins/down-driver.spec"
317
+	err := ioutil.WriteFile(specPath, []byte("tcp://127.0.0.7:9999"), 0644)
318
+	c.Assert(err, check.IsNil)
319
+	defer os.RemoveAll(specPath)
320
+
321
+	chCmd1 := make(chan struct{})
322
+	chCmd2 := make(chan error)
323
+	cmd1 := exec.Command(dockerBinary, "volume", "create", "-d", "down-driver")
324
+	cmd2 := exec.Command(dockerBinary, "volume", "create")
325
+
326
+	c.Assert(cmd1.Start(), checker.IsNil)
327
+	defer cmd1.Process.Kill()
328
+	time.Sleep(100 * time.Millisecond) // ensure API has been called
329
+	c.Assert(cmd2.Start(), checker.IsNil)
330
+
331
+	go func() {
332
+		cmd1.Wait()
333
+		close(chCmd1)
334
+	}()
335
+	go func() {
336
+		chCmd2 <- cmd2.Wait()
337
+	}()
338
+
339
+	select {
340
+	case <-chCmd1:
341
+		cmd2.Process.Kill()
342
+		c.Fatalf("volume create with down driver finished unexpectedly")
343
+	case err := <-chCmd2:
344
+		c.Assert(err, checker.IsNil)
345
+	case <-time.After(5 * time.Second):
346
+		cmd2.Process.Kill()
347
+		c.Fatal("volume creates are blocked by previous create requests when previous driver is down")
348
+	}
349
+}
350
+
351
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyExists(c *check.C) {
352
+	err := s.d.StartWithBusybox()
353
+	c.Assert(err, checker.IsNil)
354
+
355
+	specPath := "/etc/docker/plugins/test-external-volume-driver-retry.spec"
356
+	os.RemoveAll(specPath)
357
+	defer os.RemoveAll(specPath)
358
+
359
+	errchan := make(chan error)
360
+	go func() {
361
+		if out, err := s.d.Cmd("run", "--rm", "--name", "test-data-retry", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver-retry", "busybox:latest"); err != nil {
362
+			errchan <- fmt.Errorf("%v:\n%s", err, out)
363
+		}
364
+		close(errchan)
365
+	}()
366
+	go func() {
367
+		// wait for a retry to occur, then create spec to allow plugin to register
368
+		time.Sleep(2000 * time.Millisecond)
369
+		// no need to check for an error here since it will get picked up by the timeout later
370
+		ioutil.WriteFile(specPath, []byte(s.server.URL), 0644)
371
+	}()
372
+
373
+	select {
374
+	case err := <-errchan:
375
+		c.Assert(err, checker.IsNil)
376
+	case <-time.After(8 * time.Second):
377
+		c.Fatal("volume creates fail when plugin not immediately available")
378
+	}
379
+
380
+	_, err = s.d.Cmd("volume", "rm", "external-volume-test")
381
+	c.Assert(err, checker.IsNil)
382
+
383
+	c.Assert(s.ec.activations, checker.Equals, 1)
384
+	c.Assert(s.ec.creations, checker.Equals, 1)
385
+	c.Assert(s.ec.removals, checker.Equals, 1)
386
+	c.Assert(s.ec.mounts, checker.Equals, 1)
387
+	c.Assert(s.ec.unmounts, checker.Equals, 1)
388
+}
389
+
390
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c *check.C) {
391
+	dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "foo")
392
+	dockerCmd(c, "run", "-d", "--name", "testing", "-v", "foo:/bar", "busybox", "top")
393
+
394
+	var mounts []struct {
395
+		Name   string
396
+		Driver string
397
+	}
398
+	out := inspectFieldJSON(c, "testing", "Mounts")
399
+	c.Assert(json.NewDecoder(strings.NewReader(out)).Decode(&mounts), checker.IsNil)
400
+	c.Assert(len(mounts), checker.Equals, 1, check.Commentf(out))
401
+	c.Assert(mounts[0].Name, checker.Equals, "foo")
402
+	c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver")
403
+}
404
+
405
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverList(c *check.C) {
406
+	dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc3")
407
+	out, _ := dockerCmd(c, "volume", "ls")
408
+	ls := strings.Split(strings.TrimSpace(out), "\n")
409
+	c.Assert(len(ls), check.Equals, 2, check.Commentf("\n%s", out))
410
+
411
+	vol := strings.Fields(ls[len(ls)-1])
412
+	c.Assert(len(vol), check.Equals, 2, check.Commentf("%v", vol))
413
+	c.Assert(vol[0], check.Equals, "test-external-volume-driver")
414
+	c.Assert(vol[1], check.Equals, "abc3")
415
+
416
+	c.Assert(s.ec.lists, check.Equals, 1)
417
+}
418
+
419
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGet(c *check.C) {
420
+	out, _, err := dockerCmdWithError("volume", "inspect", "dummy")
421
+	c.Assert(err, check.NotNil, check.Commentf(out))
422
+	c.Assert(s.ec.gets, check.Equals, 1)
423
+	c.Assert(out, checker.Contains, "No such volume")
424
+
425
+	dockerCmd(c, "volume", "create", "--name", "test", "-d", "test-external-volume-driver")
426
+	out, _ = dockerCmd(c, "volume", "inspect", "test")
427
+
428
+	type vol struct {
429
+		Status map[string]string
430
+	}
431
+	var st []vol
432
+
433
+	c.Assert(json.Unmarshal([]byte(out), &st), checker.IsNil)
434
+	c.Assert(st, checker.HasLen, 1)
435
+	c.Assert(st[0].Status, checker.HasLen, 1, check.Commentf("%v", st[0]))
436
+	c.Assert(st[0].Status["Hello"], checker.Equals, "world", check.Commentf("%v", st[0].Status))
437
+}
438
+
439
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverWithDaemnRestart(c *check.C) {
440
+	dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc1")
441
+	err := s.d.Restart()
442
+	c.Assert(err, checker.IsNil)
443
+
444
+	dockerCmd(c, "run", "--name=test", "-v", "abc1:/foo", "busybox", "true")
445
+	var mounts []types.MountPoint
446
+	inspectFieldAndMarshall(c, "test", "Mounts", &mounts)
447
+	c.Assert(mounts, checker.HasLen, 1)
448
+	c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver")
449
+}
450
+
451
+// Ensures that the daemon handles when the plugin responds to a `Get` request with a null volume and a null error.
452
+// Prior the daemon would panic in this scenario.
453
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGetEmptyResponse(c *check.C) {
454
+	dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc2", "--opt", "ninja=1")
455
+	out, _, err := dockerCmdWithError("volume", "inspect", "abc2")
456
+	c.Assert(err, checker.NotNil, check.Commentf(out))
457
+	c.Assert(out, checker.Contains, "No such volume")
458
+}
459
+
460
+// Ensure only cached paths are used in volume list to prevent N+1 calls to `VolumeDriver.Path`
461
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C) {
462
+	c.Assert(s.d.Start(), checker.IsNil)
463
+	c.Assert(s.ec.paths, checker.Equals, 0)
464
+
465
+	out, err := s.d.Cmd("volume", "create", "--name=test", "--driver=test-external-volume-driver")
466
+	c.Assert(err, checker.IsNil, check.Commentf(out))
467
+	c.Assert(s.ec.paths, checker.Equals, 1)
468
+
469
+	out, err = s.d.Cmd("volume", "ls")
470
+	c.Assert(err, checker.IsNil, check.Commentf(out))
471
+	c.Assert(s.ec.paths, checker.Equals, 1)
472
+
473
+	out, err = s.d.Cmd("volume", "inspect", "--format='{{.Mountpoint}}'", "test")
474
+	c.Assert(err, checker.IsNil, check.Commentf(out))
475
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
476
+	c.Assert(s.ec.paths, checker.Equals, 1)
477
+}
0 478
deleted file mode 100644
... ...
@@ -1,462 +0,0 @@
1
-// +build !windows
2
-
3
-package main
4
-
5
-import (
6
-	"encoding/json"
7
-	"fmt"
8
-	"io"
9
-	"io/ioutil"
10
-	"net/http"
11
-	"net/http/httptest"
12
-	"os"
13
-	"os/exec"
14
-	"path/filepath"
15
-	"strings"
16
-	"time"
17
-
18
-	"github.com/docker/docker/pkg/integration/checker"
19
-	"github.com/docker/engine-api/types"
20
-	"github.com/go-check/check"
21
-)
22
-
23
-func init() {
24
-	check.Suite(&DockerExternalVolumeSuite{
25
-		ds: &DockerSuite{},
26
-	})
27
-}
28
-
29
-type eventCounter struct {
30
-	activations int
31
-	creations   int
32
-	removals    int
33
-	mounts      int
34
-	unmounts    int
35
-	paths       int
36
-	lists       int
37
-	gets        int
38
-}
39
-
40
-type DockerExternalVolumeSuite struct {
41
-	server *httptest.Server
42
-	ds     *DockerSuite
43
-	d      *Daemon
44
-	ec     *eventCounter
45
-}
46
-
47
-func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
48
-	s.d = NewDaemon(c)
49
-	s.ec = &eventCounter{}
50
-}
51
-
52
-func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {
53
-	s.d.Stop()
54
-	s.ds.TearDownTest(c)
55
-}
56
-
57
-func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
58
-	mux := http.NewServeMux()
59
-	s.server = httptest.NewServer(mux)
60
-
61
-	type pluginRequest struct {
62
-		Name string
63
-		Opts map[string]string
64
-	}
65
-
66
-	type pluginResp struct {
67
-		Mountpoint string `json:",omitempty"`
68
-		Err        string `json:",omitempty"`
69
-	}
70
-
71
-	type vol struct {
72
-		Name       string
73
-		Mountpoint string
74
-		Ninja      bool // hack used to trigger an null volume return on `Get`
75
-	}
76
-	var volList []vol
77
-
78
-	read := func(b io.ReadCloser) (pluginRequest, error) {
79
-		defer b.Close()
80
-		var pr pluginRequest
81
-		if err := json.NewDecoder(b).Decode(&pr); err != nil {
82
-			return pr, err
83
-		}
84
-		return pr, nil
85
-	}
86
-
87
-	send := func(w http.ResponseWriter, data interface{}) {
88
-		switch t := data.(type) {
89
-		case error:
90
-			http.Error(w, t.Error(), 500)
91
-		case string:
92
-			w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
93
-			fmt.Fprintln(w, t)
94
-		default:
95
-			w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
96
-			json.NewEncoder(w).Encode(&data)
97
-		}
98
-	}
99
-
100
-	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
101
-		s.ec.activations++
102
-		send(w, `{"Implements": ["VolumeDriver"]}`)
103
-	})
104
-
105
-	mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
106
-		s.ec.creations++
107
-		pr, err := read(r.Body)
108
-		if err != nil {
109
-			send(w, err)
110
-			return
111
-		}
112
-		_, isNinja := pr.Opts["ninja"]
113
-		volList = append(volList, vol{Name: pr.Name, Ninja: isNinja})
114
-		send(w, nil)
115
-	})
116
-
117
-	mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
118
-		s.ec.lists++
119
-		vols := []vol{}
120
-		for _, v := range volList {
121
-			if v.Ninja {
122
-				continue
123
-			}
124
-			vols = append(vols, v)
125
-		}
126
-		send(w, map[string][]vol{"Volumes": vols})
127
-	})
128
-
129
-	mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) {
130
-		s.ec.gets++
131
-		pr, err := read(r.Body)
132
-		if err != nil {
133
-			send(w, err)
134
-			return
135
-		}
136
-
137
-		for _, v := range volList {
138
-			if v.Name == pr.Name {
139
-				if v.Ninja {
140
-					send(w, map[string]vol{})
141
-					return
142
-				}
143
-				v.Mountpoint = hostVolumePath(pr.Name)
144
-				send(w, map[string]vol{"Volume": v})
145
-				return
146
-			}
147
-		}
148
-		send(w, `{"Err": "no such volume"}`)
149
-	})
150
-
151
-	mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
152
-		s.ec.removals++
153
-		pr, err := read(r.Body)
154
-		if err != nil {
155
-			send(w, err)
156
-			return
157
-		}
158
-
159
-		for i, v := range volList {
160
-			if v.Name == pr.Name {
161
-				if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil {
162
-					send(w, &pluginResp{Err: err.Error()})
163
-					return
164
-				}
165
-				volList = append(volList[:i], volList[i+1:]...)
166
-				break
167
-			}
168
-		}
169
-		send(w, nil)
170
-	})
171
-
172
-	mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
173
-		s.ec.paths++
174
-
175
-		pr, err := read(r.Body)
176
-		if err != nil {
177
-			send(w, err)
178
-			return
179
-		}
180
-		p := hostVolumePath(pr.Name)
181
-		send(w, &pluginResp{Mountpoint: p})
182
-	})
183
-
184
-	mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
185
-		s.ec.mounts++
186
-
187
-		pr, err := read(r.Body)
188
-		if err != nil {
189
-			send(w, err)
190
-			return
191
-		}
192
-
193
-		p := hostVolumePath(pr.Name)
194
-		if err := os.MkdirAll(p, 0755); err != nil {
195
-			send(w, &pluginResp{Err: err.Error()})
196
-			return
197
-		}
198
-
199
-		if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil {
200
-			send(w, err)
201
-			return
202
-		}
203
-
204
-		send(w, &pluginResp{Mountpoint: p})
205
-	})
206
-
207
-	mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
208
-		s.ec.unmounts++
209
-
210
-		_, err := read(r.Body)
211
-		if err != nil {
212
-			send(w, err)
213
-			return
214
-		}
215
-
216
-		send(w, nil)
217
-	})
218
-
219
-	err := os.MkdirAll("/etc/docker/plugins", 0755)
220
-	c.Assert(err, checker.IsNil)
221
-
222
-	err = ioutil.WriteFile("/etc/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644)
223
-	c.Assert(err, checker.IsNil)
224
-}
225
-
226
-func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
227
-	s.server.Close()
228
-
229
-	err := os.RemoveAll("/etc/docker/plugins")
230
-	c.Assert(err, checker.IsNil)
231
-}
232
-
233
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) {
234
-	err := s.d.StartWithBusybox()
235
-	c.Assert(err, checker.IsNil)
236
-
237
-	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")
238
-	c.Assert(err, checker.IsNil, check.Commentf(out))
239
-	c.Assert(out, checker.Contains, s.server.URL)
240
-
241
-	_, err = s.d.Cmd("volume", "rm", "external-volume-test")
242
-	c.Assert(err, checker.IsNil)
243
-
244
-	p := hostVolumePath("external-volume-test")
245
-	_, err = os.Lstat(p)
246
-	c.Assert(err, checker.NotNil)
247
-	c.Assert(os.IsNotExist(err), checker.True, check.Commentf("Expected volume path in host to not exist: %s, %v\n", p, err))
248
-
249
-	c.Assert(s.ec.activations, checker.Equals, 1)
250
-	c.Assert(s.ec.creations, checker.Equals, 1)
251
-	c.Assert(s.ec.removals, checker.Equals, 1)
252
-	c.Assert(s.ec.mounts, checker.Equals, 1)
253
-	c.Assert(s.ec.unmounts, checker.Equals, 1)
254
-}
255
-
256
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnnamed(c *check.C) {
257
-	err := s.d.StartWithBusybox()
258
-	c.Assert(err, checker.IsNil)
259
-
260
-	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")
261
-	c.Assert(err, checker.IsNil, check.Commentf(out))
262
-	c.Assert(out, checker.Contains, s.server.URL)
263
-
264
-	c.Assert(s.ec.activations, checker.Equals, 1)
265
-	c.Assert(s.ec.creations, checker.Equals, 1)
266
-	c.Assert(s.ec.removals, checker.Equals, 1)
267
-	c.Assert(s.ec.mounts, checker.Equals, 1)
268
-	c.Assert(s.ec.unmounts, checker.Equals, 1)
269
-}
270
-
271
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverVolumesFrom(c *check.C) {
272
-	err := s.d.StartWithBusybox()
273
-	c.Assert(err, checker.IsNil)
274
-
275
-	out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest")
276
-	c.Assert(err, checker.IsNil, check.Commentf(out))
277
-
278
-	out, err = s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp")
279
-	c.Assert(err, checker.IsNil, check.Commentf(out))
280
-
281
-	out, err = s.d.Cmd("rm", "-fv", "vol-test1")
282
-	c.Assert(err, checker.IsNil, check.Commentf(out))
283
-
284
-	c.Assert(s.ec.activations, checker.Equals, 1)
285
-	c.Assert(s.ec.creations, checker.Equals, 1)
286
-	c.Assert(s.ec.removals, checker.Equals, 1)
287
-	c.Assert(s.ec.mounts, checker.Equals, 2)
288
-	c.Assert(s.ec.unmounts, checker.Equals, 2)
289
-}
290
-
291
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverDeleteContainer(c *check.C) {
292
-	err := s.d.StartWithBusybox()
293
-	c.Assert(err, checker.IsNil)
294
-
295
-	out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest")
296
-	c.Assert(err, checker.IsNil, check.Commentf(out))
297
-
298
-	out, err = s.d.Cmd("rm", "-fv", "vol-test1")
299
-	c.Assert(err, checker.IsNil, check.Commentf(out))
300
-
301
-	c.Assert(s.ec.activations, checker.Equals, 1)
302
-	c.Assert(s.ec.creations, checker.Equals, 1)
303
-	c.Assert(s.ec.removals, checker.Equals, 1)
304
-	c.Assert(s.ec.mounts, checker.Equals, 1)
305
-	c.Assert(s.ec.unmounts, checker.Equals, 1)
306
-}
307
-
308
-func hostVolumePath(name string) string {
309
-	return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
310
-}
311
-
312
-// Make sure a request to use a down driver doesn't block other requests
313
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *check.C) {
314
-	specPath := "/etc/docker/plugins/down-driver.spec"
315
-	err := ioutil.WriteFile(specPath, []byte("tcp://127.0.0.7:9999"), 0644)
316
-	c.Assert(err, check.IsNil)
317
-	defer os.RemoveAll(specPath)
318
-
319
-	chCmd1 := make(chan struct{})
320
-	chCmd2 := make(chan error)
321
-	cmd1 := exec.Command(dockerBinary, "volume", "create", "-d", "down-driver")
322
-	cmd2 := exec.Command(dockerBinary, "volume", "create")
323
-
324
-	c.Assert(cmd1.Start(), checker.IsNil)
325
-	defer cmd1.Process.Kill()
326
-	time.Sleep(100 * time.Millisecond) // ensure API has been called
327
-	c.Assert(cmd2.Start(), checker.IsNil)
328
-
329
-	go func() {
330
-		cmd1.Wait()
331
-		close(chCmd1)
332
-	}()
333
-	go func() {
334
-		chCmd2 <- cmd2.Wait()
335
-	}()
336
-
337
-	select {
338
-	case <-chCmd1:
339
-		cmd2.Process.Kill()
340
-		c.Fatalf("volume create with down driver finished unexpectedly")
341
-	case err := <-chCmd2:
342
-		c.Assert(err, checker.IsNil)
343
-	case <-time.After(5 * time.Second):
344
-		cmd2.Process.Kill()
345
-		c.Fatal("volume creates are blocked by previous create requests when previous driver is down")
346
-	}
347
-}
348
-
349
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyExists(c *check.C) {
350
-	err := s.d.StartWithBusybox()
351
-	c.Assert(err, checker.IsNil)
352
-
353
-	specPath := "/etc/docker/plugins/test-external-volume-driver-retry.spec"
354
-	os.RemoveAll(specPath)
355
-	defer os.RemoveAll(specPath)
356
-
357
-	errchan := make(chan error)
358
-	go func() {
359
-		if out, err := s.d.Cmd("run", "--rm", "--name", "test-data-retry", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver-retry", "busybox:latest"); err != nil {
360
-			errchan <- fmt.Errorf("%v:\n%s", err, out)
361
-		}
362
-		close(errchan)
363
-	}()
364
-	go func() {
365
-		// wait for a retry to occur, then create spec to allow plugin to register
366
-		time.Sleep(2000 * time.Millisecond)
367
-		// no need to check for an error here since it will get picked up by the timeout later
368
-		ioutil.WriteFile(specPath, []byte(s.server.URL), 0644)
369
-	}()
370
-
371
-	select {
372
-	case err := <-errchan:
373
-		c.Assert(err, checker.IsNil)
374
-	case <-time.After(8 * time.Second):
375
-		c.Fatal("volume creates fail when plugin not immediately available")
376
-	}
377
-
378
-	_, err = s.d.Cmd("volume", "rm", "external-volume-test")
379
-	c.Assert(err, checker.IsNil)
380
-
381
-	c.Assert(s.ec.activations, checker.Equals, 1)
382
-	c.Assert(s.ec.creations, checker.Equals, 1)
383
-	c.Assert(s.ec.removals, checker.Equals, 1)
384
-	c.Assert(s.ec.mounts, checker.Equals, 1)
385
-	c.Assert(s.ec.unmounts, checker.Equals, 1)
386
-}
387
-
388
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c *check.C) {
389
-	dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "foo")
390
-	dockerCmd(c, "run", "-d", "--name", "testing", "-v", "foo:/bar", "busybox", "top")
391
-
392
-	var mounts []struct {
393
-		Name   string
394
-		Driver string
395
-	}
396
-	out := inspectFieldJSON(c, "testing", "Mounts")
397
-	c.Assert(json.NewDecoder(strings.NewReader(out)).Decode(&mounts), checker.IsNil)
398
-	c.Assert(len(mounts), checker.Equals, 1, check.Commentf(out))
399
-	c.Assert(mounts[0].Name, checker.Equals, "foo")
400
-	c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver")
401
-}
402
-
403
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverList(c *check.C) {
404
-	dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc3")
405
-	out, _ := dockerCmd(c, "volume", "ls")
406
-	ls := strings.Split(strings.TrimSpace(out), "\n")
407
-	c.Assert(len(ls), check.Equals, 2, check.Commentf("\n%s", out))
408
-
409
-	vol := strings.Fields(ls[len(ls)-1])
410
-	c.Assert(len(vol), check.Equals, 2, check.Commentf("%v", vol))
411
-	c.Assert(vol[0], check.Equals, "test-external-volume-driver")
412
-	c.Assert(vol[1], check.Equals, "abc3")
413
-
414
-	c.Assert(s.ec.lists, check.Equals, 1)
415
-}
416
-
417
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGet(c *check.C) {
418
-	out, _, err := dockerCmdWithError("volume", "inspect", "dummy")
419
-	c.Assert(err, check.NotNil, check.Commentf(out))
420
-	c.Assert(s.ec.gets, check.Equals, 1)
421
-	c.Assert(out, checker.Contains, "No such volume")
422
-}
423
-
424
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverWithDaemnRestart(c *check.C) {
425
-	dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc1")
426
-	err := s.d.Restart()
427
-	c.Assert(err, checker.IsNil)
428
-
429
-	dockerCmd(c, "run", "--name=test", "-v", "abc1:/foo", "busybox", "true")
430
-	var mounts []types.MountPoint
431
-	inspectFieldAndMarshall(c, "test", "Mounts", &mounts)
432
-	c.Assert(mounts, checker.HasLen, 1)
433
-	c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver")
434
-}
435
-
436
-// Ensures that the daemon handles when the plugin responds to a `Get` request with a null volume and a null error.
437
-// Prior the daemon would panic in this scenario.
438
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGetEmptyResponse(c *check.C) {
439
-	dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc2", "--opt", "ninja=1")
440
-	out, _, err := dockerCmdWithError("volume", "inspect", "abc2")
441
-	c.Assert(err, checker.NotNil, check.Commentf(out))
442
-	c.Assert(out, checker.Contains, "No such volume")
443
-}
444
-
445
-// Ensure only cached paths are used in volume list to prevent N+1 calls to `VolumeDriver.Path`
446
-func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C) {
447
-	c.Assert(s.d.Start(), checker.IsNil)
448
-	c.Assert(s.ec.paths, checker.Equals, 0)
449
-
450
-	out, err := s.d.Cmd("volume", "create", "--name=test", "--driver=test-external-volume-driver")
451
-	c.Assert(err, checker.IsNil, check.Commentf(out))
452
-	c.Assert(s.ec.paths, checker.Equals, 1)
453
-
454
-	out, err = s.d.Cmd("volume", "ls")
455
-	c.Assert(err, checker.IsNil, check.Commentf(out))
456
-	c.Assert(s.ec.paths, checker.Equals, 1)
457
-
458
-	out, err = s.d.Cmd("volume", "inspect", "--format='{{.Mountpoint}}'", "test")
459
-	c.Assert(err, checker.IsNil, check.Commentf(out))
460
-	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
461
-	c.Assert(s.ec.paths, checker.Equals, 1)
462
-}
... ...
@@ -64,6 +64,7 @@ func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
64 64
 		name:       v.Name,
65 65
 		driverName: a.Name(),
66 66
 		eMount:     v.Mountpoint,
67
+		status:     v.Status,
67 68
 	}, nil
68 69
 }
69 70
 
... ...
@@ -72,11 +73,13 @@ type volumeAdapter struct {
72 72
 	name       string
73 73
 	driverName string
74 74
 	eMount     string // ephemeral host volume path
75
+	status     map[string]interface{}
75 76
 }
76 77
 
77 78
 type proxyVolume struct {
78 79
 	Name       string
79 80
 	Mountpoint string
81
+	Status     map[string]interface{}
80 82
 }
81 83
 
82 84
 func (a *volumeAdapter) Name() string {
... ...
@@ -111,3 +114,11 @@ func (a *volumeAdapter) Unmount() error {
111 111
 	}
112 112
 	return err
113 113
 }
114
+
115
+func (a *volumeAdapter) Status() map[string]interface{} {
116
+	out := make(map[string]interface{}, len(a.status))
117
+	for k, v := range a.status {
118
+		out[k] = v
119
+	}
120
+	return out
121
+}
... ...
@@ -328,3 +328,7 @@ func validateOpts(opts map[string]string) error {
328 328
 	}
329 329
 	return nil
330 330
 }
331
+
332
+func (v *localVolume) Status() map[string]interface{} {
333
+	return nil
334
+}
... ...
@@ -24,6 +24,9 @@ func (NoopVolume) Mount() (string, error) { return "noop", nil }
24 24
 // Unmount unmounts the volume from the container
25 25
 func (NoopVolume) Unmount() error { return nil }
26 26
 
27
+// Status proivdes low-level details about the volume
28
+func (NoopVolume) Status() map[string]interface{} { return nil }
29
+
27 30
 // FakeVolume is a fake volume with a random name
28 31
 type FakeVolume struct {
29 32
 	name       string
... ...
@@ -50,6 +53,9 @@ func (FakeVolume) Mount() (string, error) { return "fake", nil }
50 50
 // Unmount unmounts the volume from the container
51 51
 func (FakeVolume) Unmount() error { return nil }
52 52
 
53
+// Status proivdes low-level details about the volume
54
+func (FakeVolume) Status() map[string]interface{} { return nil }
55
+
53 56
 // FakeDriver is a driver that generates fake volumes
54 57
 type FakeDriver struct {
55 58
 	name string
... ...
@@ -41,6 +41,8 @@ type Volume interface {
41 41
 	Mount() (string, error)
42 42
 	// Unmount unmounts the volume when it is no longer in use.
43 43
 	Unmount() error
44
+	// Status returns low-level status information about a volume
45
+	Status() map[string]interface{}
44 46
 }
45 47
 
46 48
 // MountPoint is the intersection point between a volume and a container. It