// +build !windows package main import ( "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/http/httptest" "os" "strings" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/daemon/graphdriver/vfs" "github.com/docker/docker/integration-cli/daemon" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/plugins" "github.com/go-check/check" ) func init() { check.Suite(&DockerExternalGraphdriverSuite{ ds: &DockerSuite{}, }) } type DockerExternalGraphdriverSuite struct { server *httptest.Server jserver *httptest.Server ds *DockerSuite d *daemon.Daemon ec map[string]*graphEventsCounter } type graphEventsCounter struct { activations int creations int removals int gets int puts int stats int cleanups int exists int init int metadata int diff int applydiff int changes int diffsize int } func (s *DockerExternalGraphdriverSuite) SetUpTest(c *check.C) { s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{ Experimental: testEnv.ExperimentalDaemon(), }) } func (s *DockerExternalGraphdriverSuite) OnTimeout(c *check.C) { s.d.DumpStackAndQuit() } func (s *DockerExternalGraphdriverSuite) TearDownTest(c *check.C) { if s.d != nil { s.d.Stop(c) s.ds.TearDownTest(c) } } func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) { s.ec = make(map[string]*graphEventsCounter) s.setUpPluginViaSpecFile(c) s.setUpPluginViaJSONFile(c) } func (s *DockerExternalGraphdriverSuite) setUpPluginViaSpecFile(c *check.C) { mux := http.NewServeMux() s.server = httptest.NewServer(mux) s.setUpPlugin(c, "test-external-graph-driver", "spec", mux, []byte(s.server.URL)) } func (s *DockerExternalGraphdriverSuite) setUpPluginViaJSONFile(c *check.C) { mux := http.NewServeMux() s.jserver = httptest.NewServer(mux) p := plugins.NewLocalPlugin("json-external-graph-driver", s.jserver.URL) b, err := json.Marshal(p) c.Assert(err, check.IsNil) s.setUpPlugin(c, "json-external-graph-driver", "json", mux, b) } func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ext string, mux *http.ServeMux, b []byte) { type graphDriverRequest struct { ID string `json:",omitempty"` Parent string `json:",omitempty"` MountLabel string `json:",omitempty"` ReadOnly bool `json:",omitempty"` } type graphDriverResponse struct { Err error `json:",omitempty"` Dir string `json:",omitempty"` Exists bool `json:",omitempty"` Status [][2]string `json:",omitempty"` Metadata map[string]string `json:",omitempty"` Changes []archive.Change `json:",omitempty"` Size int64 `json:",omitempty"` } respond := func(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") switch t := data.(type) { case error: fmt.Fprintln(w, fmt.Sprintf(`{"Err": %q}`, t.Error())) case string: fmt.Fprintln(w, t) default: json.NewEncoder(w).Encode(&data) } } decReq := func(b io.ReadCloser, out interface{}, w http.ResponseWriter) error { defer b.Close() if err := json.NewDecoder(b).Decode(&out); err != nil { http.Error(w, fmt.Sprintf("error decoding json: %s", err.Error()), 500) } return nil } base, err := ioutil.TempDir("", name) c.Assert(err, check.IsNil) vfsProto, err := vfs.Init(base, []string{}, nil, nil) c.Assert(err, check.IsNil, check.Commentf("error initializing graph driver")) driver := graphdriver.NewNaiveDiffDriver(vfsProto, nil, nil) s.ec[ext] = &graphEventsCounter{} mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].activations++ respond(w, `{"Implements": ["GraphDriver"]}`) }) mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].init++ respond(w, "{}") }) mux.HandleFunc("/GraphDriver.CreateReadWrite", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].creations++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } if err := driver.CreateReadWrite(req.ID, req.Parent, nil); err != nil { respond(w, err) return } respond(w, "{}") }) mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].creations++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } if err := driver.Create(req.ID, req.Parent, nil); err != nil { respond(w, err) return } respond(w, "{}") }) mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].removals++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } if err := driver.Remove(req.ID); err != nil { respond(w, err) return } respond(w, "{}") }) mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].gets++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } // TODO @gupta-ak: Figure out what to do here. dir, err := driver.Get(req.ID, req.MountLabel) if err != nil { respond(w, err) return } respond(w, &graphDriverResponse{Dir: dir.Path()}) }) mux.HandleFunc("/GraphDriver.Put", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].puts++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } if err := driver.Put(req.ID); err != nil { respond(w, err) return } respond(w, "{}") }) mux.HandleFunc("/GraphDriver.Exists", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].exists++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } respond(w, &graphDriverResponse{Exists: driver.Exists(req.ID)}) }) mux.HandleFunc("/GraphDriver.Status", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].stats++ respond(w, &graphDriverResponse{Status: driver.Status()}) }) mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].cleanups++ err := driver.Cleanup() if err != nil { respond(w, err) return } respond(w, `{}`) }) mux.HandleFunc("/GraphDriver.GetMetadata", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].metadata++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } data, err := driver.GetMetadata(req.ID) if err != nil { respond(w, err) return } respond(w, &graphDriverResponse{Metadata: data}) }) mux.HandleFunc("/GraphDriver.Diff", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].diff++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } diff, err := driver.Diff(req.ID, req.Parent) if err != nil { respond(w, err) return } io.Copy(w, diff) }) mux.HandleFunc("/GraphDriver.Changes", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].changes++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } changes, err := driver.Changes(req.ID, req.Parent) if err != nil { respond(w, err) return } respond(w, &graphDriverResponse{Changes: changes}) }) mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].applydiff++ diff := r.Body defer r.Body.Close() id := r.URL.Query().Get("id") parent := r.URL.Query().Get("parent") if id == "" { http.Error(w, fmt.Sprintf("missing id"), 409) } size, err := driver.ApplyDiff(id, parent, diff) if err != nil { respond(w, err) return } respond(w, &graphDriverResponse{Size: size}) }) mux.HandleFunc("/GraphDriver.DiffSize", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].diffsize++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } size, err := driver.DiffSize(req.ID, req.Parent) if err != nil { respond(w, err) return } respond(w, &graphDriverResponse{Size: size}) }) err = os.MkdirAll("/etc/docker/plugins", 0755) c.Assert(err, check.IsNil, check.Commentf("error creating /etc/docker/plugins")) specFile := "/etc/docker/plugins/" + name + "." + ext err = ioutil.WriteFile(specFile, b, 0644) c.Assert(err, check.IsNil, check.Commentf("error writing to %s", specFile)) } func (s *DockerExternalGraphdriverSuite) TearDownSuite(c *check.C) { s.server.Close() s.jserver.Close() err := os.RemoveAll("/etc/docker/plugins") c.Assert(err, check.IsNil, check.Commentf("error removing /etc/docker/plugins")) } func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) { testRequires(c, ExperimentalDaemon) s.testExternalGraphDriver("test-external-graph-driver", "spec", c) s.testExternalGraphDriver("json-external-graph-driver", "json", c) } func (s *DockerExternalGraphdriverSuite) testExternalGraphDriver(name string, ext string, c *check.C) { s.d.StartWithBusybox(c, "-s", name) out, err := s.d.Cmd("run", "--name=graphtest", "busybox", "sh", "-c", "echo hello > /hello") c.Assert(err, check.IsNil, check.Commentf(out)) s.d.Restart(c, "-s", name) out, err = s.d.Cmd("inspect", "--format={{.GraphDriver.Name}}", "graphtest") c.Assert(err, check.IsNil, check.Commentf(out)) c.Assert(strings.TrimSpace(out), check.Equals, name) out, err = s.d.Cmd("diff", "graphtest") c.Assert(err, check.IsNil, check.Commentf(out)) c.Assert(strings.Contains(out, "A /hello"), check.Equals, true, check.Commentf("diff output: %s", out)) out, err = s.d.Cmd("rm", "-f", "graphtest") c.Assert(err, check.IsNil, check.Commentf(out)) out, err = s.d.Cmd("info") c.Assert(err, check.IsNil, check.Commentf(out)) s.d.Stop(c) // Don't check s.ec.exists, because the daemon no longer calls the // Exists function. c.Assert(s.ec[ext].activations, check.Equals, 2) c.Assert(s.ec[ext].init, check.Equals, 2) c.Assert(s.ec[ext].creations >= 1, check.Equals, true) c.Assert(s.ec[ext].removals >= 1, check.Equals, true) c.Assert(s.ec[ext].gets >= 1, check.Equals, true) c.Assert(s.ec[ext].puts >= 1, check.Equals, true) c.Assert(s.ec[ext].stats, check.Equals, 5) c.Assert(s.ec[ext].cleanups, check.Equals, 2) c.Assert(s.ec[ext].applydiff >= 1, check.Equals, true) c.Assert(s.ec[ext].changes, check.Equals, 1) c.Assert(s.ec[ext].diffsize, check.Equals, 0) c.Assert(s.ec[ext].diff, check.Equals, 0) c.Assert(s.ec[ext].metadata, check.Equals, 1) } func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriverPull(c *check.C) { testRequires(c, Network, ExperimentalDaemon) s.d.Start(c) out, err := s.d.Cmd("pull", "busybox:latest") c.Assert(err, check.IsNil, check.Commentf(out)) out, err = s.d.Cmd("run", "-d", "busybox", "top") c.Assert(err, check.IsNil, check.Commentf(out)) }