ipcmode_test.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. package container // import "github.com/docker/docker/integration/container"
  2. import (
  3. "bufio"
  4. "context"
  5. "io/ioutil"
  6. "os"
  7. "regexp"
  8. "strings"
  9. "testing"
  10. "github.com/docker/docker/api/types"
  11. containertypes "github.com/docker/docker/api/types/container"
  12. "github.com/docker/docker/integration/internal/container"
  13. "github.com/docker/docker/internal/test/daemon"
  14. "github.com/docker/docker/internal/test/request"
  15. "gotest.tools/assert"
  16. is "gotest.tools/assert/cmp"
  17. "gotest.tools/fs"
  18. "gotest.tools/skip"
  19. )
  20. // testIpcCheckDevExists checks whether a given mount (identified by its
  21. // major:minor pair from /proc/self/mountinfo) exists on the host system.
  22. //
  23. // The format of /proc/self/mountinfo is like:
  24. //
  25. // 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw
  26. // ^^^^\
  27. // - this is the minor:major we look for
  28. func testIpcCheckDevExists(mm string) (bool, error) {
  29. f, err := os.Open("/proc/self/mountinfo")
  30. if err != nil {
  31. return false, err
  32. }
  33. defer f.Close()
  34. s := bufio.NewScanner(f)
  35. for s.Scan() {
  36. fields := strings.Fields(s.Text())
  37. if len(fields) < 7 {
  38. continue
  39. }
  40. if fields[2] == mm {
  41. return true, nil
  42. }
  43. }
  44. return false, s.Err()
  45. }
  46. // testIpcNonePrivateShareable is a helper function to test "none",
  47. // "private" and "shareable" modes.
  48. func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool, mustBeShared bool) {
  49. defer setupTest(t)()
  50. cfg := containertypes.Config{
  51. Image: "busybox",
  52. Cmd: []string{"top"},
  53. }
  54. hostCfg := containertypes.HostConfig{
  55. IpcMode: containertypes.IpcMode(mode),
  56. }
  57. client := request.NewAPIClient(t)
  58. ctx := context.Background()
  59. resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
  60. assert.NilError(t, err)
  61. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  62. err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
  63. assert.NilError(t, err)
  64. // get major:minor pair for /dev/shm from container's /proc/self/mountinfo
  65. cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
  66. result, err := container.Exec(ctx, client, resp.ID, []string{"sh", "-c", cmd})
  67. assert.NilError(t, err)
  68. mm := result.Combined()
  69. if !mustBeMounted {
  70. assert.Check(t, is.Equal(mm, ""))
  71. // no more checks to perform
  72. return
  73. }
  74. assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm)))
  75. shared, err := testIpcCheckDevExists(mm)
  76. assert.NilError(t, err)
  77. t.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared)
  78. assert.Check(t, is.Equal(shared, mustBeShared))
  79. }
  80. // TestIpcModeNone checks the container "none" IPC mode
  81. // (--ipc none) works as expected. It makes sure there is no
  82. // /dev/shm mount inside the container.
  83. func TestIpcModeNone(t *testing.T) {
  84. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  85. testIpcNonePrivateShareable(t, "none", false, false)
  86. }
  87. // TestAPIIpcModePrivate checks the container private IPC mode
  88. // (--ipc private) works as expected. It gets the minor:major pair
  89. // of /dev/shm mount from the container, and makes sure there is no
  90. // such pair on the host.
  91. func TestIpcModePrivate(t *testing.T) {
  92. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  93. testIpcNonePrivateShareable(t, "private", true, false)
  94. }
  95. // TestAPIIpcModeShareable checks the container shareable IPC mode
  96. // (--ipc shareable) works as expected. It gets the minor:major pair
  97. // of /dev/shm mount from the container, and makes sure such pair
  98. // also exists on the host.
  99. func TestIpcModeShareable(t *testing.T) {
  100. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  101. testIpcNonePrivateShareable(t, "shareable", true, true)
  102. }
  103. // testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios
  104. func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
  105. t.Helper()
  106. defer setupTest(t)()
  107. cfg := containertypes.Config{
  108. Image: "busybox",
  109. Cmd: []string{"top"},
  110. }
  111. hostCfg := containertypes.HostConfig{
  112. IpcMode: containertypes.IpcMode(donorMode),
  113. }
  114. ctx := context.Background()
  115. client := request.NewAPIClient(t)
  116. // create and start the "donor" container
  117. resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
  118. assert.NilError(t, err)
  119. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  120. name1 := resp.ID
  121. err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{})
  122. assert.NilError(t, err)
  123. // create and start the second container
  124. hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
  125. resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
  126. assert.NilError(t, err)
  127. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  128. name2 := resp.ID
  129. err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{})
  130. if !mustWork {
  131. // start should fail with a specific error
  132. assert.Check(t, is.ErrorContains(err, "non-shareable IPC"))
  133. // no more checks to perform here
  134. return
  135. }
  136. // start should succeed
  137. assert.NilError(t, err)
  138. // check that IPC is shared
  139. // 1. create a file in the first container
  140. _, err = container.Exec(ctx, client, name1, []string{"sh", "-c", "printf covfefe > /dev/shm/bar"})
  141. assert.NilError(t, err)
  142. // 2. check it's the same file in the second one
  143. result, err := container.Exec(ctx, client, name2, []string{"cat", "/dev/shm/bar"})
  144. assert.NilError(t, err)
  145. out := result.Combined()
  146. assert.Check(t, is.Equal(true, regexp.MustCompile("^covfefe$").MatchString(out)))
  147. }
  148. // TestAPIIpcModeShareableAndPrivate checks that
  149. // 1) a container created with --ipc container:ID can use IPC of another shareable container.
  150. // 2) a container created with --ipc container:ID can NOT use IPC of another private container.
  151. func TestAPIIpcModeShareableAndContainer(t *testing.T) {
  152. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  153. testIpcContainer(t, "shareable", true)
  154. testIpcContainer(t, "private", false)
  155. }
  156. /* TestAPIIpcModeHost checks that a container created with --ipc host
  157. * can use IPC of the host system.
  158. */
  159. func TestAPIIpcModeHost(t *testing.T) {
  160. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon() || testEnv.IsUserNamespace())
  161. cfg := containertypes.Config{
  162. Image: "busybox",
  163. Cmd: []string{"top"},
  164. }
  165. hostCfg := containertypes.HostConfig{
  166. IpcMode: containertypes.IpcMode("host"),
  167. }
  168. ctx := context.Background()
  169. client := testEnv.APIClient()
  170. resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
  171. assert.NilError(t, err)
  172. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  173. name := resp.ID
  174. err = client.ContainerStart(ctx, name, types.ContainerStartOptions{})
  175. assert.NilError(t, err)
  176. // check that IPC is shared
  177. // 1. create a file inside container
  178. _, err = container.Exec(ctx, client, name, []string{"sh", "-c", "printf covfefe > /dev/shm/." + name})
  179. assert.NilError(t, err)
  180. // 2. check it's the same on the host
  181. bytes, err := ioutil.ReadFile("/dev/shm/." + name)
  182. assert.NilError(t, err)
  183. assert.Check(t, is.Equal("covfefe", string(bytes)))
  184. // 3. clean up
  185. _, err = container.Exec(ctx, client, name, []string{"rm", "-f", "/dev/shm/." + name})
  186. assert.NilError(t, err)
  187. }
  188. // testDaemonIpcPrivateShareable is a helper function to test "private" and "shareable" daemon default ipc modes.
  189. func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...string) {
  190. defer setupTest(t)()
  191. d := daemon.New(t)
  192. d.StartWithBusybox(t, arg...)
  193. defer d.Stop(t)
  194. client, err := d.NewClient()
  195. assert.Check(t, err, "error creating client")
  196. cfg := containertypes.Config{
  197. Image: "busybox",
  198. Cmd: []string{"top"},
  199. }
  200. ctx := context.Background()
  201. resp, err := client.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, "")
  202. assert.NilError(t, err)
  203. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  204. err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
  205. assert.NilError(t, err)
  206. // get major:minor pair for /dev/shm from container's /proc/self/mountinfo
  207. cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
  208. result, err := container.Exec(ctx, client, resp.ID, []string{"sh", "-c", cmd})
  209. assert.NilError(t, err)
  210. mm := result.Combined()
  211. assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm)))
  212. shared, err := testIpcCheckDevExists(mm)
  213. assert.NilError(t, err)
  214. t.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, shared: %v, mustBeShared: %v\n", mm, shared, mustBeShared)
  215. assert.Check(t, is.Equal(shared, mustBeShared))
  216. }
  217. // TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended.
  218. func TestDaemonIpcModeShareable(t *testing.T) {
  219. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  220. testDaemonIpcPrivateShareable(t, true, "--default-ipc-mode", "shareable")
  221. }
  222. // TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended.
  223. func TestDaemonIpcModePrivate(t *testing.T) {
  224. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  225. testDaemonIpcPrivateShareable(t, false, "--default-ipc-mode", "private")
  226. }
  227. // used to check if an IpcMode given in config works as intended
  228. func testDaemonIpcFromConfig(t *testing.T, mode string, mustExist bool) {
  229. config := `{"default-ipc-mode": "` + mode + `"}`
  230. file := fs.NewFile(t, "test-daemon-ipc-config", fs.WithContent(config))
  231. defer file.Remove()
  232. testDaemonIpcPrivateShareable(t, mustExist, "--config-file", file.Path())
  233. }
  234. // TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended.
  235. func TestDaemonIpcModePrivateFromConfig(t *testing.T) {
  236. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  237. testDaemonIpcFromConfig(t, "private", false)
  238. }
  239. // TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended.
  240. func TestDaemonIpcModeShareableFromConfig(t *testing.T) {
  241. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  242. testDaemonIpcFromConfig(t, "shareable", true)
  243. }