ipcmode_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package container // import "github.com/docker/docker/integration/container"
  2. import (
  3. "bufio"
  4. "context"
  5. "os"
  6. "regexp"
  7. "strings"
  8. "testing"
  9. "github.com/docker/docker/api/types"
  10. containertypes "github.com/docker/docker/api/types/container"
  11. "github.com/docker/docker/integration/internal/container"
  12. "github.com/docker/docker/internal/test/request"
  13. "gotest.tools/assert"
  14. is "gotest.tools/assert/cmp"
  15. "gotest.tools/skip"
  16. )
  17. // testIpcCheckDevExists checks whether a given mount (identified by its
  18. // major:minor pair from /proc/self/mountinfo) exists on the host system.
  19. //
  20. // The format of /proc/self/mountinfo is like:
  21. //
  22. // 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw
  23. // ^^^^\
  24. // - this is the minor:major we look for
  25. func testIpcCheckDevExists(mm string) (bool, error) {
  26. f, err := os.Open("/proc/self/mountinfo")
  27. if err != nil {
  28. return false, err
  29. }
  30. defer f.Close()
  31. s := bufio.NewScanner(f)
  32. for s.Scan() {
  33. fields := strings.Fields(s.Text())
  34. if len(fields) < 7 {
  35. continue
  36. }
  37. if fields[2] == mm {
  38. return true, nil
  39. }
  40. }
  41. return false, s.Err()
  42. }
  43. // testIpcNonePrivateShareable is a helper function to test "none",
  44. // "private" and "shareable" modes.
  45. func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool, mustBeShared bool) {
  46. defer setupTest(t)()
  47. cfg := containertypes.Config{
  48. Image: "busybox",
  49. Cmd: []string{"top"},
  50. }
  51. hostCfg := containertypes.HostConfig{
  52. IpcMode: containertypes.IpcMode(mode),
  53. }
  54. client := request.NewAPIClient(t)
  55. ctx := context.Background()
  56. resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
  57. assert.NilError(t, err)
  58. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  59. err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
  60. assert.NilError(t, err)
  61. // get major:minor pair for /dev/shm from container's /proc/self/mountinfo
  62. cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
  63. result, err := container.Exec(ctx, client, resp.ID, []string{"sh", "-c", cmd})
  64. assert.NilError(t, err)
  65. mm := result.Combined()
  66. if !mustBeMounted {
  67. assert.Check(t, is.Equal(mm, ""))
  68. // no more checks to perform
  69. return
  70. }
  71. assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm)))
  72. shared, err := testIpcCheckDevExists(mm)
  73. assert.NilError(t, err)
  74. t.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared)
  75. assert.Check(t, is.Equal(shared, mustBeShared))
  76. }
  77. // TestIpcModeNone checks the container "none" IPC mode
  78. // (--ipc none) works as expected. It makes sure there is no
  79. // /dev/shm mount inside the container.
  80. func TestIpcModeNone(t *testing.T) {
  81. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  82. testIpcNonePrivateShareable(t, "none", false, false)
  83. }
  84. // TestAPIIpcModePrivate checks the container private IPC mode
  85. // (--ipc private) works as expected. It gets the minor:major pair
  86. // of /dev/shm mount from the container, and makes sure there is no
  87. // such pair on the host.
  88. func TestIpcModePrivate(t *testing.T) {
  89. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  90. testIpcNonePrivateShareable(t, "private", true, false)
  91. }
  92. // TestAPIIpcModeShareable checks the container shareable IPC mode
  93. // (--ipc shareable) works as expected. It gets the minor:major pair
  94. // of /dev/shm mount from the container, and makes sure such pair
  95. // also exists on the host.
  96. func TestIpcModeShareable(t *testing.T) {
  97. skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
  98. testIpcNonePrivateShareable(t, "shareable", true, true)
  99. }
  100. // testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios
  101. func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
  102. t.Helper()
  103. defer setupTest(t)()
  104. cfg := containertypes.Config{
  105. Image: "busybox",
  106. Cmd: []string{"top"},
  107. }
  108. hostCfg := containertypes.HostConfig{
  109. IpcMode: containertypes.IpcMode(donorMode),
  110. }
  111. ctx := context.Background()
  112. client := request.NewAPIClient(t)
  113. // create and start the "donor" container
  114. resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
  115. assert.NilError(t, err)
  116. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  117. name1 := resp.ID
  118. err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{})
  119. assert.NilError(t, err)
  120. // create and start the second container
  121. hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
  122. resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
  123. assert.NilError(t, err)
  124. assert.Check(t, is.Equal(len(resp.Warnings), 0))
  125. name2 := resp.ID
  126. err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{})
  127. if !mustWork {
  128. // start should fail with a specific error
  129. assert.Check(t, is.ErrorContains(err, "non-shareable IPC"))
  130. // no more checks to perform here
  131. return
  132. }
  133. // start should succeed
  134. assert.NilError(t, err)
  135. // check that IPC is shared
  136. // 1. create a file in the first container
  137. _, err = container.Exec(ctx, client, name1, []string{"sh", "-c", "printf covfefe > /dev/shm/bar"})
  138. assert.NilError(t, err)
  139. // 2. check it's the same file in the second one
  140. result, err := container.Exec(ctx, client, name2, []string{"cat", "/dev/shm/bar"})
  141. assert.NilError(t, err)
  142. out := result.Combined()
  143. assert.Check(t, is.Equal(true, regexp.MustCompile("^covfefe$").MatchString(out)))
  144. }
  145. // TestAPIIpcModeShareableAndPrivate checks that
  146. // 1) a container created with --ipc container:ID can use IPC of another shareable container.
  147. // 2) a container created with --ipc container:ID can NOT use IPC of another private container.
  148. func TestAPIIpcModeShareableAndContainer(t *testing.T) {
  149. skip.If(t, testEnv.DaemonInfo.OSType != "linux")
  150. testIpcContainer(t, "shareable", true)
  151. testIpcContainer(t, "private", false)
  152. }