Merge pull request #13161 from calavera/plugin_discovery

Proposal: Volume refactor and external volume plugins
Upstream-commit: bce3e761c0a78ca55cefa9c6d040c1bc9138c1fb
Component: engine
This commit is contained in:
Arnaud Porterie
2015-05-23 18:44:18 -07:00
43 changed files with 1802 additions and 1190 deletions
@@ -166,7 +166,7 @@ func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) {
c.Assert(status, check.Equals, http.StatusInternalServerError)
c.Assert(err, check.IsNil)
if !strings.Contains(string(body), "Duplicate volume") {
if !strings.Contains(string(body), "Duplicate bind") {
c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err)
}
}
@@ -210,49 +210,6 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {
}
}
// Ensure that volumes-from has priority over binds/anything else
// This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start
func (s *DockerSuite) TestVolumesFromHasPriority(c *check.C) {
volName := "voltst2"
volPath := "/tmp"
if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
c.Fatal(out, err)
}
name := "testing"
config := map[string]interface{}{
"Image": "busybox",
"Volumes": map[string]struct{}{volPath: {}},
}
status, _, err := sockRequest("POST", "/containers/create?name="+name, config)
c.Assert(status, check.Equals, http.StatusCreated)
c.Assert(err, check.IsNil)
bindPath := randomUnixTmpDirPath("test")
config = map[string]interface{}{
"VolumesFrom": []string{volName},
"Binds": []string{bindPath + ":/tmp"},
}
status, _, err = sockRequest("POST", "/containers/"+name+"/start", config)
c.Assert(status, check.Equals, http.StatusNoContent)
c.Assert(err, check.IsNil)
pth, err := inspectFieldMap(name, "Volumes", volPath)
if err != nil {
c.Fatal(err)
}
pth2, err := inspectFieldMap(volName, "Volumes", volPath)
if err != nil {
c.Fatal(err)
}
if pth != pth2 {
c.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
}
}
func (s *DockerSuite) TestGetContainerStats(c *check.C) {
var (
name = "statscontainer"
@@ -284,35 +284,6 @@ func (s *DockerDaemonSuite) TestDaemonAllocatesListeningPort(c *check.C) {
}
}
// #9629
func (s *DockerDaemonSuite) TestDaemonVolumesBindsRefs(c *check.C) {
if err := s.d.StartWithBusybox(); err != nil {
c.Fatal(err)
}
tmp, err := ioutil.TempDir(os.TempDir(), "")
if err != nil {
c.Fatal(err)
}
defer os.RemoveAll(tmp)
if err := ioutil.WriteFile(tmp+"/test", []byte("testing"), 0655); err != nil {
c.Fatal(err)
}
if out, err := s.d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil {
c.Fatal(err, out)
}
if err := s.d.Restart(); err != nil {
c.Fatal(err)
}
if out, err := s.d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil {
c.Fatal(err, out)
}
}
func (s *DockerDaemonSuite) TestDaemonKeyGeneration(c *check.C) {
// TODO: skip or update for Windows daemon
os.Remove("/etc/docker/key.json")
@@ -360,76 +331,6 @@ func (s *DockerDaemonSuite) TestDaemonKeyMigration(c *check.C) {
}
}
// Simulate an older daemon (pre 1.3) coming up with volumes specified in containers
// without corresponding volume json
func (s *DockerDaemonSuite) TestDaemonUpgradeWithVolumes(c *check.C) {
graphDir := filepath.Join(os.TempDir(), "docker-test")
defer os.RemoveAll(graphDir)
if err := s.d.StartWithBusybox("-g", graphDir); err != nil {
c.Fatal(err)
}
tmpDir := filepath.Join(os.TempDir(), "test")
defer os.RemoveAll(tmpDir)
if out, err := s.d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil {
c.Fatal(err, out)
}
if err := s.d.Stop(); err != nil {
c.Fatal(err)
}
// Remove this since we're expecting the daemon to re-create it too
if err := os.RemoveAll(tmpDir); err != nil {
c.Fatal(err)
}
configDir := filepath.Join(graphDir, "volumes")
if err := os.RemoveAll(configDir); err != nil {
c.Fatal(err)
}
if err := s.d.Start("-g", graphDir); err != nil {
c.Fatal(err)
}
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
c.Fatalf("expected volume path %s to exist but it does not", tmpDir)
}
dir, err := ioutil.ReadDir(configDir)
if err != nil {
c.Fatal(err)
}
if len(dir) == 0 {
c.Fatalf("expected volumes config dir to contain data for new volume")
}
// Now with just removing the volume config and not the volume data
if err := s.d.Stop(); err != nil {
c.Fatal(err)
}
if err := os.RemoveAll(configDir); err != nil {
c.Fatal(err)
}
if err := s.d.Start("-g", graphDir); err != nil {
c.Fatal(err)
}
dir, err = ioutil.ReadDir(configDir)
if err != nil {
c.Fatal(err)
}
if len(dir) == 0 {
c.Fatalf("expected volumes config dir to contain data for new volume")
}
}
// GH#11320 - verify that the daemon exits on failure properly
// Note that this explicitly tests the conflict of {-b,--bridge} and {--bip} options as the means
// to get a daemon init failure; no other tests for -b/--bip conflict are therefore required
@@ -395,21 +395,6 @@ func (s *DockerSuite) TestRunModeNetContainerHostname(c *check.C) {
}
}
// Regression test for #4741
func (s *DockerSuite) TestRunWithVolumesAsFiles(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/etc/hosts:/target-file", "busybox", "true")
out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
if err != nil && exitCode != 0 {
c.Fatal("1", out, stderr, err)
}
runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/target-file")
out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd)
if err != nil && exitCode != 0 {
c.Fatal("2", out, stderr, err)
}
}
// Regression test for #4979
func (s *DockerSuite) TestRunWithVolumesFromExited(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/some/dir", "busybox", "touch", "/some/dir/file")
@@ -455,14 +440,6 @@ func (s *DockerSuite) TestRunCreateVolumesInSymlinkDir(c *check.C) {
}
}
// Regression test for #4830
func (s *DockerSuite) TestRunWithRelativePath(c *check.C) {
runCmd := exec.Command(dockerBinary, "run", "-v", "tmp:/other-tmp", "busybox", "true")
if _, _, _, err := runCommandWithStdoutStderr(runCmd); err == nil {
c.Fatalf("relative path should result in an error")
}
}
func (s *DockerSuite) TestRunVolumesMountedAsReadonly(c *check.C) {
cmd := exec.Command(dockerBinary, "run", "-v", "/test:/test:ro", "busybox", "touch", "/test/somefile")
if code, err := runCommand(cmd); err == nil || code == 0 {
@@ -536,7 +513,7 @@ func (s *DockerSuite) TestRunNoDupVolumes(c *check.C) {
if out, _, err := runCommandWithOutput(cmd); err == nil {
c.Fatal("Expected error about duplicate volume definitions")
} else {
if !strings.Contains(out, "Duplicate volume") {
if !strings.Contains(out, "Duplicate bind mount") {
c.Fatalf("Expected 'duplicate volume' error, got %v", err)
}
}
@@ -2333,7 +2310,13 @@ func (s *DockerSuite) TestRunMountOrdering(c *check.C) {
c.Fatal(err)
}
cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp", tmpDir), "-v", fmt.Sprintf("%s:/tmp/foo", fooDir), "-v", fmt.Sprintf("%s:/tmp/tmp2", tmpDir2), "-v", fmt.Sprintf("%s:/tmp/tmp2/foo", fooDir), "busybox:latest", "sh", "-c", "ls /tmp/touch-me && ls /tmp/foo/touch-me && ls /tmp/tmp2/touch-me && ls /tmp/tmp2/foo/touch-me")
cmd := exec.Command(dockerBinary, "run",
"-v", fmt.Sprintf("%s:/tmp", tmpDir),
"-v", fmt.Sprintf("%s:/tmp/foo", fooDir),
"-v", fmt.Sprintf("%s:/tmp/tmp2", tmpDir2),
"-v", fmt.Sprintf("%s:/tmp/tmp2/foo", fooDir),
"busybox:latest", "sh", "-c",
"ls /tmp/touch-me && ls /tmp/foo/touch-me && ls /tmp/tmp2/touch-me && ls /tmp/tmp2/foo/touch-me")
out, _, err := runCommandWithOutput(cmd)
if err != nil {
c.Fatal(out, err)
@@ -2427,41 +2410,6 @@ func (s *DockerSuite) TestVolumesNoCopyData(c *check.C) {
}
}
func (s *DockerSuite) TestRunVolumesNotRecreatedOnStart(c *check.C) {
testRequires(c, SameHostDaemon)
// Clear out any remnants from other tests
info, err := ioutil.ReadDir(volumesConfigPath)
if err != nil {
c.Fatal(err)
}
if len(info) > 0 {
for _, f := range info {
if err := os.RemoveAll(volumesConfigPath + "/" + f.Name()); err != nil {
c.Fatal(err)
}
}
}
cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "--name", "lone_starr", "busybox")
if _, err := runCommand(cmd); err != nil {
c.Fatal(err)
}
cmd = exec.Command(dockerBinary, "start", "lone_starr")
if _, err := runCommand(cmd); err != nil {
c.Fatal(err)
}
info, err = ioutil.ReadDir(volumesConfigPath)
if err != nil {
c.Fatal(err)
}
if len(info) != 1 {
c.Fatalf("Expected only 1 volume have %v", len(info))
}
}
func (s *DockerSuite) TestRunNoOutputFromPullInStdout(c *check.C) {
// just run with unknown image
cmd := exec.Command(dockerBinary, "run", "asdfsg")
@@ -2496,7 +2444,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
out, err = inspectFieldMap("dark_helmet", "Volumes", "/foo")
c.Assert(err, check.IsNil)
if !strings.Contains(out, volumesStoragePath) {
if !strings.Contains(out, volumesConfigPath) {
c.Fatalf("Volume was not defined for /foo\n%q", out)
}
@@ -2507,7 +2455,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
}
out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar")
c.Assert(err, check.IsNil)
if !strings.Contains(out, volumesStoragePath) {
if !strings.Contains(out, volumesConfigPath) {
c.Fatalf("Volume was not defined for /bar\n%q", out)
}
}
@@ -126,32 +126,6 @@ func (s *DockerSuite) TestStartRecordError(c *check.C) {
}
// gh#8726: a failed Start() breaks --volumes-from on subsequent Start()'s
func (s *DockerSuite) TestStartVolumesFromFailsCleanly(c *check.C) {
// Create the first data volume
dockerCmd(c, "run", "-d", "--name", "data_before", "-v", "/foo", "busybox")
// Expect this to fail because the data test after contaienr doesn't exist yet
if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "consumer", "--volumes-from", "data_before", "--volumes-from", "data_after", "busybox")); err == nil {
c.Fatal("Expected error but got none")
}
// Create the second data volume
dockerCmd(c, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox")
// Now, all the volumes should be there
dockerCmd(c, "start", "consumer")
// Check that we have the volumes we want
out, _ := dockerCmd(c, "inspect", "--format='{{ len .Volumes }}'", "consumer")
nVolumes := strings.Trim(out, " \r\n'")
if nVolumes != "2" {
c.Fatalf("Missing volumes: expected 2, got %s", nVolumes)
}
}
func (s *DockerSuite) TestStartPausedContainer(c *check.C) {
defer unpauseAllContainers()
@@ -0,0 +1,249 @@
// +build experimental
// +build !windows
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"github.com/go-check/check"
)
func init() {
check.Suite(&DockerExternalVolumeSuite{
ds: &DockerSuite{},
})
}
type eventCounter struct {
activations int
creations int
removals int
mounts int
unmounts int
paths int
}
type DockerExternalVolumeSuite struct {
server *httptest.Server
ds *DockerSuite
d *Daemon
ec *eventCounter
}
func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
s.d = NewDaemon(c)
s.ds.SetUpTest(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) {
mux := http.NewServeMux()
s.server = httptest.NewServer(mux)
type pluginRequest struct {
name string
}
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
s.ec.activations++
w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
fmt.Fprintln(w, `{"Implements": ["VolumeDriver"]}`)
})
mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
s.ec.creations++
w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
fmt.Fprintln(w, `{}`)
})
mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
s.ec.removals++
w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
fmt.Fprintln(w, `{}`)
})
mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
s.ec.paths++
var pr pluginRequest
if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
http.Error(w, err.Error(), 500)
}
p := hostVolumePath(pr.name)
w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
})
mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
s.ec.mounts++
var pr pluginRequest
if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
http.Error(w, err.Error(), 500)
}
p := hostVolumePath(pr.name)
if err := os.MkdirAll(p, 0755); err != nil {
http.Error(w, err.Error(), 500)
}
if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil {
http.Error(w, err.Error(), 500)
}
w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
})
mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
s.ec.unmounts++
var pr pluginRequest
if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
http.Error(w, err.Error(), 500)
}
p := hostVolumePath(pr.name)
if err := os.RemoveAll(p); err != nil {
http.Error(w, err.Error(), 500)
}
w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
fmt.Fprintln(w, `{}`)
})
if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil {
c.Fatal(err)
}
if err := ioutil.WriteFile("/usr/share/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644); err != nil {
c.Fatal(err)
}
}
func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
s.server.Close()
if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
c.Fatal(err)
}
}
func (s *DockerExternalVolumeSuite) TestStartExternalNamedVolumeDriver(c *check.C) {
if err := s.d.StartWithBusybox(); err != nil {
c.Fatal(err)
}
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")
if err != nil {
c.Fatal(err)
}
if !strings.Contains(out, s.server.URL) {
c.Fatalf("External volume mount failed. Output: %s\n", out)
}
p := hostVolumePath("external-volume-test")
_, err = os.Lstat(p)
if err == nil {
c.Fatalf("Expected error checking volume path in host: %s\n", p)
}
if !os.IsNotExist(err) {
c.Fatalf("Expected volume path in host to not exist: %s, %v\n", p, err)
}
c.Assert(s.ec.activations, check.Equals, 1)
c.Assert(s.ec.creations, check.Equals, 1)
c.Assert(s.ec.removals, check.Equals, 1)
c.Assert(s.ec.mounts, check.Equals, 1)
c.Assert(s.ec.unmounts, check.Equals, 1)
}
func (s *DockerExternalVolumeSuite) TestStartExternalVolumeUnnamedDriver(c *check.C) {
if err := s.d.StartWithBusybox(); err != nil {
c.Fatal(err)
}
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")
if err != nil {
c.Fatal(err)
}
if !strings.Contains(out, s.server.URL) {
c.Fatalf("External volume mount failed. Output: %s\n", out)
}
c.Assert(s.ec.activations, check.Equals, 1)
c.Assert(s.ec.creations, check.Equals, 1)
c.Assert(s.ec.removals, check.Equals, 1)
c.Assert(s.ec.mounts, check.Equals, 1)
c.Assert(s.ec.unmounts, check.Equals, 1)
}
func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverVolumesFrom(c *check.C) {
if err := s.d.StartWithBusybox(); err != nil {
c.Fatal(err)
}
if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
c.Fatal(err)
}
if _, err := s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp"); err != nil {
c.Fatal(err)
}
if _, err := s.d.Cmd("rm", "-f", "vol-test1"); err != nil {
c.Fatal(err)
}
c.Assert(s.ec.activations, check.Equals, 1)
c.Assert(s.ec.creations, check.Equals, 2)
c.Assert(s.ec.removals, check.Equals, 1)
c.Assert(s.ec.mounts, check.Equals, 2)
c.Assert(s.ec.unmounts, check.Equals, 2)
}
func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverDeleteContainer(c *check.C) {
if err := s.d.StartWithBusybox(); err != nil {
c.Fatal(err)
}
if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
c.Fatal(err)
}
if _, err := s.d.Cmd("rm", "-fv", "vol-test1"); err != nil {
c.Fatal(err)
}
c.Assert(s.ec.activations, check.Equals, 1)
c.Assert(s.ec.creations, check.Equals, 1)
c.Assert(s.ec.removals, check.Equals, 1)
c.Assert(s.ec.mounts, check.Equals, 1)
c.Assert(s.ec.unmounts, check.Equals, 1)
}
func hostVolumePath(name string) string {
return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
}
@@ -18,7 +18,6 @@ var (
dockerBasePath = "/var/lib/docker"
volumesConfigPath = dockerBasePath + "/volumes"
volumesStoragePath = dockerBasePath + "/vfs/dir"
containerStoragePath = dockerBasePath + "/containers"
runtimePath = "/var/run/docker"