github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/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/internal/test/daemon" 23 "github.com/docker/docker/pkg/archive" 24 "github.com/docker/docker/pkg/plugins" 25 "gotest.tools/assert" 26 is "gotest.tools/assert/cmp" 27 "gotest.tools/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 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.Fprintln(w, fmt.Sprintf(`{"Err": %q}`, 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 := ioutil.TempDir("", 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, fmt.Sprintf("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 = ioutil.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(t, c, ctx, 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:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0", types.ImagePullOptions{}) 398 assert.NilError(t, err) 399 _, err = io.Copy(ioutil.Discard, r) 400 assert.NilError(t, err) 401 402 container.Run(t, ctx, c, container.WithImage("busybox:latest@sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0")) 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 defer responseReader.Close() 428 assert.NilError(t, err) 429 // ensure it's done by waiting for EOF on the response 430 _, err = io.Copy(ioutil.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(t, client, ctx, plugin, nil) 438 } 439 440 // nolint: golint 441 func testGraphDriver(t *testing.T, c client.APIClient, ctx context.Context, driverName string, afterContainerRunFn func(*testing.T)) { //nolint: golint 442 id := container.Run(t, ctx, 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 }