external_test.go 12 KB

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