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