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 }