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 }