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