external_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. package graphdriver
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "net/http"
  9. "net/http/httptest"
  10. "os"
  11. "runtime"
  12. "testing"
  13. "github.com/docker/docker/api/types"
  14. containertypes "github.com/docker/docker/api/types/container"
  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/plugins"
  22. "github.com/docker/docker/testutil/daemon"
  23. "gotest.tools/assert"
  24. is "gotest.tools/assert/cmp"
  25. "gotest.tools/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. // Setup plugin(s)
  48. ec := make(map[string]*graphEventsCounter)
  49. sserver := setupPluginViaSpecFile(t, ec)
  50. jserver := setupPluginViaJSONFile(t, ec)
  51. // Create daemon
  52. d := daemon.New(t, daemon.WithExperimental)
  53. c := d.NewClientT(t)
  54. for _, tc := range []struct {
  55. name string
  56. test func(client.APIClient, *daemon.Daemon) func(*testing.T)
  57. }{
  58. {
  59. name: "json",
  60. test: testExternalGraphDriver("json", ec),
  61. },
  62. {
  63. name: "spec",
  64. test: testExternalGraphDriver("spec", ec),
  65. },
  66. {
  67. name: "pull",
  68. test: testGraphDriverPull,
  69. },
  70. } {
  71. t.Run(tc.name, tc.test(c, d))
  72. }
  73. sserver.Close()
  74. jserver.Close()
  75. err := os.RemoveAll("/etc/docker/plugins")
  76. assert.NilError(t, err)
  77. }
  78. func setupPluginViaSpecFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
  79. mux := http.NewServeMux()
  80. server := httptest.NewServer(mux)
  81. setupPlugin(t, ec, "spec", mux, []byte(server.URL))
  82. return server
  83. }
  84. func setupPluginViaJSONFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
  85. mux := http.NewServeMux()
  86. server := httptest.NewServer(mux)
  87. p := plugins.NewLocalPlugin("json-external-graph-driver", server.URL)
  88. b, err := json.Marshal(p)
  89. assert.NilError(t, err)
  90. setupPlugin(t, ec, "json", mux, b)
  91. return server
  92. }
  93. func setupPlugin(t *testing.T, ec map[string]*graphEventsCounter, ext string, mux *http.ServeMux, b []byte) {
  94. name := fmt.Sprintf("%s-external-graph-driver", ext)
  95. type graphDriverRequest struct {
  96. ID string `json:",omitempty"`
  97. Parent string `json:",omitempty"`
  98. MountLabel string `json:",omitempty"`
  99. ReadOnly bool `json:",omitempty"`
  100. }
  101. type graphDriverResponse struct {
  102. Err error `json:",omitempty"`
  103. Dir string `json:",omitempty"`
  104. Exists bool `json:",omitempty"`
  105. Status [][2]string `json:",omitempty"`
  106. Metadata map[string]string `json:",omitempty"`
  107. Changes []archive.Change `json:",omitempty"`
  108. Size int64 `json:",omitempty"`
  109. }
  110. respond := func(w http.ResponseWriter, data interface{}) {
  111. w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
  112. switch t := data.(type) {
  113. case error:
  114. fmt.Fprintln(w, fmt.Sprintf(`{"Err": %q}`, t.Error()))
  115. case string:
  116. fmt.Fprintln(w, t)
  117. default:
  118. json.NewEncoder(w).Encode(&data)
  119. }
  120. }
  121. decReq := func(b io.ReadCloser, out interface{}, w http.ResponseWriter) error {
  122. defer b.Close()
  123. if err := json.NewDecoder(b).Decode(&out); err != nil {
  124. http.Error(w, fmt.Sprintf("error decoding json: %s", err.Error()), 500)
  125. }
  126. return nil
  127. }
  128. base, err := ioutil.TempDir("", name)
  129. assert.NilError(t, err)
  130. vfsProto, err := vfs.Init(base, []string{}, nil, nil)
  131. assert.NilError(t, err, "error initializing graph driver")
  132. driver := graphdriver.NewNaiveDiffDriver(vfsProto, nil, nil)
  133. ec[ext] = &graphEventsCounter{}
  134. mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
  135. ec[ext].activations++
  136. respond(w, `{"Implements": ["GraphDriver"]}`)
  137. })
  138. mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) {
  139. ec[ext].init++
  140. respond(w, "{}")
  141. })
  142. mux.HandleFunc("/GraphDriver.CreateReadWrite", func(w http.ResponseWriter, r *http.Request) {
  143. ec[ext].creations++
  144. var req graphDriverRequest
  145. if err := decReq(r.Body, &req, w); err != nil {
  146. return
  147. }
  148. if err := driver.CreateReadWrite(req.ID, req.Parent, nil); err != nil {
  149. respond(w, err)
  150. return
  151. }
  152. respond(w, "{}")
  153. })
  154. mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) {
  155. ec[ext].creations++
  156. var req graphDriverRequest
  157. if err := decReq(r.Body, &req, w); err != nil {
  158. return
  159. }
  160. if err := driver.Create(req.ID, req.Parent, nil); err != nil {
  161. respond(w, err)
  162. return
  163. }
  164. respond(w, "{}")
  165. })
  166. mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
  167. ec[ext].removals++
  168. var req graphDriverRequest
  169. if err := decReq(r.Body, &req, w); err != nil {
  170. return
  171. }
  172. if err := driver.Remove(req.ID); err != nil {
  173. respond(w, err)
  174. return
  175. }
  176. respond(w, "{}")
  177. })
  178. mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) {
  179. ec[ext].gets++
  180. var req graphDriverRequest
  181. if err := decReq(r.Body, &req, w); err != nil {
  182. return
  183. }
  184. // TODO @gupta-ak: Figure out what to do here.
  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.Path()})
  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, fmt.Sprintf("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 = ioutil.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:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0", types.ImagePullOptions{})
  335. assert.NilError(t, err)
  336. _, err = io.Copy(ioutil.Discard, r)
  337. assert.NilError(t, err)
  338. container.Run(ctx, t, c, container.WithImage("busybox:latest@sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0"))
  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(ioutil.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, "--storage-opt", "overlay2.override_kernel_check=1")
  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. }