// +build experimental // +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/pkg/archive" "github.com/go-check/check" ) func init() { check.Suite(&DockerExternalGraphdriverSuite{ ds: &DockerSuite{}, }) } type DockerExternalGraphdriverSuite struct { server *httptest.Server ds *DockerSuite d *Daemon ec *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 = NewDaemon(c) s.ec = &graphEventsCounter{} } func (s *DockerExternalGraphdriverSuite) TearDownTest(c *check.C) { s.d.Stop() s.ds.TearDownTest(c) } func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) { mux := http.NewServeMux() s.server = httptest.NewServer(mux) type graphDriverRequest struct { ID string `json:",omitempty"` Parent string `json:",omitempty"` MountLabel string `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", "appplication/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("", "external-graph-test") 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) mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { s.ec.activations++ respond(w, `{"Implements": ["GraphDriver"]}`) }) mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) { s.ec.init++ respond(w, "{}") }) mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) { s.ec.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.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.gets++ var req graphDriverRequest if err := decReq(r.Body, &req, w); err != nil { return } dir, err := driver.Get(req.ID, req.MountLabel) if err != nil { respond(w, err) return } respond(w, &graphDriverResponse{Dir: dir}) }) mux.HandleFunc("/GraphDriver.Put", func(w http.ResponseWriter, r *http.Request) { s.ec.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.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.stats++ respond(w, &graphDriverResponse{Status: driver.Status()}) }) mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) { s.ec.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.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.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.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.applydiff++ var diff archive.Reader = 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.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")) err = ioutil.WriteFile("/etc/docker/plugins/test-external-graph-driver.spec", []byte(s.server.URL), 0644) c.Assert(err, check.IsNil, check.Commentf("error writing to /etc/docker/plugins/test-external-graph-driver.spec")) } func (s *DockerExternalGraphdriverSuite) TearDownSuite(c *check.C) { s.server.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) { if err := s.d.StartWithBusybox("-s", "test-external-graph-driver"); err != nil { b, _ := ioutil.ReadFile(s.d.LogFileName()) c.Assert(err, check.IsNil, check.Commentf("\n%s", string(b))) } out, err := s.d.Cmd("run", "-d", "--name=graphtest", "busybox", "sh", "-c", "echo hello > /hello") c.Assert(err, check.IsNil, check.Commentf(out)) err = s.d.Restart("-s", "test-external-graph-driver") 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, "test-external-graph-driver") 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) 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)) err = s.d.Stop() c.Assert(err, check.IsNil) // Don't check s.ec.exists, because the daemon no longer calls the // Exists function. c.Assert(s.ec.activations, check.Equals, 2) c.Assert(s.ec.init, check.Equals, 2) c.Assert(s.ec.creations >= 1, check.Equals, true) c.Assert(s.ec.removals >= 1, check.Equals, true) c.Assert(s.ec.gets >= 1, check.Equals, true) c.Assert(s.ec.puts >= 1, check.Equals, true) c.Assert(s.ec.stats, check.Equals, 3) c.Assert(s.ec.cleanups, check.Equals, 2) c.Assert(s.ec.applydiff >= 1, check.Equals, true) c.Assert(s.ec.changes, check.Equals, 1) c.Assert(s.ec.diffsize, check.Equals, 0) c.Assert(s.ec.diff, check.Equals, 0) c.Assert(s.ec.metadata, check.Equals, 1) } func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriverPull(c *check.C) { testRequires(c, Network) c.Assert(s.d.Start(), check.IsNil) 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)) }