external_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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. dir, err := driver.Get(req.ID, req.MountLabel)
  186. if err != nil {
  187. respond(w, err)
  188. return
  189. }
  190. respond(w, &graphDriverResponse{Dir: dir})
  191. })
  192. mux.HandleFunc("/GraphDriver.Put", func(w http.ResponseWriter, r *http.Request) {
  193. ec[ext].puts++
  194. var req graphDriverRequest
  195. if err := decReq(r.Body, &req, w); err != nil {
  196. return
  197. }
  198. if err := driver.Put(req.ID); err != nil {
  199. respond(w, err)
  200. return
  201. }
  202. respond(w, "{}")
  203. })
  204. mux.HandleFunc("/GraphDriver.Exists", func(w http.ResponseWriter, r *http.Request) {
  205. ec[ext].exists++
  206. var req graphDriverRequest
  207. if err := decReq(r.Body, &req, w); err != nil {
  208. return
  209. }
  210. respond(w, &graphDriverResponse{Exists: driver.Exists(req.ID)})
  211. })
  212. mux.HandleFunc("/GraphDriver.Status", func(w http.ResponseWriter, r *http.Request) {
  213. ec[ext].stats++
  214. respond(w, &graphDriverResponse{Status: driver.Status()})
  215. })
  216. mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) {
  217. ec[ext].cleanups++
  218. err := driver.Cleanup()
  219. if err != nil {
  220. respond(w, err)
  221. return
  222. }
  223. respond(w, `{}`)
  224. })
  225. mux.HandleFunc("/GraphDriver.GetMetadata", func(w http.ResponseWriter, r *http.Request) {
  226. ec[ext].metadata++
  227. var req graphDriverRequest
  228. if err := decReq(r.Body, &req, w); err != nil {
  229. return
  230. }
  231. data, err := driver.GetMetadata(req.ID)
  232. if err != nil {
  233. respond(w, err)
  234. return
  235. }
  236. respond(w, &graphDriverResponse{Metadata: data})
  237. })
  238. mux.HandleFunc("/GraphDriver.Diff", func(w http.ResponseWriter, r *http.Request) {
  239. ec[ext].diff++
  240. var req graphDriverRequest
  241. if err := decReq(r.Body, &req, w); err != nil {
  242. return
  243. }
  244. diff, err := driver.Diff(req.ID, req.Parent)
  245. if err != nil {
  246. respond(w, err)
  247. return
  248. }
  249. io.Copy(w, diff)
  250. })
  251. mux.HandleFunc("/GraphDriver.Changes", func(w http.ResponseWriter, r *http.Request) {
  252. ec[ext].changes++
  253. var req graphDriverRequest
  254. if err := decReq(r.Body, &req, w); err != nil {
  255. return
  256. }
  257. changes, err := driver.Changes(req.ID, req.Parent)
  258. if err != nil {
  259. respond(w, err)
  260. return
  261. }
  262. respond(w, &graphDriverResponse{Changes: changes})
  263. })
  264. mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) {
  265. ec[ext].applydiff++
  266. diff := r.Body
  267. defer r.Body.Close()
  268. id := r.URL.Query().Get("id")
  269. parent := r.URL.Query().Get("parent")
  270. if id == "" {
  271. http.Error(w, "missing id", 409)
  272. }
  273. size, err := driver.ApplyDiff(id, parent, diff)
  274. if err != nil {
  275. respond(w, err)
  276. return
  277. }
  278. respond(w, &graphDriverResponse{Size: size})
  279. })
  280. mux.HandleFunc("/GraphDriver.DiffSize", func(w http.ResponseWriter, r *http.Request) {
  281. ec[ext].diffsize++
  282. var req graphDriverRequest
  283. if err := decReq(r.Body, &req, w); err != nil {
  284. return
  285. }
  286. size, err := driver.DiffSize(req.ID, req.Parent)
  287. if err != nil {
  288. respond(w, err)
  289. return
  290. }
  291. respond(w, &graphDriverResponse{Size: size})
  292. })
  293. err = os.MkdirAll("/etc/docker/plugins", 0755)
  294. assert.NilError(t, err)
  295. specFile := "/etc/docker/plugins/" + name + "." + ext
  296. err = os.WriteFile(specFile, b, 0644)
  297. assert.NilError(t, err)
  298. }
  299. func testExternalGraphDriver(ext string, ec map[string]*graphEventsCounter) func(client.APIClient, *daemon.Daemon) func(*testing.T) {
  300. return func(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
  301. return func(t *testing.T) {
  302. driverName := fmt.Sprintf("%s-external-graph-driver", ext)
  303. d.StartWithBusybox(t, "-s", driverName)
  304. ctx := context.Background()
  305. testGraphDriver(ctx, t, c, driverName, func(t *testing.T) {
  306. d.Restart(t, "-s", driverName)
  307. })
  308. _, err := c.Info(ctx)
  309. assert.NilError(t, err)
  310. d.Stop(t)
  311. // Don't check ec.exists, because the daemon no longer calls the
  312. // Exists function.
  313. assert.Check(t, is.Equal(ec[ext].activations, 2))
  314. assert.Check(t, is.Equal(ec[ext].init, 2))
  315. assert.Check(t, ec[ext].creations >= 1)
  316. assert.Check(t, ec[ext].removals >= 1)
  317. assert.Check(t, ec[ext].gets >= 1)
  318. assert.Check(t, ec[ext].puts >= 1)
  319. assert.Check(t, is.Equal(ec[ext].stats, 5))
  320. assert.Check(t, is.Equal(ec[ext].cleanups, 2))
  321. assert.Check(t, ec[ext].applydiff >= 1)
  322. assert.Check(t, is.Equal(ec[ext].changes, 1))
  323. assert.Check(t, is.Equal(ec[ext].diffsize, 0))
  324. assert.Check(t, is.Equal(ec[ext].diff, 0))
  325. assert.Check(t, is.Equal(ec[ext].metadata, 1))
  326. }
  327. }
  328. }
  329. func testGraphDriverPull(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
  330. return func(t *testing.T) {
  331. d.Start(t)
  332. defer d.Stop(t)
  333. ctx := context.Background()
  334. r, err := c.ImagePull(ctx, "busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209", types.ImagePullOptions{})
  335. assert.NilError(t, err)
  336. _, err = io.Copy(io.Discard, r)
  337. assert.NilError(t, err)
  338. container.Run(ctx, t, c, container.WithImage("busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209"))
  339. }
  340. }
  341. func TestGraphdriverPluginV2(t *testing.T) {
  342. skip.If(t, runtime.GOOS == "windows")
  343. skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
  344. skip.If(t, !requirement.HasHubConnectivity(t))
  345. skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
  346. skip.If(t, !requirement.Overlay2Supported(testEnv.DaemonInfo.KernelVersion))
  347. d := daemon.New(t, daemon.WithExperimental())
  348. d.Start(t)
  349. defer d.Stop(t)
  350. client := d.NewClientT(t)
  351. defer client.Close()
  352. ctx := context.Background()
  353. // install the plugin
  354. plugin := "cpuguy83/docker-overlay2-graphdriver-plugin"
  355. responseReader, err := client.PluginInstall(ctx, plugin, types.PluginInstallOptions{
  356. RemoteRef: plugin,
  357. AcceptAllPermissions: true,
  358. })
  359. assert.NilError(t, err)
  360. defer responseReader.Close()
  361. // ensure it's done by waiting for EOF on the response
  362. _, err = io.Copy(io.Discard, responseReader)
  363. assert.NilError(t, err)
  364. // restart the daemon with the plugin set as the storage driver
  365. d.Stop(t)
  366. d.StartWithBusybox(t, "-s", plugin)
  367. testGraphDriver(ctx, t, client, plugin, nil)
  368. }
  369. func testGraphDriver(ctx context.Context, t *testing.T, c client.APIClient, driverName string, afterContainerRunFn func(*testing.T)) {
  370. id := container.Run(ctx, t, c, container.WithCmd("sh", "-c", "echo hello > /hello"))
  371. if afterContainerRunFn != nil {
  372. afterContainerRunFn(t)
  373. }
  374. i, err := c.ContainerInspect(ctx, id)
  375. assert.NilError(t, err)
  376. assert.Check(t, is.Equal(i.GraphDriver.Name, driverName))
  377. diffs, err := c.ContainerDiff(ctx, id)
  378. assert.NilError(t, err)
  379. assert.Check(t, is.Contains(diffs, containertypes.ContainerChangeResponseItem{
  380. Kind: archive.ChangeAdd,
  381. Path: "/hello",
  382. }), "diffs: %v", diffs)
  383. err = c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
  384. Force: true,
  385. })
  386. assert.NilError(t, err)
  387. }