github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/integration/plugin/graphdriver/external_test.go (about)

     1  package graphdriver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"runtime"
    12  	"testing"
    13  
    14  	"github.com/docker/docker/api/types"
    15  	containertypes "github.com/docker/docker/api/types/container"
    16  	"github.com/docker/docker/client"
    17  	"github.com/docker/docker/daemon/graphdriver"
    18  	"github.com/docker/docker/daemon/graphdriver/vfs"
    19  	"github.com/docker/docker/integration/internal/container"
    20  	"github.com/docker/docker/integration/internal/requirement"
    21  	"github.com/docker/docker/pkg/archive"
    22  	"github.com/docker/docker/pkg/idtools"
    23  	"github.com/docker/docker/pkg/plugins"
    24  	"github.com/docker/docker/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.Fprintf(w, "{\"Err\": %q}\n", 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 := os.MkdirTemp("", name)
   149  	assert.NilError(t, err)
   150  	vfsProto, err := vfs.Init(base, []string{}, idtools.IdentityMapping{})
   151  	assert.NilError(t, err, "error initializing graph driver")
   152  	driver := graphdriver.NewNaiveDiffDriver(vfsProto, idtools.IdentityMapping{})
   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  		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})
   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, "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 = os.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(ctx, t, c, 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:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209", types.ImagePullOptions{})
   398  		assert.NilError(t, err)
   399  		_, err = io.Copy(io.Discard, r)
   400  		assert.NilError(t, err)
   401  
   402  		container.Run(ctx, t, c, container.WithImage("busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209"))
   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  	assert.NilError(t, err)
   428  	defer responseReader.Close()
   429  	// ensure it's done by waiting for EOF on the response
   430  	_, err = io.Copy(io.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)
   436  
   437  	testGraphDriver(ctx, t, client, plugin, nil)
   438  }
   439  
   440  func testGraphDriver(ctx context.Context, t *testing.T, c client.APIClient, driverName string, afterContainerRunFn func(*testing.T)) {
   441  	id := container.Run(ctx, t, c, container.WithCmd("sh", "-c", "echo hello > /hello"))
   442  
   443  	if afterContainerRunFn != nil {
   444  		afterContainerRunFn(t)
   445  	}
   446  
   447  	i, err := c.ContainerInspect(ctx, id)
   448  	assert.NilError(t, err)
   449  	assert.Check(t, is.Equal(i.GraphDriver.Name, driverName))
   450  
   451  	diffs, err := c.ContainerDiff(ctx, id)
   452  	assert.NilError(t, err)
   453  	assert.Check(t, is.Contains(diffs, containertypes.ContainerChangeResponseItem{
   454  		Kind: archive.ChangeAdd,
   455  		Path: "/hello",
   456  	}), "diffs: %v", diffs)
   457  
   458  	err = c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
   459  		Force: true,
   460  	})
   461  	assert.NilError(t, err)
   462  }