github.com/getong/docker@v1.13.1/integration-cli/docker_cli_external_volume_driver_unix_test.go (about) 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/api/types" 19 "github.com/docker/docker/pkg/integration/checker" 20 "github.com/docker/docker/pkg/stringid" 21 "github.com/docker/docker/volume" 22 "github.com/go-check/check" 23 ) 24 25 const volumePluginName = "test-external-volume-driver" 26 27 func init() { 28 check.Suite(&DockerExternalVolumeSuite{ 29 ds: &DockerSuite{}, 30 }) 31 } 32 33 type eventCounter struct { 34 activations int 35 creations int 36 removals int 37 mounts int 38 unmounts int 39 paths int 40 lists int 41 gets int 42 caps int 43 } 44 45 type DockerExternalVolumeSuite struct { 46 ds *DockerSuite 47 d *Daemon 48 *volumePlugin 49 } 50 51 func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) { 52 s.d = NewDaemon(c) 53 s.ec = &eventCounter{} 54 } 55 56 func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) { 57 s.d.Stop() 58 s.ds.TearDownTest(c) 59 } 60 61 func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) { 62 s.volumePlugin = newVolumePlugin(c, volumePluginName) 63 } 64 65 type volumePlugin struct { 66 ec *eventCounter 67 *httptest.Server 68 vols map[string]vol 69 } 70 71 type vol struct { 72 Name string 73 Mountpoint string 74 Ninja bool // hack used to trigger a null volume return on `Get` 75 Status map[string]interface{} 76 Options map[string]string 77 } 78 79 func (p *volumePlugin) Close() { 80 p.Server.Close() 81 } 82 83 func newVolumePlugin(c *check.C, name string) *volumePlugin { 84 mux := http.NewServeMux() 85 s := &volumePlugin{Server: httptest.NewServer(mux), ec: &eventCounter{}, vols: make(map[string]vol)} 86 87 type pluginRequest struct { 88 Name string 89 Opts map[string]string 90 ID string 91 } 92 93 type pluginResp struct { 94 Mountpoint string `json:",omitempty"` 95 Err string `json:",omitempty"` 96 } 97 98 read := func(b io.ReadCloser) (pluginRequest, error) { 99 defer b.Close() 100 var pr pluginRequest 101 if err := json.NewDecoder(b).Decode(&pr); err != nil { 102 return pr, err 103 } 104 return pr, nil 105 } 106 107 send := func(w http.ResponseWriter, data interface{}) { 108 switch t := data.(type) { 109 case error: 110 http.Error(w, t.Error(), 500) 111 case string: 112 w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") 113 fmt.Fprintln(w, t) 114 default: 115 w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") 116 json.NewEncoder(w).Encode(&data) 117 } 118 } 119 120 mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { 121 s.ec.activations++ 122 send(w, `{"Implements": ["VolumeDriver"]}`) 123 }) 124 125 mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) { 126 s.ec.creations++ 127 pr, err := read(r.Body) 128 if err != nil { 129 send(w, err) 130 return 131 } 132 _, isNinja := pr.Opts["ninja"] 133 status := map[string]interface{}{"Hello": "world"} 134 s.vols[pr.Name] = vol{Name: pr.Name, Ninja: isNinja, Status: status, Options: pr.Opts} 135 send(w, nil) 136 }) 137 138 mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) { 139 s.ec.lists++ 140 vols := make([]vol, 0, len(s.vols)) 141 for _, v := range s.vols { 142 if v.Ninja { 143 continue 144 } 145 vols = append(vols, v) 146 } 147 send(w, map[string][]vol{"Volumes": vols}) 148 }) 149 150 mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) { 151 s.ec.gets++ 152 pr, err := read(r.Body) 153 if err != nil { 154 send(w, err) 155 return 156 } 157 158 v, exists := s.vols[pr.Name] 159 if !exists { 160 send(w, `{"Err": "no such volume"}`) 161 } 162 163 if v.Ninja { 164 send(w, map[string]vol{}) 165 return 166 } 167 168 v.Mountpoint = hostVolumePath(pr.Name) 169 send(w, map[string]vol{"Volume": v}) 170 return 171 }) 172 173 mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) { 174 s.ec.removals++ 175 pr, err := read(r.Body) 176 if err != nil { 177 send(w, err) 178 return 179 } 180 181 v, ok := s.vols[pr.Name] 182 if !ok { 183 send(w, nil) 184 return 185 } 186 187 if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil { 188 send(w, &pluginResp{Err: err.Error()}) 189 return 190 } 191 delete(s.vols, v.Name) 192 send(w, nil) 193 }) 194 195 mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) { 196 s.ec.paths++ 197 198 pr, err := read(r.Body) 199 if err != nil { 200 send(w, err) 201 return 202 } 203 p := hostVolumePath(pr.Name) 204 send(w, &pluginResp{Mountpoint: p}) 205 }) 206 207 mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) { 208 s.ec.mounts++ 209 210 pr, err := read(r.Body) 211 if err != nil { 212 send(w, err) 213 return 214 } 215 216 if v, exists := s.vols[pr.Name]; exists { 217 // Use this to simulate a mount failure 218 if _, exists := v.Options["invalidOption"]; exists { 219 send(w, fmt.Errorf("invalid argument")) 220 return 221 } 222 } 223 224 p := hostVolumePath(pr.Name) 225 if err := os.MkdirAll(p, 0755); err != nil { 226 send(w, &pluginResp{Err: err.Error()}) 227 return 228 } 229 230 if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.Server.URL), 0644); err != nil { 231 send(w, err) 232 return 233 } 234 235 if err := ioutil.WriteFile(filepath.Join(p, "mountID"), []byte(pr.ID), 0644); err != nil { 236 send(w, err) 237 return 238 } 239 240 send(w, &pluginResp{Mountpoint: p}) 241 }) 242 243 mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) { 244 s.ec.unmounts++ 245 246 _, err := read(r.Body) 247 if err != nil { 248 send(w, err) 249 return 250 } 251 252 send(w, nil) 253 }) 254 255 mux.HandleFunc("/VolumeDriver.Capabilities", func(w http.ResponseWriter, r *http.Request) { 256 s.ec.caps++ 257 258 _, err := read(r.Body) 259 if err != nil { 260 send(w, err) 261 return 262 } 263 264 send(w, `{"Capabilities": { "Scope": "global" }}`) 265 }) 266 267 err := os.MkdirAll("/etc/docker/plugins", 0755) 268 c.Assert(err, checker.IsNil) 269 270 err = ioutil.WriteFile("/etc/docker/plugins/"+name+".spec", []byte(s.Server.URL), 0644) 271 c.Assert(err, checker.IsNil) 272 return s 273 } 274 275 func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) { 276 s.volumePlugin.Close() 277 278 err := os.RemoveAll("/etc/docker/plugins") 279 c.Assert(err, checker.IsNil) 280 } 281 282 func (s *DockerExternalVolumeSuite) TestVolumeCLICreateOptionConflict(c *check.C) { 283 dockerCmd(c, "volume", "create", "test") 284 285 out, _, err := dockerCmdWithError("volume", "create", "test", "--driver", volumePluginName) 286 c.Assert(err, check.NotNil, check.Commentf("volume create exception name already in use with another driver")) 287 c.Assert(out, checker.Contains, "A volume named test already exists") 288 289 out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Driver }}", "test") 290 _, _, err = dockerCmdWithError("volume", "create", "test", "--driver", strings.TrimSpace(out)) 291 c.Assert(err, check.IsNil) 292 293 // make sure hidden --name option conflicts with positional arg name 294 out, _, err = dockerCmdWithError("volume", "create", "--name", "test2", "test2") 295 c.Assert(err, check.NotNil, check.Commentf("Conflicting options: either specify --name or provide positional arg, not both")) 296 } 297 298 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) { 299 err := s.d.StartWithBusybox() 300 c.Assert(err, checker.IsNil) 301 302 out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", volumePluginName, "busybox:latest", "cat", "/tmp/external-volume-test/test") 303 c.Assert(err, checker.IsNil, check.Commentf(out)) 304 c.Assert(out, checker.Contains, s.Server.URL) 305 306 _, err = s.d.Cmd("volume", "rm", "external-volume-test") 307 c.Assert(err, checker.IsNil) 308 309 p := hostVolumePath("external-volume-test") 310 _, err = os.Lstat(p) 311 c.Assert(err, checker.NotNil) 312 c.Assert(os.IsNotExist(err), checker.True, check.Commentf("Expected volume path in host to not exist: %s, %v\n", p, err)) 313 314 c.Assert(s.ec.activations, checker.Equals, 1) 315 c.Assert(s.ec.creations, checker.Equals, 1) 316 c.Assert(s.ec.removals, checker.Equals, 1) 317 c.Assert(s.ec.mounts, checker.Equals, 1) 318 c.Assert(s.ec.unmounts, checker.Equals, 1) 319 } 320 321 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnnamed(c *check.C) { 322 err := s.d.StartWithBusybox() 323 c.Assert(err, checker.IsNil) 324 325 out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "/tmp/external-volume-test", "--volume-driver", volumePluginName, "busybox:latest", "cat", "/tmp/external-volume-test/test") 326 c.Assert(err, checker.IsNil, check.Commentf(out)) 327 c.Assert(out, checker.Contains, s.Server.URL) 328 329 c.Assert(s.ec.activations, checker.Equals, 1) 330 c.Assert(s.ec.creations, checker.Equals, 1) 331 c.Assert(s.ec.removals, checker.Equals, 1) 332 c.Assert(s.ec.mounts, checker.Equals, 1) 333 c.Assert(s.ec.unmounts, checker.Equals, 1) 334 } 335 336 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverVolumesFrom(c *check.C) { 337 err := s.d.StartWithBusybox() 338 c.Assert(err, checker.IsNil) 339 340 out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", volumePluginName, "busybox:latest") 341 c.Assert(err, checker.IsNil, check.Commentf(out)) 342 343 out, err = s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp") 344 c.Assert(err, checker.IsNil, check.Commentf(out)) 345 346 out, err = s.d.Cmd("rm", "-fv", "vol-test1") 347 c.Assert(err, checker.IsNil, check.Commentf(out)) 348 349 c.Assert(s.ec.activations, checker.Equals, 1) 350 c.Assert(s.ec.creations, checker.Equals, 1) 351 c.Assert(s.ec.removals, checker.Equals, 1) 352 c.Assert(s.ec.mounts, checker.Equals, 2) 353 c.Assert(s.ec.unmounts, checker.Equals, 2) 354 } 355 356 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverDeleteContainer(c *check.C) { 357 err := s.d.StartWithBusybox() 358 c.Assert(err, checker.IsNil) 359 360 out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", volumePluginName, "busybox:latest") 361 c.Assert(err, checker.IsNil, check.Commentf(out)) 362 363 out, err = s.d.Cmd("rm", "-fv", "vol-test1") 364 c.Assert(err, checker.IsNil, check.Commentf(out)) 365 366 c.Assert(s.ec.activations, checker.Equals, 1) 367 c.Assert(s.ec.creations, checker.Equals, 1) 368 c.Assert(s.ec.removals, checker.Equals, 1) 369 c.Assert(s.ec.mounts, checker.Equals, 1) 370 c.Assert(s.ec.unmounts, checker.Equals, 1) 371 } 372 373 func hostVolumePath(name string) string { 374 return fmt.Sprintf("/var/lib/docker/volumes/%s", name) 375 } 376 377 // Make sure a request to use a down driver doesn't block other requests 378 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *check.C) { 379 specPath := "/etc/docker/plugins/down-driver.spec" 380 err := ioutil.WriteFile(specPath, []byte("tcp://127.0.0.7:9999"), 0644) 381 c.Assert(err, check.IsNil) 382 defer os.RemoveAll(specPath) 383 384 chCmd1 := make(chan struct{}) 385 chCmd2 := make(chan error) 386 cmd1 := exec.Command(dockerBinary, "volume", "create", "-d", "down-driver") 387 cmd2 := exec.Command(dockerBinary, "volume", "create") 388 389 c.Assert(cmd1.Start(), checker.IsNil) 390 defer cmd1.Process.Kill() 391 time.Sleep(100 * time.Millisecond) // ensure API has been called 392 c.Assert(cmd2.Start(), checker.IsNil) 393 394 go func() { 395 cmd1.Wait() 396 close(chCmd1) 397 }() 398 go func() { 399 chCmd2 <- cmd2.Wait() 400 }() 401 402 select { 403 case <-chCmd1: 404 cmd2.Process.Kill() 405 c.Fatalf("volume create with down driver finished unexpectedly") 406 case err := <-chCmd2: 407 c.Assert(err, checker.IsNil) 408 case <-time.After(5 * time.Second): 409 cmd2.Process.Kill() 410 c.Fatal("volume creates are blocked by previous create requests when previous driver is down") 411 } 412 } 413 414 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyExists(c *check.C) { 415 err := s.d.StartWithBusybox() 416 c.Assert(err, checker.IsNil) 417 418 specPath := "/etc/docker/plugins/test-external-volume-driver-retry.spec" 419 os.RemoveAll(specPath) 420 defer os.RemoveAll(specPath) 421 422 errchan := make(chan error) 423 go func() { 424 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 { 425 errchan <- fmt.Errorf("%v:\n%s", err, out) 426 } 427 close(errchan) 428 }() 429 go func() { 430 // wait for a retry to occur, then create spec to allow plugin to register 431 time.Sleep(2000 * time.Millisecond) 432 // no need to check for an error here since it will get picked up by the timeout later 433 ioutil.WriteFile(specPath, []byte(s.Server.URL), 0644) 434 }() 435 436 select { 437 case err := <-errchan: 438 c.Assert(err, checker.IsNil) 439 case <-time.After(8 * time.Second): 440 c.Fatal("volume creates fail when plugin not immediately available") 441 } 442 443 _, err = s.d.Cmd("volume", "rm", "external-volume-test") 444 c.Assert(err, checker.IsNil) 445 446 c.Assert(s.ec.activations, checker.Equals, 1) 447 c.Assert(s.ec.creations, checker.Equals, 1) 448 c.Assert(s.ec.removals, checker.Equals, 1) 449 c.Assert(s.ec.mounts, checker.Equals, 1) 450 c.Assert(s.ec.unmounts, checker.Equals, 1) 451 } 452 453 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c *check.C) { 454 dockerCmd(c, "volume", "create", "-d", volumePluginName, "foo") 455 dockerCmd(c, "run", "-d", "--name", "testing", "-v", "foo:/bar", "busybox", "top") 456 457 var mounts []struct { 458 Name string 459 Driver string 460 } 461 out := inspectFieldJSON(c, "testing", "Mounts") 462 c.Assert(json.NewDecoder(strings.NewReader(out)).Decode(&mounts), checker.IsNil) 463 c.Assert(len(mounts), checker.Equals, 1, check.Commentf(out)) 464 c.Assert(mounts[0].Name, checker.Equals, "foo") 465 c.Assert(mounts[0].Driver, checker.Equals, volumePluginName) 466 } 467 468 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverList(c *check.C) { 469 dockerCmd(c, "volume", "create", "-d", volumePluginName, "abc3") 470 out, _ := dockerCmd(c, "volume", "ls") 471 ls := strings.Split(strings.TrimSpace(out), "\n") 472 c.Assert(len(ls), check.Equals, 2, check.Commentf("\n%s", out)) 473 474 vol := strings.Fields(ls[len(ls)-1]) 475 c.Assert(len(vol), check.Equals, 2, check.Commentf("%v", vol)) 476 c.Assert(vol[0], check.Equals, volumePluginName) 477 c.Assert(vol[1], check.Equals, "abc3") 478 479 c.Assert(s.ec.lists, check.Equals, 1) 480 } 481 482 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGet(c *check.C) { 483 out, _, err := dockerCmdWithError("volume", "inspect", "dummy") 484 c.Assert(err, check.NotNil, check.Commentf(out)) 485 c.Assert(out, checker.Contains, "No such volume") 486 c.Assert(s.ec.gets, check.Equals, 1) 487 488 dockerCmd(c, "volume", "create", "test", "-d", volumePluginName) 489 out, _ = dockerCmd(c, "volume", "inspect", "test") 490 491 type vol struct { 492 Status map[string]string 493 } 494 var st []vol 495 496 c.Assert(json.Unmarshal([]byte(out), &st), checker.IsNil) 497 c.Assert(st, checker.HasLen, 1) 498 c.Assert(st[0].Status, checker.HasLen, 1, check.Commentf("%v", st[0])) 499 c.Assert(st[0].Status["Hello"], checker.Equals, "world", check.Commentf("%v", st[0].Status)) 500 } 501 502 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverWithDaemonRestart(c *check.C) { 503 dockerCmd(c, "volume", "create", "-d", volumePluginName, "abc1") 504 err := s.d.Restart() 505 c.Assert(err, checker.IsNil) 506 507 dockerCmd(c, "run", "--name=test", "-v", "abc1:/foo", "busybox", "true") 508 var mounts []types.MountPoint 509 inspectFieldAndMarshall(c, "test", "Mounts", &mounts) 510 c.Assert(mounts, checker.HasLen, 1) 511 c.Assert(mounts[0].Driver, checker.Equals, volumePluginName) 512 } 513 514 // Ensures that the daemon handles when the plugin responds to a `Get` request with a null volume and a null error. 515 // Prior the daemon would panic in this scenario. 516 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGetEmptyResponse(c *check.C) { 517 c.Assert(s.d.Start(), checker.IsNil) 518 519 out, err := s.d.Cmd("volume", "create", "-d", volumePluginName, "abc2", "--opt", "ninja=1") 520 c.Assert(err, checker.IsNil, check.Commentf(out)) 521 522 out, err = s.d.Cmd("volume", "inspect", "abc2") 523 c.Assert(err, checker.NotNil, check.Commentf(out)) 524 c.Assert(out, checker.Contains, "No such volume") 525 } 526 527 // Ensure only cached paths are used in volume list to prevent N+1 calls to `VolumeDriver.Path` 528 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C) { 529 c.Assert(s.d.Start(), checker.IsNil) 530 c.Assert(s.ec.paths, checker.Equals, 0) 531 532 out, err := s.d.Cmd("volume", "create", "test", "--driver=test-external-volume-driver") 533 c.Assert(err, checker.IsNil, check.Commentf(out)) 534 c.Assert(s.ec.paths, checker.Equals, 1) 535 536 out, err = s.d.Cmd("volume", "ls") 537 c.Assert(err, checker.IsNil, check.Commentf(out)) 538 c.Assert(s.ec.paths, checker.Equals, 1) 539 540 out, err = s.d.Cmd("volume", "inspect", "--format='{{.Mountpoint}}'", "test") 541 c.Assert(err, checker.IsNil, check.Commentf(out)) 542 c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") 543 c.Assert(s.ec.paths, checker.Equals, 1) 544 } 545 546 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C) { 547 err := s.d.StartWithBusybox() 548 c.Assert(err, checker.IsNil) 549 550 out, err := s.d.Cmd("run", "--rm", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", volumePluginName, "busybox:latest", "cat", "/tmp/external-volume-test/test") 551 c.Assert(err, checker.IsNil, check.Commentf(out)) 552 c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") 553 } 554 555 // Check that VolumeDriver.Capabilities gets called, and only called once 556 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverCapabilities(c *check.C) { 557 c.Assert(s.d.Start(), checker.IsNil) 558 c.Assert(s.ec.caps, checker.Equals, 0) 559 560 for i := 0; i < 3; i++ { 561 out, err := s.d.Cmd("volume", "create", "-d", volumePluginName, fmt.Sprintf("test%d", i)) 562 c.Assert(err, checker.IsNil, check.Commentf(out)) 563 c.Assert(s.ec.caps, checker.Equals, 1) 564 out, err = s.d.Cmd("volume", "inspect", "--format={{.Scope}}", fmt.Sprintf("test%d", i)) 565 c.Assert(err, checker.IsNil) 566 c.Assert(strings.TrimSpace(out), checker.Equals, volume.GlobalScope) 567 } 568 } 569 570 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverOutOfBandDelete(c *check.C) { 571 driverName := stringid.GenerateNonCryptoID() 572 p := newVolumePlugin(c, driverName) 573 defer p.Close() 574 575 c.Assert(s.d.StartWithBusybox(), checker.IsNil) 576 577 out, err := s.d.Cmd("volume", "create", "-d", driverName, "--name", "test") 578 c.Assert(err, checker.IsNil, check.Commentf(out)) 579 580 out, err = s.d.Cmd("volume", "create", "-d", "local", "--name", "test") 581 c.Assert(err, checker.NotNil, check.Commentf(out)) 582 c.Assert(out, checker.Contains, "volume named test already exists") 583 584 // simulate out of band volume deletion on plugin level 585 delete(p.vols, "test") 586 587 // test re-create with same driver 588 out, err = s.d.Cmd("volume", "create", "-d", driverName, "--opt", "foo=bar", "--name", "test") 589 c.Assert(err, checker.IsNil, check.Commentf(out)) 590 out, err = s.d.Cmd("volume", "inspect", "test") 591 c.Assert(err, checker.IsNil, check.Commentf(out)) 592 593 var vs []types.Volume 594 err = json.Unmarshal([]byte(out), &vs) 595 c.Assert(err, checker.IsNil) 596 c.Assert(vs, checker.HasLen, 1) 597 c.Assert(vs[0].Driver, checker.Equals, driverName) 598 c.Assert(vs[0].Options, checker.NotNil) 599 c.Assert(vs[0].Options["foo"], checker.Equals, "bar") 600 c.Assert(vs[0].Driver, checker.Equals, driverName) 601 602 // simulate out of band volume deletion on plugin level 603 delete(p.vols, "test") 604 605 // test create with different driver 606 out, err = s.d.Cmd("volume", "create", "-d", "local", "--name", "test") 607 c.Assert(err, checker.IsNil, check.Commentf(out)) 608 609 out, err = s.d.Cmd("volume", "inspect", "test") 610 c.Assert(err, checker.IsNil, check.Commentf(out)) 611 vs = nil 612 err = json.Unmarshal([]byte(out), &vs) 613 c.Assert(err, checker.IsNil) 614 c.Assert(vs, checker.HasLen, 1) 615 c.Assert(vs[0].Options, checker.HasLen, 0) 616 c.Assert(vs[0].Driver, checker.Equals, "local") 617 } 618 619 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnmountOnMountFail(c *check.C) { 620 c.Assert(s.d.StartWithBusybox(), checker.IsNil) 621 s.d.Cmd("volume", "create", "-d", "test-external-volume-driver", "--opt=invalidOption=1", "--name=testumount") 622 623 out, _ := s.d.Cmd("run", "-v", "testumount:/foo", "busybox", "true") 624 c.Assert(s.ec.unmounts, checker.Equals, 0, check.Commentf(out)) 625 out, _ = s.d.Cmd("run", "-w", "/foo", "-v", "testumount:/foo", "busybox", "true") 626 c.Assert(s.ec.unmounts, checker.Equals, 0, check.Commentf(out)) 627 }