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