github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/integration/plugin/graphdriver/external_test.go (about)

     1  package graphdriver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"os"
    12  	"runtime"
    13  	"testing"
    14  
    15  	"github.com/docker/docker/api/types"
    16  	containertypes "github.com/docker/docker/api/types/container"
    17  	"github.com/docker/docker/client"
    18  	"github.com/docker/docker/daemon/graphdriver"
    19  	"github.com/docker/docker/daemon/graphdriver/vfs"
    20  	"github.com/docker/docker/integration/internal/container"
    21  	"github.com/docker/docker/integration/internal/requirement"
    22  	"github.com/docker/docker/internal/test/daemon"
    23  	"github.com/docker/docker/pkg/archive"
    24  	"github.com/docker/docker/pkg/plugins"
    25  	"gotest.tools/assert"
    26  	is "gotest.tools/assert/cmp"
    27  	"gotest.tools/skip"
    28  )
    29  
    30  type graphEventsCounter struct {
    31  	activations int
    32  	creations   int
    33  	removals    int
    34  	gets        int
    35  	puts        int
    36  	stats       int
    37  	cleanups    int
    38  	exists      int
    39  	init        int
    40  	metadata    int
    41  	diff        int
    42  	applydiff   int
    43  	changes     int
    44  	diffsize    int
    45  }
    46  
    47  func TestExternalGraphDriver(t *testing.T) {
    48  	skip.If(t, runtime.GOOS == "windows")
    49  	skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
    50  	skip.If(t, !requirement.HasHubConnectivity(t))
    51  
    52  	// Setup plugin(s)
    53  	ec := make(map[string]*graphEventsCounter)
    54  	sserver := setupPluginViaSpecFile(t, ec)
    55  	jserver := setupPluginViaJSONFile(t, ec)
    56  	// Create daemon
    57  	d := daemon.New(t, daemon.WithExperimental)
    58  	c := d.NewClientT(t)
    59  
    60  	for _, tc := range []struct {
    61  		name string
    62  		test func(client.APIClient, *daemon.Daemon) func(*testing.T)
    63  	}{
    64  		{
    65  			name: "json",
    66  			test: testExternalGraphDriver("json", ec),
    67  		},
    68  		{
    69  			name: "spec",
    70  			test: testExternalGraphDriver("spec", ec),
    71  		},
    72  		{
    73  			name: "pull",
    74  			test: testGraphDriverPull,
    75  		},
    76  	} {
    77  		t.Run(tc.name, tc.test(c, d))
    78  	}
    79  
    80  	sserver.Close()
    81  	jserver.Close()
    82  	err := os.RemoveAll("/etc/docker/plugins")
    83  	assert.NilError(t, err)
    84  }
    85  
    86  func setupPluginViaSpecFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
    87  	mux := http.NewServeMux()
    88  	server := httptest.NewServer(mux)
    89  
    90  	setupPlugin(t, ec, "spec", mux, []byte(server.URL))
    91  
    92  	return server
    93  }
    94  
    95  func setupPluginViaJSONFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
    96  	mux := http.NewServeMux()
    97  	server := httptest.NewServer(mux)
    98  
    99  	p := plugins.NewLocalPlugin("json-external-graph-driver", server.URL)
   100  	b, err := json.Marshal(p)
   101  	assert.NilError(t, err)
   102  
   103  	setupPlugin(t, ec, "json", mux, b)
   104  
   105  	return server
   106  }
   107  
   108  func setupPlugin(t *testing.T, ec map[string]*graphEventsCounter, ext string, mux *http.ServeMux, b []byte) {
   109  	name := fmt.Sprintf("%s-external-graph-driver", ext)
   110  	type graphDriverRequest struct {
   111  		ID         string `json:",omitempty"`
   112  		Parent     string `json:",omitempty"`
   113  		MountLabel string `json:",omitempty"`
   114  		ReadOnly   bool   `json:",omitempty"`
   115  	}
   116  
   117  	type graphDriverResponse struct {
   118  		Err      error             `json:",omitempty"`
   119  		Dir      string            `json:",omitempty"`
   120  		Exists   bool              `json:",omitempty"`
   121  		Status   [][2]string       `json:",omitempty"`
   122  		Metadata map[string]string `json:",omitempty"`
   123  		Changes  []archive.Change  `json:",omitempty"`
   124  		Size     int64             `json:",omitempty"`
   125  	}
   126  
   127  	respond := func(w http.ResponseWriter, data interface{}) {
   128  		w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
   129  		switch t := data.(type) {
   130  		case error:
   131  			fmt.Fprintln(w, fmt.Sprintf(`{"Err": %q}`, t.Error()))
   132  		case string:
   133  			fmt.Fprintln(w, t)
   134  		default:
   135  			json.NewEncoder(w).Encode(&data)
   136  		}
   137  	}
   138  
   139  	decReq := func(b io.ReadCloser, out interface{}, w http.ResponseWriter) error {
   140  		defer b.Close()
   141  		if err := json.NewDecoder(b).Decode(&out); err != nil {
   142  			http.Error(w, fmt.Sprintf("error decoding json: %s", err.Error()), 500)
   143  		}
   144  		return nil
   145  	}
   146  
   147  	base, err := ioutil.TempDir("", name)
   148  	assert.NilError(t, err)
   149  	vfsProto, err := vfs.Init(base, []string{}, nil, nil)
   150  	assert.NilError(t, err, "error initializing graph driver")
   151  	driver := graphdriver.NewNaiveDiffDriver(vfsProto, nil, nil)
   152  
   153  	ec[ext] = &graphEventsCounter{}
   154  	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
   155  		ec[ext].activations++
   156  		respond(w, `{"Implements": ["GraphDriver"]}`)
   157  	})
   158  
   159  	mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) {
   160  		ec[ext].init++
   161  		respond(w, "{}")
   162  	})
   163  
   164  	mux.HandleFunc("/GraphDriver.CreateReadWrite", func(w http.ResponseWriter, r *http.Request) {
   165  		ec[ext].creations++
   166  
   167  		var req graphDriverRequest
   168  		if err := decReq(r.Body, &req, w); err != nil {
   169  			return
   170  		}
   171  		if err := driver.CreateReadWrite(req.ID, req.Parent, nil); err != nil {
   172  			respond(w, err)
   173  			return
   174  		}
   175  		respond(w, "{}")
   176  	})
   177  
   178  	mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) {
   179  		ec[ext].creations++
   180  
   181  		var req graphDriverRequest
   182  		if err := decReq(r.Body, &req, w); err != nil {
   183  			return
   184  		}
   185  		if err := driver.Create(req.ID, req.Parent, nil); err != nil {
   186  			respond(w, err)
   187  			return
   188  		}
   189  		respond(w, "{}")
   190  	})
   191  
   192  	mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
   193  		ec[ext].removals++
   194  
   195  		var req graphDriverRequest
   196  		if err := decReq(r.Body, &req, w); err != nil {
   197  			return
   198  		}
   199  
   200  		if err := driver.Remove(req.ID); err != nil {
   201  			respond(w, err)
   202  			return
   203  		}
   204  		respond(w, "{}")
   205  	})
   206  
   207  	mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) {
   208  		ec[ext].gets++
   209  
   210  		var req graphDriverRequest
   211  		if err := decReq(r.Body, &req, w); err != nil {
   212  			return
   213  		}
   214  
   215  		// TODO @gupta-ak: Figure out what to do here.
   216  		dir, err := driver.Get(req.ID, req.MountLabel)
   217  		if err != nil {
   218  			respond(w, err)
   219  			return
   220  		}
   221  		respond(w, &graphDriverResponse{Dir: dir.Path()})
   222  	})
   223  
   224  	mux.HandleFunc("/GraphDriver.Put", func(w http.ResponseWriter, r *http.Request) {
   225  		ec[ext].puts++
   226  
   227  		var req graphDriverRequest
   228  		if err := decReq(r.Body, &req, w); err != nil {
   229  			return
   230  		}
   231  
   232  		if err := driver.Put(req.ID); err != nil {
   233  			respond(w, err)
   234  			return
   235  		}
   236  		respond(w, "{}")
   237  	})
   238  
   239  	mux.HandleFunc("/GraphDriver.Exists", func(w http.ResponseWriter, r *http.Request) {
   240  		ec[ext].exists++
   241  
   242  		var req graphDriverRequest
   243  		if err := decReq(r.Body, &req, w); err != nil {
   244  			return
   245  		}
   246  		respond(w, &graphDriverResponse{Exists: driver.Exists(req.ID)})
   247  	})
   248  
   249  	mux.HandleFunc("/GraphDriver.Status", func(w http.ResponseWriter, r *http.Request) {
   250  		ec[ext].stats++
   251  		respond(w, &graphDriverResponse{Status: driver.Status()})
   252  	})
   253  
   254  	mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) {
   255  		ec[ext].cleanups++
   256  		err := driver.Cleanup()
   257  		if err != nil {
   258  			respond(w, err)
   259  			return
   260  		}
   261  		respond(w, `{}`)
   262  	})
   263  
   264  	mux.HandleFunc("/GraphDriver.GetMetadata", func(w http.ResponseWriter, r *http.Request) {
   265  		ec[ext].metadata++
   266  
   267  		var req graphDriverRequest
   268  		if err := decReq(r.Body, &req, w); err != nil {
   269  			return
   270  		}
   271  
   272  		data, err := driver.GetMetadata(req.ID)
   273  		if err != nil {
   274  			respond(w, err)
   275  			return
   276  		}
   277  		respond(w, &graphDriverResponse{Metadata: data})
   278  	})
   279  
   280  	mux.HandleFunc("/GraphDriver.Diff", func(w http.ResponseWriter, r *http.Request) {
   281  		ec[ext].diff++
   282  
   283  		var req graphDriverRequest
   284  		if err := decReq(r.Body, &req, w); err != nil {
   285  			return
   286  		}
   287  
   288  		diff, err := driver.Diff(req.ID, req.Parent)
   289  		if err != nil {
   290  			respond(w, err)
   291  			return
   292  		}
   293  		io.Copy(w, diff)
   294  	})
   295  
   296  	mux.HandleFunc("/GraphDriver.Changes", func(w http.ResponseWriter, r *http.Request) {
   297  		ec[ext].changes++
   298  		var req graphDriverRequest
   299  		if err := decReq(r.Body, &req, w); err != nil {
   300  			return
   301  		}
   302  
   303  		changes, err := driver.Changes(req.ID, req.Parent)
   304  		if err != nil {
   305  			respond(w, err)
   306  			return
   307  		}
   308  		respond(w, &graphDriverResponse{Changes: changes})
   309  	})
   310  
   311  	mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) {
   312  		ec[ext].applydiff++
   313  		diff := r.Body
   314  		defer r.Body.Close()
   315  
   316  		id := r.URL.Query().Get("id")
   317  		parent := r.URL.Query().Get("parent")
   318  
   319  		if id == "" {
   320  			http.Error(w, fmt.Sprintf("missing id"), 409)
   321  		}
   322  
   323  		size, err := driver.ApplyDiff(id, parent, diff)
   324  		if err != nil {
   325  			respond(w, err)
   326  			return
   327  		}
   328  		respond(w, &graphDriverResponse{Size: size})
   329  	})
   330  
   331  	mux.HandleFunc("/GraphDriver.DiffSize", func(w http.ResponseWriter, r *http.Request) {
   332  		ec[ext].diffsize++
   333  
   334  		var req graphDriverRequest
   335  		if err := decReq(r.Body, &req, w); err != nil {
   336  			return
   337  		}
   338  
   339  		size, err := driver.DiffSize(req.ID, req.Parent)
   340  		if err != nil {
   341  			respond(w, err)
   342  			return
   343  		}
   344  		respond(w, &graphDriverResponse{Size: size})
   345  	})
   346  
   347  	err = os.MkdirAll("/etc/docker/plugins", 0755)
   348  	assert.NilError(t, err)
   349  
   350  	specFile := "/etc/docker/plugins/" + name + "." + ext
   351  	err = ioutil.WriteFile(specFile, b, 0644)
   352  	assert.NilError(t, err)
   353  }
   354  
   355  func testExternalGraphDriver(ext string, ec map[string]*graphEventsCounter) func(client.APIClient, *daemon.Daemon) func(*testing.T) {
   356  	return func(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
   357  		return func(t *testing.T) {
   358  			driverName := fmt.Sprintf("%s-external-graph-driver", ext)
   359  			d.StartWithBusybox(t, "-s", driverName)
   360  
   361  			ctx := context.Background()
   362  
   363  			testGraphDriver(t, c, ctx, driverName, func(t *testing.T) {
   364  				d.Restart(t, "-s", driverName)
   365  			})
   366  
   367  			_, err := c.Info(ctx)
   368  			assert.NilError(t, err)
   369  
   370  			d.Stop(t)
   371  
   372  			// Don't check ec.exists, because the daemon no longer calls the
   373  			// Exists function.
   374  			assert.Check(t, is.Equal(ec[ext].activations, 2))
   375  			assert.Check(t, is.Equal(ec[ext].init, 2))
   376  			assert.Check(t, ec[ext].creations >= 1)
   377  			assert.Check(t, ec[ext].removals >= 1)
   378  			assert.Check(t, ec[ext].gets >= 1)
   379  			assert.Check(t, ec[ext].puts >= 1)
   380  			assert.Check(t, is.Equal(ec[ext].stats, 5))
   381  			assert.Check(t, is.Equal(ec[ext].cleanups, 2))
   382  			assert.Check(t, ec[ext].applydiff >= 1)
   383  			assert.Check(t, is.Equal(ec[ext].changes, 1))
   384  			assert.Check(t, is.Equal(ec[ext].diffsize, 0))
   385  			assert.Check(t, is.Equal(ec[ext].diff, 0))
   386  			assert.Check(t, is.Equal(ec[ext].metadata, 1))
   387  		}
   388  	}
   389  }
   390  
   391  func testGraphDriverPull(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
   392  	return func(t *testing.T) {
   393  		d.Start(t)
   394  		defer d.Stop(t)
   395  		ctx := context.Background()
   396  
   397  		r, err := c.ImagePull(ctx, "busybox:latest@sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0", types.ImagePullOptions{})
   398  		assert.NilError(t, err)
   399  		_, err = io.Copy(ioutil.Discard, r)
   400  		assert.NilError(t, err)
   401  
   402  		container.Run(t, ctx, c, container.WithImage("busybox:latest@sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0"))
   403  	}
   404  }
   405  
   406  func TestGraphdriverPluginV2(t *testing.T) {
   407  	skip.If(t, runtime.GOOS == "windows")
   408  	skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
   409  	skip.If(t, !requirement.HasHubConnectivity(t))
   410  	skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
   411  	skip.If(t, !requirement.Overlay2Supported(testEnv.DaemonInfo.KernelVersion))
   412  
   413  	d := daemon.New(t, daemon.WithExperimental)
   414  	d.Start(t)
   415  	defer d.Stop(t)
   416  
   417  	client := d.NewClientT(t)
   418  	defer client.Close()
   419  	ctx := context.Background()
   420  
   421  	// install the plugin
   422  	plugin := "cpuguy83/docker-overlay2-graphdriver-plugin"
   423  	responseReader, err := client.PluginInstall(ctx, plugin, types.PluginInstallOptions{
   424  		RemoteRef:            plugin,
   425  		AcceptAllPermissions: true,
   426  	})
   427  	defer responseReader.Close()
   428  	assert.NilError(t, err)
   429  	// ensure it's done by waiting for EOF on the response
   430  	_, err = io.Copy(ioutil.Discard, responseReader)
   431  	assert.NilError(t, err)
   432  
   433  	// restart the daemon with the plugin set as the storage driver
   434  	d.Stop(t)
   435  	d.StartWithBusybox(t, "-s", plugin, "--storage-opt", "overlay2.override_kernel_check=1")
   436  
   437  	testGraphDriver(t, client, ctx, plugin, nil)
   438  }
   439  
   440  // nolint: golint
   441  func testGraphDriver(t *testing.T, c client.APIClient, ctx context.Context, driverName string, afterContainerRunFn func(*testing.T)) { //nolint: golint
   442  	id := container.Run(t, ctx, c, container.WithCmd("sh", "-c", "echo hello > /hello"))
   443  
   444  	if afterContainerRunFn != nil {
   445  		afterContainerRunFn(t)
   446  	}
   447  
   448  	i, err := c.ContainerInspect(ctx, id)
   449  	assert.NilError(t, err)
   450  	assert.Check(t, is.Equal(i.GraphDriver.Name, driverName))
   451  
   452  	diffs, err := c.ContainerDiff(ctx, id)
   453  	assert.NilError(t, err)
   454  	assert.Check(t, is.Contains(diffs, containertypes.ContainerChangeResponseItem{
   455  		Kind: archive.ChangeAdd,
   456  		Path: "/hello",
   457  	}), "diffs: %v", diffs)
   458  
   459  	err = c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
   460  		Force: true,
   461  	})
   462  	assert.NilError(t, err)
   463  }