external_test.go 12 KB

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