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