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