external_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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, testEnv.UsingSnapshotter())
  45. skip.If(t, runtime.GOOS == "windows")
  46. skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
  47. skip.If(t, !requirement.HasHubConnectivity(t))
  48. skip.If(t, testEnv.IsRootless, "rootless mode doesn't support external graph driver")
  49. // Setup plugin(s)
  50. ec := make(map[string]*graphEventsCounter)
  51. sserver := setupPluginViaSpecFile(t, ec)
  52. jserver := setupPluginViaJSONFile(t, ec)
  53. // Create daemon
  54. d := daemon.New(t, daemon.WithExperimental())
  55. c := d.NewClientT(t)
  56. for _, tc := range []struct {
  57. name string
  58. test func(client.APIClient, *daemon.Daemon) func(*testing.T)
  59. }{
  60. {
  61. name: "json",
  62. test: testExternalGraphDriver("json", ec),
  63. },
  64. {
  65. name: "spec",
  66. test: testExternalGraphDriver("spec", ec),
  67. },
  68. {
  69. name: "pull",
  70. test: testGraphDriverPull,
  71. },
  72. } {
  73. t.Run(tc.name, tc.test(c, d))
  74. }
  75. sserver.Close()
  76. jserver.Close()
  77. err := os.RemoveAll("/etc/docker/plugins")
  78. assert.NilError(t, err)
  79. }
  80. func setupPluginViaSpecFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
  81. mux := http.NewServeMux()
  82. server := httptest.NewServer(mux)
  83. setupPlugin(t, ec, "spec", mux, []byte(server.URL))
  84. return server
  85. }
  86. func setupPluginViaJSONFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
  87. mux := http.NewServeMux()
  88. server := httptest.NewServer(mux)
  89. p := plugins.NewLocalPlugin("json-external-graph-driver", server.URL)
  90. b, err := json.Marshal(p)
  91. assert.NilError(t, err)
  92. setupPlugin(t, ec, "json", mux, b)
  93. return server
  94. }
  95. func setupPlugin(t *testing.T, ec map[string]*graphEventsCounter, ext string, mux *http.ServeMux, b []byte) {
  96. name := fmt.Sprintf("%s-external-graph-driver", ext)
  97. type graphDriverRequest struct {
  98. ID string `json:",omitempty"`
  99. Parent string `json:",omitempty"`
  100. MountLabel string `json:",omitempty"`
  101. ReadOnly bool `json:",omitempty"`
  102. }
  103. type graphDriverResponse struct {
  104. Err error `json:",omitempty"`
  105. Dir string `json:",omitempty"`
  106. Exists bool `json:",omitempty"`
  107. Status [][2]string `json:",omitempty"`
  108. Metadata map[string]string `json:",omitempty"`
  109. Changes []archive.Change `json:",omitempty"`
  110. Size int64 `json:",omitempty"`
  111. }
  112. respond := func(w http.ResponseWriter, data interface{}) {
  113. w.Header().Set("Content-Type", plugins.VersionMimetype)
  114. switch t := data.(type) {
  115. case error:
  116. fmt.Fprintf(w, "{\"Err\": %q}\n", t.Error())
  117. case string:
  118. fmt.Fprintln(w, t)
  119. default:
  120. json.NewEncoder(w).Encode(&data)
  121. }
  122. }
  123. decReq := func(b io.ReadCloser, out interface{}, w http.ResponseWriter) error {
  124. defer b.Close()
  125. if err := json.NewDecoder(b).Decode(&out); err != nil {
  126. http.Error(w, fmt.Sprintf("error decoding json: %s", err.Error()), 500)
  127. }
  128. return nil
  129. }
  130. base, err := os.MkdirTemp("", name)
  131. assert.NilError(t, err)
  132. vfsProto, err := vfs.Init(base, []string{}, idtools.IdentityMapping{})
  133. assert.NilError(t, err, "error initializing graph driver")
  134. driver := graphdriver.NewNaiveDiffDriver(vfsProto, idtools.IdentityMapping{})
  135. ec[ext] = &graphEventsCounter{}
  136. mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
  137. ec[ext].activations++
  138. respond(w, `{"Implements": ["GraphDriver"]}`)
  139. })
  140. mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) {
  141. ec[ext].init++
  142. respond(w, "{}")
  143. })
  144. mux.HandleFunc("/GraphDriver.CreateReadWrite", func(w http.ResponseWriter, r *http.Request) {
  145. ec[ext].creations++
  146. var req graphDriverRequest
  147. if err := decReq(r.Body, &req, w); err != nil {
  148. return
  149. }
  150. if err := driver.CreateReadWrite(req.ID, req.Parent, nil); err != nil {
  151. respond(w, err)
  152. return
  153. }
  154. respond(w, "{}")
  155. })
  156. mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) {
  157. ec[ext].creations++
  158. var req graphDriverRequest
  159. if err := decReq(r.Body, &req, w); err != nil {
  160. return
  161. }
  162. if err := driver.Create(req.ID, req.Parent, nil); err != nil {
  163. respond(w, err)
  164. return
  165. }
  166. respond(w, "{}")
  167. })
  168. mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
  169. ec[ext].removals++
  170. var req graphDriverRequest
  171. if err := decReq(r.Body, &req, w); err != nil {
  172. return
  173. }
  174. if err := driver.Remove(req.ID); err != nil {
  175. respond(w, err)
  176. return
  177. }
  178. respond(w, "{}")
  179. })
  180. mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) {
  181. ec[ext].gets++
  182. var req graphDriverRequest
  183. if err := decReq(r.Body, &req, w); err != nil {
  184. return
  185. }
  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", 0o755)
  295. assert.NilError(t, err)
  296. specFile := "/etc/docker/plugins/" + name + "." + ext
  297. err = os.WriteFile(specFile, b, 0o644)
  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, testEnv.UsingSnapshotter())
  344. skip.If(t, runtime.GOOS == "windows")
  345. skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
  346. skip.If(t, !requirement.HasHubConnectivity(t))
  347. skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
  348. skip.If(t, !requirement.Overlay2Supported(testEnv.DaemonInfo.KernelVersion))
  349. d := daemon.New(t, daemon.WithExperimental())
  350. d.Start(t)
  351. defer d.Stop(t)
  352. client := d.NewClientT(t)
  353. defer client.Close()
  354. ctx := context.Background()
  355. // install the plugin
  356. plugin := "cpuguy83/docker-overlay2-graphdriver-plugin"
  357. responseReader, err := client.PluginInstall(ctx, plugin, types.PluginInstallOptions{
  358. RemoteRef: plugin,
  359. AcceptAllPermissions: true,
  360. })
  361. assert.NilError(t, err)
  362. defer responseReader.Close()
  363. // ensure it's done by waiting for EOF on the response
  364. _, err = io.Copy(io.Discard, responseReader)
  365. assert.NilError(t, err)
  366. // restart the daemon with the plugin set as the storage driver
  367. d.Stop(t)
  368. d.StartWithBusybox(t, "-s", plugin)
  369. testGraphDriver(ctx, t, client, plugin, nil)
  370. }
  371. func testGraphDriver(ctx context.Context, t *testing.T, c client.APIClient, driverName string, afterContainerRunFn func(*testing.T)) {
  372. id := container.Run(ctx, t, c, container.WithCmd("sh", "-c", "echo hello > /hello"))
  373. if afterContainerRunFn != nil {
  374. afterContainerRunFn(t)
  375. }
  376. i, err := c.ContainerInspect(ctx, id)
  377. assert.NilError(t, err)
  378. assert.Check(t, is.Equal(i.GraphDriver.Name, driverName))
  379. diffs, err := c.ContainerDiff(ctx, id)
  380. assert.NilError(t, err)
  381. assert.Check(t, is.Contains(diffs, containertypes.FilesystemChange{
  382. Kind: containertypes.ChangeAdd,
  383. Path: "/hello",
  384. }), "diffs: %v", diffs)
  385. err = c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
  386. Force: true,
  387. })
  388. assert.NilError(t, err)
  389. }