9a2d0bc3ad
Fixes #22564 When an error occurs on mount, there should not be any call later to unmount. This can throw off refcounting in the underlying driver unexpectedly. Consider these two cases: ``` $ docker run -v foo:/bar busybox true ``` ``` $ docker run -v foo:/bar -w /foo busybox true ``` In the first case, if mounting `foo` fails, the volume driver will not get a call to unmount (this is the incorrect behavior). In the second case, the volume driver will not get a call to unmount (correct behavior). This occurs because in the first case, `/bar` does not exist in the container, and as such there is no call to `volume.Mount()` during the `create` phase. It will error out during the `start` phase. In the second case `/bar` is created before dealing with the volume because of the `-w`. Because of this, when the volume is being setup docker will try to copy the image path contents in the volume, in which case it will attempt to mount the volume and fail. This happens during the `create` phase. This makes it so the container will not be created (or at least fully created) and the user gets the error on `create` instead of `start`. The error handling is different in these two phases. Changed to only send `unmount` if the volume is mounted. While investigating the cause of the reported issue I found some odd behavior in unmount calls so I've cleaned those up a bit here as well. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
583 lines
17 KiB
Go
583 lines
17 KiB
Go
// +build !windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/pkg/integration/checker"
|
|
"github.com/docker/docker/pkg/stringid"
|
|
"github.com/docker/docker/volume"
|
|
"github.com/go-check/check"
|
|
)
|
|
|
|
const volumePluginName = "test-external-volume-driver"
|
|
|
|
func init() {
|
|
check.Suite(&DockerExternalVolumeSuite{
|
|
ds: &DockerSuite{},
|
|
})
|
|
}
|
|
|
|
type eventCounter struct {
|
|
activations int
|
|
creations int
|
|
removals int
|
|
mounts int
|
|
unmounts int
|
|
paths int
|
|
lists int
|
|
gets int
|
|
caps int
|
|
}
|
|
|
|
type DockerExternalVolumeSuite struct {
|
|
ds *DockerSuite
|
|
d *Daemon
|
|
*volumePlugin
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
|
|
s.d = NewDaemon(c)
|
|
s.ec = &eventCounter{}
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {
|
|
s.d.Stop()
|
|
s.ds.TearDownTest(c)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
|
s.volumePlugin = newVolumePlugin(c, volumePluginName)
|
|
}
|
|
|
|
type volumePlugin struct {
|
|
ec *eventCounter
|
|
*httptest.Server
|
|
vols map[string]vol
|
|
}
|
|
|
|
type vol struct {
|
|
Name string
|
|
Mountpoint string
|
|
Ninja bool // hack used to trigger a null volume return on `Get`
|
|
Status map[string]interface{}
|
|
Options map[string]string
|
|
}
|
|
|
|
func (p *volumePlugin) Close() {
|
|
p.Server.Close()
|
|
}
|
|
|
|
func newVolumePlugin(c *check.C, name string) *volumePlugin {
|
|
mux := http.NewServeMux()
|
|
s := &volumePlugin{Server: httptest.NewServer(mux), ec: &eventCounter{}, vols: make(map[string]vol)}
|
|
|
|
type pluginRequest struct {
|
|
Name string
|
|
Opts map[string]string
|
|
ID string
|
|
}
|
|
|
|
type pluginResp struct {
|
|
Mountpoint string `json:",omitempty"`
|
|
Err string `json:",omitempty"`
|
|
}
|
|
|
|
read := func(b io.ReadCloser) (pluginRequest, error) {
|
|
defer b.Close()
|
|
var pr pluginRequest
|
|
if err := json.NewDecoder(b).Decode(&pr); err != nil {
|
|
return pr, err
|
|
}
|
|
return pr, nil
|
|
}
|
|
|
|
send := func(w http.ResponseWriter, data interface{}) {
|
|
switch t := data.(type) {
|
|
case error:
|
|
http.Error(w, t.Error(), 500)
|
|
case string:
|
|
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
|
fmt.Fprintln(w, t)
|
|
default:
|
|
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
|
|
json.NewEncoder(w).Encode(&data)
|
|
}
|
|
}
|
|
|
|
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.activations++
|
|
send(w, `{"Implements": ["VolumeDriver"]}`)
|
|
})
|
|
|
|
mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.creations++
|
|
pr, err := read(r.Body)
|
|
if err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
_, isNinja := pr.Opts["ninja"]
|
|
status := map[string]interface{}{"Hello": "world"}
|
|
s.vols[pr.Name] = vol{Name: pr.Name, Ninja: isNinja, Status: status, Options: pr.Opts}
|
|
send(w, nil)
|
|
})
|
|
|
|
mux.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.lists++
|
|
vols := make([]vol, 0, len(s.vols))
|
|
for _, v := range s.vols {
|
|
if v.Ninja {
|
|
continue
|
|
}
|
|
vols = append(vols, v)
|
|
}
|
|
send(w, map[string][]vol{"Volumes": vols})
|
|
})
|
|
|
|
mux.HandleFunc("/VolumeDriver.Get", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.gets++
|
|
pr, err := read(r.Body)
|
|
if err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
|
|
v, exists := s.vols[pr.Name]
|
|
if !exists {
|
|
send(w, `{"Err": "no such volume"}`)
|
|
}
|
|
|
|
if v.Ninja {
|
|
send(w, map[string]vol{})
|
|
return
|
|
}
|
|
|
|
v.Mountpoint = hostVolumePath(pr.Name)
|
|
send(w, map[string]vol{"Volume": v})
|
|
return
|
|
})
|
|
|
|
mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.removals++
|
|
pr, err := read(r.Body)
|
|
if err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
|
|
v, ok := s.vols[pr.Name]
|
|
if !ok {
|
|
send(w, nil)
|
|
return
|
|
}
|
|
|
|
if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil {
|
|
send(w, &pluginResp{Err: err.Error()})
|
|
return
|
|
}
|
|
delete(s.vols, v.Name)
|
|
send(w, nil)
|
|
})
|
|
|
|
mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.paths++
|
|
|
|
pr, err := read(r.Body)
|
|
if err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
p := hostVolumePath(pr.Name)
|
|
send(w, &pluginResp{Mountpoint: p})
|
|
})
|
|
|
|
mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.mounts++
|
|
|
|
pr, err := read(r.Body)
|
|
if err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
|
|
if v, exists := s.vols[pr.Name]; exists {
|
|
// Use this to simulate a mount failure
|
|
if _, exists := v.Options["invalidOption"]; exists {
|
|
send(w, fmt.Errorf("invalid argument"))
|
|
return
|
|
}
|
|
}
|
|
|
|
p := hostVolumePath(pr.Name)
|
|
if err := os.MkdirAll(p, 0755); err != nil {
|
|
send(w, &pluginResp{Err: err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.Server.URL), 0644); err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
|
|
if err := ioutil.WriteFile(filepath.Join(p, "mountID"), []byte(pr.ID), 0644); err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
|
|
send(w, &pluginResp{Mountpoint: p})
|
|
})
|
|
|
|
mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.unmounts++
|
|
|
|
_, err := read(r.Body)
|
|
if err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
|
|
send(w, nil)
|
|
})
|
|
|
|
mux.HandleFunc("/VolumeDriver.Capabilities", func(w http.ResponseWriter, r *http.Request) {
|
|
s.ec.caps++
|
|
|
|
_, err := read(r.Body)
|
|
if err != nil {
|
|
send(w, err)
|
|
return
|
|
}
|
|
|
|
send(w, `{"Capabilities": { "Scope": "global" }}`)
|
|
})
|
|
|
|
err := os.MkdirAll("/etc/docker/plugins", 0755)
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
err = ioutil.WriteFile("/etc/docker/plugins/"+name+".spec", []byte(s.Server.URL), 0644)
|
|
c.Assert(err, checker.IsNil)
|
|
return s
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
|
|
s.volumePlugin.Close()
|
|
|
|
err := os.RemoveAll("/etc/docker/plugins")
|
|
c.Assert(err, checker.IsNil)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverNamed(c *check.C) {
|
|
err := s.d.StartWithBusybox()
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
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")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
c.Assert(out, checker.Contains, s.Server.URL)
|
|
|
|
_, err = s.d.Cmd("volume", "rm", "external-volume-test")
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
p := hostVolumePath("external-volume-test")
|
|
_, err = os.Lstat(p)
|
|
c.Assert(err, checker.NotNil)
|
|
c.Assert(os.IsNotExist(err), checker.True, check.Commentf("Expected volume path in host to not exist: %s, %v\n", p, err))
|
|
|
|
c.Assert(s.ec.activations, checker.Equals, 1)
|
|
c.Assert(s.ec.creations, checker.Equals, 1)
|
|
c.Assert(s.ec.removals, checker.Equals, 1)
|
|
c.Assert(s.ec.mounts, checker.Equals, 1)
|
|
c.Assert(s.ec.unmounts, checker.Equals, 1)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnnamed(c *check.C) {
|
|
err := s.d.StartWithBusybox()
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
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")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
c.Assert(out, checker.Contains, s.Server.URL)
|
|
|
|
c.Assert(s.ec.activations, checker.Equals, 1)
|
|
c.Assert(s.ec.creations, checker.Equals, 1)
|
|
c.Assert(s.ec.removals, checker.Equals, 1)
|
|
c.Assert(s.ec.mounts, checker.Equals, 1)
|
|
c.Assert(s.ec.unmounts, checker.Equals, 1)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverVolumesFrom(c *check.C) {
|
|
err := s.d.StartWithBusybox()
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", volumePluginName, "busybox:latest")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
|
|
out, err = s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
|
|
out, err = s.d.Cmd("rm", "-fv", "vol-test1")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
|
|
c.Assert(s.ec.activations, checker.Equals, 1)
|
|
c.Assert(s.ec.creations, checker.Equals, 1)
|
|
c.Assert(s.ec.removals, checker.Equals, 1)
|
|
c.Assert(s.ec.mounts, checker.Equals, 2)
|
|
c.Assert(s.ec.unmounts, checker.Equals, 2)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverDeleteContainer(c *check.C) {
|
|
err := s.d.StartWithBusybox()
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
out, err := s.d.Cmd("run", "--name", "vol-test1", "-v", "/foo", "--volume-driver", volumePluginName, "busybox:latest")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
|
|
out, err = s.d.Cmd("rm", "-fv", "vol-test1")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
|
|
c.Assert(s.ec.activations, checker.Equals, 1)
|
|
c.Assert(s.ec.creations, checker.Equals, 1)
|
|
c.Assert(s.ec.removals, checker.Equals, 1)
|
|
c.Assert(s.ec.mounts, checker.Equals, 1)
|
|
c.Assert(s.ec.unmounts, checker.Equals, 1)
|
|
}
|
|
|
|
func hostVolumePath(name string) string {
|
|
return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
|
|
}
|
|
|
|
// Make sure a request to use a down driver doesn't block other requests
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverLookupNotBlocked(c *check.C) {
|
|
specPath := "/etc/docker/plugins/down-driver.spec"
|
|
err := ioutil.WriteFile(specPath, []byte("tcp://127.0.0.7:9999"), 0644)
|
|
c.Assert(err, check.IsNil)
|
|
defer os.RemoveAll(specPath)
|
|
|
|
chCmd1 := make(chan struct{})
|
|
chCmd2 := make(chan error)
|
|
cmd1 := exec.Command(dockerBinary, "volume", "create", "-d", "down-driver")
|
|
cmd2 := exec.Command(dockerBinary, "volume", "create")
|
|
|
|
c.Assert(cmd1.Start(), checker.IsNil)
|
|
defer cmd1.Process.Kill()
|
|
time.Sleep(100 * time.Millisecond) // ensure API has been called
|
|
c.Assert(cmd2.Start(), checker.IsNil)
|
|
|
|
go func() {
|
|
cmd1.Wait()
|
|
close(chCmd1)
|
|
}()
|
|
go func() {
|
|
chCmd2 <- cmd2.Wait()
|
|
}()
|
|
|
|
select {
|
|
case <-chCmd1:
|
|
cmd2.Process.Kill()
|
|
c.Fatalf("volume create with down driver finished unexpectedly")
|
|
case err := <-chCmd2:
|
|
c.Assert(err, checker.IsNil)
|
|
case <-time.After(5 * time.Second):
|
|
cmd2.Process.Kill()
|
|
c.Fatal("volume creates are blocked by previous create requests when previous driver is down")
|
|
}
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverRetryNotImmediatelyExists(c *check.C) {
|
|
err := s.d.StartWithBusybox()
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
specPath := "/etc/docker/plugins/test-external-volume-driver-retry.spec"
|
|
os.RemoveAll(specPath)
|
|
defer os.RemoveAll(specPath)
|
|
|
|
errchan := make(chan error)
|
|
go func() {
|
|
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 {
|
|
errchan <- fmt.Errorf("%v:\n%s", err, out)
|
|
}
|
|
close(errchan)
|
|
}()
|
|
go func() {
|
|
// wait for a retry to occur, then create spec to allow plugin to register
|
|
time.Sleep(2000 * time.Millisecond)
|
|
// no need to check for an error here since it will get picked up by the timeout later
|
|
ioutil.WriteFile(specPath, []byte(s.Server.URL), 0644)
|
|
}()
|
|
|
|
select {
|
|
case err := <-errchan:
|
|
c.Assert(err, checker.IsNil)
|
|
case <-time.After(8 * time.Second):
|
|
c.Fatal("volume creates fail when plugin not immediately available")
|
|
}
|
|
|
|
_, err = s.d.Cmd("volume", "rm", "external-volume-test")
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
c.Assert(s.ec.activations, checker.Equals, 1)
|
|
c.Assert(s.ec.creations, checker.Equals, 1)
|
|
c.Assert(s.ec.removals, checker.Equals, 1)
|
|
c.Assert(s.ec.mounts, checker.Equals, 1)
|
|
c.Assert(s.ec.unmounts, checker.Equals, 1)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverBindExternalVolume(c *check.C) {
|
|
dockerCmd(c, "volume", "create", "-d", volumePluginName, "foo")
|
|
dockerCmd(c, "run", "-d", "--name", "testing", "-v", "foo:/bar", "busybox", "top")
|
|
|
|
var mounts []struct {
|
|
Name string
|
|
Driver string
|
|
}
|
|
out := inspectFieldJSON(c, "testing", "Mounts")
|
|
c.Assert(json.NewDecoder(strings.NewReader(out)).Decode(&mounts), checker.IsNil)
|
|
c.Assert(len(mounts), checker.Equals, 1, check.Commentf(out))
|
|
c.Assert(mounts[0].Name, checker.Equals, "foo")
|
|
c.Assert(mounts[0].Driver, checker.Equals, volumePluginName)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverList(c *check.C) {
|
|
dockerCmd(c, "volume", "create", "-d", volumePluginName, "abc3")
|
|
out, _ := dockerCmd(c, "volume", "ls")
|
|
ls := strings.Split(strings.TrimSpace(out), "\n")
|
|
c.Assert(len(ls), check.Equals, 2, check.Commentf("\n%s", out))
|
|
|
|
vol := strings.Fields(ls[len(ls)-1])
|
|
c.Assert(len(vol), check.Equals, 2, check.Commentf("%v", vol))
|
|
c.Assert(vol[0], check.Equals, volumePluginName)
|
|
c.Assert(vol[1], check.Equals, "abc3")
|
|
|
|
c.Assert(s.ec.lists, check.Equals, 1)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGet(c *check.C) {
|
|
out, _, err := dockerCmdWithError("volume", "inspect", "dummy")
|
|
c.Assert(err, check.NotNil, check.Commentf(out))
|
|
c.Assert(out, checker.Contains, "No such volume")
|
|
c.Assert(s.ec.gets, check.Equals, 1)
|
|
|
|
dockerCmd(c, "volume", "create", "test", "-d", volumePluginName)
|
|
out, _ = dockerCmd(c, "volume", "inspect", "test")
|
|
|
|
type vol struct {
|
|
Status map[string]string
|
|
}
|
|
var st []vol
|
|
|
|
c.Assert(json.Unmarshal([]byte(out), &st), checker.IsNil)
|
|
c.Assert(st, checker.HasLen, 1)
|
|
c.Assert(st[0].Status, checker.HasLen, 1, check.Commentf("%v", st[0]))
|
|
c.Assert(st[0].Status["Hello"], checker.Equals, "world", check.Commentf("%v", st[0].Status))
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverWithDaemonRestart(c *check.C) {
|
|
dockerCmd(c, "volume", "create", "-d", volumePluginName, "abc1")
|
|
err := s.d.Restart()
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
dockerCmd(c, "run", "--name=test", "-v", "abc1:/foo", "busybox", "true")
|
|
var mounts []types.MountPoint
|
|
inspectFieldAndMarshall(c, "test", "Mounts", &mounts)
|
|
c.Assert(mounts, checker.HasLen, 1)
|
|
c.Assert(mounts[0].Driver, checker.Equals, volumePluginName)
|
|
}
|
|
|
|
// Ensures that the daemon handles when the plugin responds to a `Get` request with a null volume and a null error.
|
|
// Prior the daemon would panic in this scenario.
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverGetEmptyResponse(c *check.C) {
|
|
c.Assert(s.d.Start(), checker.IsNil)
|
|
|
|
out, err := s.d.Cmd("volume", "create", "-d", volumePluginName, "abc2", "--opt", "ninja=1")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
|
|
out, err = s.d.Cmd("volume", "inspect", "abc2")
|
|
c.Assert(err, checker.NotNil, check.Commentf(out))
|
|
c.Assert(out, checker.Contains, "No such volume")
|
|
}
|
|
|
|
// Ensure only cached paths are used in volume list to prevent N+1 calls to `VolumeDriver.Path`
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C) {
|
|
c.Assert(s.d.Start(), checker.IsNil)
|
|
c.Assert(s.ec.paths, checker.Equals, 0)
|
|
|
|
out, err := s.d.Cmd("volume", "create", "test", "--driver=test-external-volume-driver")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
c.Assert(s.ec.paths, checker.Equals, 1)
|
|
|
|
out, err = s.d.Cmd("volume", "ls")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
c.Assert(s.ec.paths, checker.Equals, 1)
|
|
|
|
out, err = s.d.Cmd("volume", "inspect", "--format='{{.Mountpoint}}'", "test")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
|
|
c.Assert(s.ec.paths, checker.Equals, 1)
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C) {
|
|
err := s.d.StartWithBusybox()
|
|
c.Assert(err, checker.IsNil)
|
|
|
|
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")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
|
|
}
|
|
|
|
// Check that VolumeDriver.Capabilities gets called, and only called once
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverCapabilities(c *check.C) {
|
|
c.Assert(s.d.Start(), checker.IsNil)
|
|
c.Assert(s.ec.caps, checker.Equals, 0)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
out, err := s.d.Cmd("volume", "create", "-d", volumePluginName, fmt.Sprintf("test%d", i))
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
c.Assert(s.ec.caps, checker.Equals, 1)
|
|
out, err = s.d.Cmd("volume", "inspect", "--format={{.Scope}}", fmt.Sprintf("test%d", i))
|
|
c.Assert(err, checker.IsNil)
|
|
c.Assert(strings.TrimSpace(out), checker.Equals, volume.GlobalScope)
|
|
}
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverOutOfBandDelete(c *check.C) {
|
|
driverName := stringid.GenerateNonCryptoID()
|
|
p := newVolumePlugin(c, driverName)
|
|
defer p.Close()
|
|
|
|
c.Assert(s.d.StartWithBusybox(), checker.IsNil)
|
|
|
|
out, err := s.d.Cmd("volume", "create", "-d", driverName, "--name", "test")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
|
|
out, err = s.d.Cmd("volume", "create", "-d", "local", "--name", "test")
|
|
c.Assert(err, checker.NotNil, check.Commentf(out))
|
|
c.Assert(out, checker.Contains, "volume named test already exists")
|
|
|
|
// simulate out of band volume deletion on plugin level
|
|
delete(p.vols, "test")
|
|
|
|
out, err = s.d.Cmd("volume", "create", "-d", "local", "--name", "test")
|
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
}
|
|
|
|
func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnmountOnMountFail(c *check.C) {
|
|
c.Assert(s.d.StartWithBusybox(), checker.IsNil)
|
|
s.d.Cmd("volume", "create", "-d", "test-external-volume-driver", "--opt=invalidOption=1", "--name=testumount")
|
|
|
|
out, _ := s.d.Cmd("run", "-v", "testumount:/foo", "busybox", "true")
|
|
c.Assert(s.ec.unmounts, checker.Equals, 0, check.Commentf(out))
|
|
out, _ = s.d.Cmd("run", "-w", "/foo", "-v", "testumount:/foo", "busybox", "true")
|
|
c.Assert(s.ec.unmounts, checker.Equals, 0, check.Commentf(out))
|
|
}
|