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