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