external_test.go 12 KB

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