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  }