mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
2938dce794
When a plugin is activated, and then `plugins.Handle` is called to register a new handler for a given plugin type, a deadlock occurs when for anything which calls `waitActive`, including `Get`, and `GetAll`. This happens because `Handle()` is setting `activated` to `false` to ensure that plugin handlers are run on next activation. Maybe these handlers should be called immediately for any plugins which are already registered... but to preserve the existing behavior while fixing the deadlock, track if handlers have been run on plugins and reset when a new handler is registered. The simplest way to reproduce the deadlock with Docker is to add a `-v /foo` to the test container created for the external graphdriver tests. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
406 lines
11 KiB
Go
406 lines
11 KiB
Go
// +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: 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", "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("", 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
|
|
}
|
|
|
|
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[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))
|
|
}
|