github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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/plugins" 23 "github.com/docker/docker/testutil/daemon" 24 "gotest.tools/v3/assert" 25 is "gotest.tools/v3/assert/cmp" 26 "gotest.tools/v3/skip" 27 ) 28 29 type graphEventsCounter struct { 30 activations int 31 creations int 32 removals int 33 gets int 34 puts int 35 stats int 36 cleanups int 37 exists int 38 init int 39 metadata int 40 diff int 41 applydiff int 42 changes int 43 diffsize int 44 } 45 46 func TestExternalGraphDriver(t *testing.T) { 47 skip.If(t, runtime.GOOS == "windows") 48 skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon") 49 skip.If(t, !requirement.HasHubConnectivity(t)) 50 skip.If(t, testEnv.IsRootless, "rootless mode doesn't support external graph driver") 51 52 // Setup plugin(s) 53 ec := make(map[string]*graphEventsCounter) 54 sserver := setupPluginViaSpecFile(t, ec) 55 jserver := setupPluginViaJSONFile(t, ec) 56 // Create daemon 57 d := daemon.New(t, daemon.WithExperimental()) 58 c := d.NewClientT(t) 59 60 for _, tc := range []struct { 61 name string 62 test func(client.APIClient, *daemon.Daemon) func(*testing.T) 63 }{ 64 { 65 name: "json", 66 test: testExternalGraphDriver("json", ec), 67 }, 68 { 69 name: "spec", 70 test: testExternalGraphDriver("spec", ec), 71 }, 72 { 73 name: "pull", 74 test: testGraphDriverPull, 75 }, 76 } { 77 t.Run(tc.name, tc.test(c, d)) 78 } 79 80 sserver.Close() 81 jserver.Close() 82 err := os.RemoveAll("/etc/docker/plugins") 83 assert.NilError(t, err) 84 } 85 86 func setupPluginViaSpecFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server { 87 mux := http.NewServeMux() 88 server := httptest.NewServer(mux) 89 90 setupPlugin(t, ec, "spec", mux, []byte(server.URL)) 91 92 return server 93 } 94 95 func setupPluginViaJSONFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server { 96 mux := http.NewServeMux() 97 server := httptest.NewServer(mux) 98 99 p := plugins.NewLocalPlugin("json-external-graph-driver", server.URL) 100 b, err := json.Marshal(p) 101 assert.NilError(t, err) 102 103 setupPlugin(t, ec, "json", mux, b) 104 105 return server 106 } 107 108 func setupPlugin(t *testing.T, ec map[string]*graphEventsCounter, ext string, mux *http.ServeMux, b []byte) { 109 name := fmt.Sprintf("%s-external-graph-driver", ext) 110 type graphDriverRequest struct { 111 ID string `json:",omitempty"` 112 Parent string `json:",omitempty"` 113 MountLabel string `json:",omitempty"` 114 ReadOnly bool `json:",omitempty"` 115 } 116 117 type graphDriverResponse struct { 118 Err error `json:",omitempty"` 119 Dir string `json:",omitempty"` 120 Exists bool `json:",omitempty"` 121 Status [][2]string `json:",omitempty"` 122 Metadata map[string]string `json:",omitempty"` 123 Changes []archive.Change `json:",omitempty"` 124 Size int64 `json:",omitempty"` 125 } 126 127 respond := func(w http.ResponseWriter, data interface{}) { 128 w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") 129 switch t := data.(type) { 130 case error: 131 fmt.Fprintf(w, "{\"Err\": %q}\n", t.Error()) 132 case string: 133 fmt.Fprintln(w, t) 134 default: 135 json.NewEncoder(w).Encode(&data) 136 } 137 } 138 139 decReq := func(b io.ReadCloser, out interface{}, w http.ResponseWriter) error { 140 defer b.Close() 141 if err := json.NewDecoder(b).Decode(&out); err != nil { 142 http.Error(w, fmt.Sprintf("error decoding json: %s", err.Error()), 500) 143 } 144 return nil 145 } 146 147 base, err := os.MkdirTemp("", name) 148 assert.NilError(t, err) 149 vfsProto, err := vfs.Init(base, []string{}, nil, nil) 150 assert.NilError(t, err, "error initializing graph driver") 151 driver := graphdriver.NewNaiveDiffDriver(vfsProto, nil, nil) 152 153 ec[ext] = &graphEventsCounter{} 154 mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { 155 ec[ext].activations++ 156 respond(w, `{"Implements": ["GraphDriver"]}`) 157 }) 158 159 mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) { 160 ec[ext].init++ 161 respond(w, "{}") 162 }) 163 164 mux.HandleFunc("/GraphDriver.CreateReadWrite", func(w http.ResponseWriter, r *http.Request) { 165 ec[ext].creations++ 166 167 var req graphDriverRequest 168 if err := decReq(r.Body, &req, w); err != nil { 169 return 170 } 171 if err := driver.CreateReadWrite(req.ID, req.Parent, nil); err != nil { 172 respond(w, err) 173 return 174 } 175 respond(w, "{}") 176 }) 177 178 mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) { 179 ec[ext].creations++ 180 181 var req graphDriverRequest 182 if err := decReq(r.Body, &req, w); err != nil { 183 return 184 } 185 if err := driver.Create(req.ID, req.Parent, nil); err != nil { 186 respond(w, err) 187 return 188 } 189 respond(w, "{}") 190 }) 191 192 mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) { 193 ec[ext].removals++ 194 195 var req graphDriverRequest 196 if err := decReq(r.Body, &req, w); err != nil { 197 return 198 } 199 200 if err := driver.Remove(req.ID); err != nil { 201 respond(w, err) 202 return 203 } 204 respond(w, "{}") 205 }) 206 207 mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) { 208 ec[ext].gets++ 209 210 var req graphDriverRequest 211 if err := decReq(r.Body, &req, w); err != nil { 212 return 213 } 214 215 // TODO @gupta-ak: Figure out what to do here. 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.Path()}) 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, "--storage-opt", "overlay2.override_kernel_check=1") 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 }