github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/integration-cli/docker_cli_start_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/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 } 63 64 type pluginResp struct { 65 Mountpoint string `json:",omitempty"` 66 Err string `json:",omitempty"` 67 } 68 69 type vol struct { 70 Name string 71 Mountpoint string 72 } 73 var volList []vol 74 75 read := func(b io.ReadCloser) (pluginRequest, error) { 76 defer b.Close() 77 var pr pluginRequest 78 if err := json.NewDecoder(b).Decode(&pr); err != nil { 79 return pr, err 80 } 81 return pr, nil 82 } 83 84 send := func(w http.ResponseWriter, data interface{}) { 85 switch t := data.(type) { 86 case error: 87 http.Error(w, t.Error(), 500) 88 case string: 89 w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") 90 fmt.Fprintln(w, t) 91 default: 92 w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") 93 json.NewEncoder(w).Encode(&data) 94 } 95 } 96 97 mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { 98 s.ec.activations++ 99 send(w, `{"Implements": ["VolumeDriver"]}`) 100 }) 101 102 mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) { 103 s.ec.creations++ 104 pr, err := read(r.Body) 105 if err != nil { 106 send(w, err) 107 return 108 } 109 volList = append(volList, vol{Name: pr.Name}) 110 send(w, nil) 111 }) 112 113 mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) { 114 s.ec.lists++ 115 send(w, map[string][]vol{"Volumes": volList}) 116 }) 117 118 mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) { 119 s.ec.gets++ 120 pr, err := read(r.Body) 121 if err != nil { 122 send(w, err) 123 return 124 } 125 126 for _, v := range volList { 127 if v.Name == pr.Name { 128 v.Mountpoint = hostVolumePath(pr.Name) 129 send(w, map[string]vol{"Volume": v}) 130 return 131 } 132 } 133 send(w, `{"Err": "no such volume"}`) 134 }) 135 136 mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) { 137 s.ec.removals++ 138 pr, err := read(r.Body) 139 if err != nil { 140 send(w, err) 141 return 142 } 143 144 if err := os.RemoveAll(hostVolumePath(pr.Name)); err != nil { 145 send(w, &pluginResp{Err: err.Error()}) 146 return 147 } 148 149 for i, v := range volList { 150 if v.Name == pr.Name { 151 if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil { 152 send(w, fmt.Sprintf(`{"Err": "%v"}`, err)) 153 return 154 } 155 volList = append(volList[:i], volList[i+1:]...) 156 break 157 } 158 } 159 send(w, nil) 160 }) 161 162 mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) { 163 s.ec.paths++ 164 165 pr, err := read(r.Body) 166 if err != nil { 167 send(w, err) 168 return 169 } 170 p := hostVolumePath(pr.Name) 171 send(w, &pluginResp{Mountpoint: p}) 172 }) 173 174 mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) { 175 s.ec.mounts++ 176 177 pr, err := read(r.Body) 178 if err != nil { 179 send(w, err) 180 return 181 } 182 183 p := hostVolumePath(pr.Name) 184 if err := os.MkdirAll(p, 0755); err != nil { 185 send(w, &pluginResp{Err: err.Error()}) 186 return 187 } 188 189 if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil { 190 send(w, err) 191 return 192 } 193 194 send(w, &pluginResp{Mountpoint: p}) 195 }) 196 197 mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) { 198 s.ec.unmounts++ 199 200 _, err := read(r.Body) 201 if err != nil { 202 send(w, err) 203 return 204 } 205 206 send(w, nil) 207 }) 208 209 err := os.MkdirAll("/etc/docker/plugins", 0755) 210 c.Assert(err, checker.IsNil) 211 212 err = ioutil.WriteFile("/etc/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644) 213 c.Assert(err, checker.IsNil) 214 } 215 216 func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) { 217 s.server.Close() 218 219 err := os.RemoveAll("/etc/docker/plugins") 220 c.Assert(err, checker.IsNil) 221 } 222 223 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) { 224 err := s.d.StartWithBusybox() 225 c.Assert(err, checker.IsNil) 226 227 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") 228 c.Assert(err, checker.IsNil, check.Commentf(out)) 229 c.Assert(out, checker.Contains, s.server.URL) 230 231 p := hostVolumePath("external-volume-test") 232 _, err = os.Lstat(p) 233 c.Assert(err, checker.NotNil) 234 c.Assert(os.IsNotExist(err), checker.True, check.Commentf("Expected volume path in host to not exist: %s, %v\n", p, err)) 235 236 c.Assert(s.ec.activations, checker.Equals, 1) 237 c.Assert(s.ec.creations, checker.Equals, 1) 238 c.Assert(s.ec.removals, checker.Equals, 1) 239 c.Assert(s.ec.mounts, checker.Equals, 1) 240 c.Assert(s.ec.unmounts, checker.Equals, 1) 241 } 242 243 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnnamed(c *check.C) { 244 err := s.d.StartWithBusybox() 245 c.Assert(err, checker.IsNil) 246 247 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") 248 c.Assert(err, checker.IsNil, check.Commentf(out)) 249 c.Assert(out, checker.Contains, s.server.URL) 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) TestExternalVolumeDriverVolumesFrom(c *check.C) { 259 err := s.d.StartWithBusybox() 260 c.Assert(err, checker.IsNil) 261 262 out, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest") 263 c.Assert(err, checker.IsNil, check.Commentf(out)) 264 265 out, err = s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp") 266 c.Assert(err, checker.IsNil, check.Commentf(out)) 267 268 out, err = s.d.Cmd("rm", "-fv", "vol-test1") 269 c.Assert(err, checker.IsNil, check.Commentf(out)) 270 271 c.Assert(s.ec.activations, checker.Equals, 1) 272 c.Assert(s.ec.creations, checker.Equals, 1) 273 c.Assert(s.ec.removals, checker.Equals, 1) 274 c.Assert(s.ec.mounts, checker.Equals, 2) 275 c.Assert(s.ec.unmounts, checker.Equals, 2) 276 } 277 278 func (s DockerExternalVolumeSuite) TestExternalVolumeDriverDeleteContainer(c *check.C) { 279 err := s.d.StartWithBusybox() 280 c.Assert(err, checker.IsNil) 281 282 out, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest") 283 c.Assert(err, checker.IsNil, check.Commentf(out)) 284 285 out, err = s.d.Cmd("rm", "-fv", "vol-test1") 286 c.Assert(err, checker.IsNil, check.Commentf(out)) 287 288 c.Assert(s.ec.activations, checker.Equals, 1) 289 c.Assert(s.ec.creations, checker.Equals, 1) 290 c.Assert(s.ec.removals, checker.Equals, 1) 291 c.Assert(s.ec.mounts, checker.Equals, 1) 292 c.Assert(s.ec.unmounts, checker.Equals, 1) 293 } 294 295 func hostVolumePath(name string) string { 296 return fmt.Sprintf("/var/lib/docker/volumes/%s", name) 297 } 298 299 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamedCheckBindLocalVolume(c *check.C) { 300 err := s.d.StartWithBusybox() 301 c.Assert(err, checker.IsNil) 302 303 expected := s.server.URL 304 dockerfile := fmt.Sprintf(`FROM busybox:latest 305 RUN mkdir /nobindthenlocalvol 306 RUN echo %s > /nobindthenlocalvol/test 307 VOLUME ["/nobindthenlocalvol"]`, expected) 308 309 img := "test-checkbindlocalvolume" 310 311 _, err = buildImageWithOutInDamon(s.d.sock(), img, dockerfile, true) 312 c.Assert(err, checker.IsNil) 313 314 out, err := s.d.Cmd("run", "--rm", "--name", "test-data-nobind", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", img, "cat", "/nobindthenlocalvol/test") 315 c.Assert(err, checker.IsNil) 316 317 c.Assert(out, checker.Contains, expected) 318 319 c.Assert(s.ec.activations, checker.Equals, 1) 320 c.Assert(s.ec.creations, checker.Equals, 1) 321 c.Assert(s.ec.removals, checker.Equals, 1) 322 c.Assert(s.ec.mounts, checker.Equals, 1) 323 c.Assert(s.ec.unmounts, checker.Equals, 1) 324 } 325 326 // Make sure a request to use a down driver doesn't block other requests 327 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *check.C) { 328 specPath := "/etc/docker/plugins/down-driver.spec" 329 err := ioutil.WriteFile(specPath, []byte("tcp://127.0.0.7:9999"), 0644) 330 c.Assert(err, check.IsNil) 331 defer os.RemoveAll(specPath) 332 333 chCmd1 := make(chan struct{}) 334 chCmd2 := make(chan error) 335 cmd1 := exec.Command(dockerBinary, "volume", "create", "-d", "down-driver") 336 cmd2 := exec.Command(dockerBinary, "volume", "create") 337 338 c.Assert(cmd1.Start(), checker.IsNil) 339 defer cmd1.Process.Kill() 340 time.Sleep(100 * time.Millisecond) // ensure API has been called 341 c.Assert(cmd2.Start(), checker.IsNil) 342 343 go func() { 344 cmd1.Wait() 345 close(chCmd1) 346 }() 347 go func() { 348 chCmd2 <- cmd2.Wait() 349 }() 350 351 select { 352 case <-chCmd1: 353 cmd2.Process.Kill() 354 c.Fatalf("volume create with down driver finished unexpectedly") 355 case err := <-chCmd2: 356 c.Assert(err, checker.IsNil) 357 case <-time.After(5 * time.Second): 358 cmd2.Process.Kill() 359 c.Fatal("volume creates are blocked by previous create requests when previous driver is down") 360 } 361 } 362 363 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyExists(c *check.C) { 364 err := s.d.StartWithBusybox() 365 c.Assert(err, checker.IsNil) 366 367 specPath := "/etc/docker/plugins/test-external-volume-driver-retry.spec" 368 os.RemoveAll(specPath) 369 defer os.RemoveAll(specPath) 370 371 errchan := make(chan error) 372 go func() { 373 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 { 374 errchan <- fmt.Errorf("%v:\n%s", err, out) 375 } 376 close(errchan) 377 }() 378 go func() { 379 // wait for a retry to occur, then create spec to allow plugin to register 380 time.Sleep(2000 * time.Millisecond) 381 // no need to check for an error here since it will get picked up by the timeout later 382 ioutil.WriteFile(specPath, []byte(s.server.URL), 0644) 383 }() 384 385 select { 386 case err := <-errchan: 387 c.Assert(err, checker.IsNil) 388 case <-time.After(8 * time.Second): 389 c.Fatal("volume creates fail when plugin not immediately available") 390 } 391 392 c.Assert(s.ec.activations, checker.Equals, 1) 393 c.Assert(s.ec.creations, checker.Equals, 1) 394 c.Assert(s.ec.removals, checker.Equals, 1) 395 c.Assert(s.ec.mounts, checker.Equals, 1) 396 c.Assert(s.ec.unmounts, checker.Equals, 1) 397 } 398 399 func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c *check.C) { 400 dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "foo") 401 dockerCmd(c, "run", "-d", "--name", "testing", "-v", "foo:/bar", "busybox", "top") 402 403 var mounts []struct { 404 Name string 405 Driver string 406 } 407 out, err := inspectFieldJSON("testing", "Mounts") 408 c.Assert(err, checker.IsNil) 409 c.Assert(json.NewDecoder(strings.NewReader(out)).Decode(&mounts), checker.IsNil) 410 c.Assert(len(mounts), checker.Equals, 1, check.Commentf(out)) 411 c.Assert(mounts[0].Name, checker.Equals, "foo") 412 c.Assert(mounts[0].Driver, checker.Equals, "test-external-volume-driver") 413 } 414 415 func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverList(c *check.C) { 416 dockerCmd(c, "volume", "create", "-d", "test-external-volume-driver", "--name", "abc") 417 out, _ := dockerCmd(c, "volume", "ls") 418 ls := strings.Split(strings.TrimSpace(out), "\n") 419 c.Assert(len(ls), check.Equals, 2, check.Commentf("\n%s", out)) 420 421 vol := strings.Fields(ls[len(ls)-1]) 422 c.Assert(len(vol), check.Equals, 2, check.Commentf("%v", vol)) 423 c.Assert(vol[0], check.Equals, "test-external-volume-driver") 424 c.Assert(vol[1], check.Equals, "abc") 425 426 c.Assert(s.ec.lists, check.Equals, 1) 427 } 428 429 func (s *DockerExternalVolumeSuite) TestStartExternalVolumeDriverGet(c *check.C) { 430 out, _, err := dockerCmdWithError("volume", "inspect", "dummy") 431 c.Assert(err, check.NotNil, check.Commentf(out)) 432 c.Assert(s.ec.gets, check.Equals, 1) 433 c.Assert(out, checker.Contains, "No such volume") 434 }