github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/integration/container/ipcmode_linux_test.go (about) 1 package container // import "github.com/demonoid81/moby/integration/container" 2 3 import ( 4 "bufio" 5 "context" 6 "io/ioutil" 7 "os" 8 "regexp" 9 "strings" 10 "testing" 11 12 "github.com/demonoid81/moby/api/types" 13 containertypes "github.com/demonoid81/moby/api/types/container" 14 "github.com/demonoid81/moby/api/types/versions" 15 "github.com/demonoid81/moby/client" 16 "github.com/demonoid81/moby/integration/internal/container" 17 "github.com/demonoid81/moby/testutil/daemon" 18 "github.com/demonoid81/moby/testutil/request" 19 "gotest.tools/v3/assert" 20 is "gotest.tools/v3/assert/cmp" 21 "gotest.tools/v3/fs" 22 "gotest.tools/v3/skip" 23 ) 24 25 // testIpcCheckDevExists checks whether a given mount (identified by its 26 // major:minor pair from /proc/self/mountinfo) exists on the host system. 27 // 28 // The format of /proc/self/mountinfo is like: 29 // 30 // 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw 31 // ^^^^\ 32 // - this is the minor:major we look for 33 func testIpcCheckDevExists(mm string) (bool, error) { 34 f, err := os.Open("/proc/self/mountinfo") 35 if err != nil { 36 return false, err 37 } 38 defer f.Close() 39 40 s := bufio.NewScanner(f) 41 for s.Scan() { 42 fields := strings.Fields(s.Text()) 43 if len(fields) < 7 { 44 continue 45 } 46 if fields[2] == mm { 47 return true, nil 48 } 49 } 50 51 return false, s.Err() 52 } 53 54 // testIpcNonePrivateShareable is a helper function to test "none", 55 // "private" and "shareable" modes. 56 func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool, mustBeShared bool) { 57 defer setupTest(t)() 58 59 cfg := containertypes.Config{ 60 Image: "busybox", 61 Cmd: []string{"top"}, 62 } 63 hostCfg := containertypes.HostConfig{ 64 IpcMode: containertypes.IpcMode(mode), 65 } 66 client := testEnv.APIClient() 67 ctx := context.Background() 68 69 resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") 70 assert.NilError(t, err) 71 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 72 73 err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) 74 assert.NilError(t, err) 75 76 // get major:minor pair for /dev/shm from container's /proc/self/mountinfo 77 cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo" 78 result, err := container.Exec(ctx, client, resp.ID, []string{"sh", "-c", cmd}) 79 assert.NilError(t, err) 80 mm := result.Combined() 81 if !mustBeMounted { 82 assert.Check(t, is.Equal(mm, "")) 83 // no more checks to perform 84 return 85 } 86 assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm))) 87 88 shared, err := testIpcCheckDevExists(mm) 89 assert.NilError(t, err) 90 t.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared) 91 assert.Check(t, is.Equal(shared, mustBeShared)) 92 } 93 94 // TestIpcModeNone checks the container "none" IPC mode 95 // (--ipc none) works as expected. It makes sure there is no 96 // /dev/shm mount inside the container. 97 func TestIpcModeNone(t *testing.T) { 98 skip.If(t, testEnv.IsRemoteDaemon) 99 100 testIpcNonePrivateShareable(t, "none", false, false) 101 } 102 103 // TestAPIIpcModePrivate checks the container private IPC mode 104 // (--ipc private) works as expected. It gets the minor:major pair 105 // of /dev/shm mount from the container, and makes sure there is no 106 // such pair on the host. 107 func TestIpcModePrivate(t *testing.T) { 108 skip.If(t, testEnv.IsRemoteDaemon) 109 110 testIpcNonePrivateShareable(t, "private", true, false) 111 } 112 113 // TestAPIIpcModeShareable checks the container shareable IPC mode 114 // (--ipc shareable) works as expected. It gets the minor:major pair 115 // of /dev/shm mount from the container, and makes sure such pair 116 // also exists on the host. 117 func TestIpcModeShareable(t *testing.T) { 118 skip.If(t, testEnv.IsRemoteDaemon) 119 skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless") 120 121 testIpcNonePrivateShareable(t, "shareable", true, true) 122 } 123 124 // testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios 125 func testIpcContainer(t *testing.T, donorMode string, mustWork bool) { 126 t.Helper() 127 128 defer setupTest(t)() 129 130 cfg := containertypes.Config{ 131 Image: "busybox", 132 Cmd: []string{"top"}, 133 } 134 hostCfg := containertypes.HostConfig{ 135 IpcMode: containertypes.IpcMode(donorMode), 136 } 137 ctx := context.Background() 138 client := testEnv.APIClient() 139 140 // create and start the "donor" container 141 resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") 142 assert.NilError(t, err) 143 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 144 name1 := resp.ID 145 146 err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{}) 147 assert.NilError(t, err) 148 149 // create and start the second container 150 hostCfg.IpcMode = containertypes.IpcMode("container:" + name1) 151 resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") 152 assert.NilError(t, err) 153 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 154 name2 := resp.ID 155 156 err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{}) 157 if !mustWork { 158 // start should fail with a specific error 159 assert.Check(t, is.ErrorContains(err, "non-shareable IPC")) 160 // no more checks to perform here 161 return 162 } 163 164 // start should succeed 165 assert.NilError(t, err) 166 167 // check that IPC is shared 168 // 1. create a file in the first container 169 _, err = container.Exec(ctx, client, name1, []string{"sh", "-c", "printf covfefe > /dev/shm/bar"}) 170 assert.NilError(t, err) 171 // 2. check it's the same file in the second one 172 result, err := container.Exec(ctx, client, name2, []string{"cat", "/dev/shm/bar"}) 173 assert.NilError(t, err) 174 out := result.Combined() 175 assert.Check(t, is.Equal(true, regexp.MustCompile("^covfefe$").MatchString(out))) 176 } 177 178 // TestAPIIpcModeShareableAndPrivate checks that 179 // 1) a container created with --ipc container:ID can use IPC of another shareable container. 180 // 2) a container created with --ipc container:ID can NOT use IPC of another private container. 181 func TestAPIIpcModeShareableAndContainer(t *testing.T) { 182 skip.If(t, testEnv.IsRemoteDaemon) 183 184 testIpcContainer(t, "shareable", true) 185 186 testIpcContainer(t, "private", false) 187 } 188 189 /* TestAPIIpcModeHost checks that a container created with --ipc host 190 * can use IPC of the host system. 191 */ 192 func TestAPIIpcModeHost(t *testing.T) { 193 skip.If(t, testEnv.IsRemoteDaemon) 194 skip.If(t, testEnv.IsUserNamespace) 195 skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless") 196 197 cfg := containertypes.Config{ 198 Image: "busybox", 199 Cmd: []string{"top"}, 200 } 201 hostCfg := containertypes.HostConfig{ 202 IpcMode: containertypes.IpcMode("host"), 203 } 204 ctx := context.Background() 205 206 client := testEnv.APIClient() 207 resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") 208 assert.NilError(t, err) 209 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 210 name := resp.ID 211 212 err = client.ContainerStart(ctx, name, types.ContainerStartOptions{}) 213 assert.NilError(t, err) 214 215 // check that IPC is shared 216 // 1. create a file inside container 217 _, err = container.Exec(ctx, client, name, []string{"sh", "-c", "printf covfefe > /dev/shm/." + name}) 218 assert.NilError(t, err) 219 // 2. check it's the same on the host 220 bytes, err := ioutil.ReadFile("/dev/shm/." + name) 221 assert.NilError(t, err) 222 assert.Check(t, is.Equal("covfefe", string(bytes))) 223 // 3. clean up 224 _, err = container.Exec(ctx, client, name, []string{"rm", "-f", "/dev/shm/." + name}) 225 assert.NilError(t, err) 226 } 227 228 // testDaemonIpcPrivateShareable is a helper function to test "private" and "shareable" daemon default ipc modes. 229 func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...string) { 230 defer setupTest(t)() 231 232 d := daemon.New(t) 233 d.StartWithBusybox(t, arg...) 234 defer d.Stop(t) 235 236 c := d.NewClientT(t) 237 238 cfg := containertypes.Config{ 239 Image: "busybox", 240 Cmd: []string{"top"}, 241 } 242 ctx := context.Background() 243 244 resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, "") 245 assert.NilError(t, err) 246 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 247 248 err = c.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) 249 assert.NilError(t, err) 250 251 // get major:minor pair for /dev/shm from container's /proc/self/mountinfo 252 cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo" 253 result, err := container.Exec(ctx, c, resp.ID, []string{"sh", "-c", cmd}) 254 assert.NilError(t, err) 255 mm := result.Combined() 256 assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm))) 257 258 shared, err := testIpcCheckDevExists(mm) 259 assert.NilError(t, err) 260 t.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, shared: %v, mustBeShared: %v\n", mm, shared, mustBeShared) 261 assert.Check(t, is.Equal(shared, mustBeShared)) 262 } 263 264 // TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended. 265 func TestDaemonIpcModeShareable(t *testing.T) { 266 skip.If(t, testEnv.IsRemoteDaemon) 267 skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless") 268 269 testDaemonIpcPrivateShareable(t, true, "--default-ipc-mode", "shareable") 270 } 271 272 // TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended. 273 func TestDaemonIpcModePrivate(t *testing.T) { 274 skip.If(t, testEnv.IsRemoteDaemon) 275 276 testDaemonIpcPrivateShareable(t, false, "--default-ipc-mode", "private") 277 } 278 279 // used to check if an IpcMode given in config works as intended 280 func testDaemonIpcFromConfig(t *testing.T, mode string, mustExist bool) { 281 skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless") 282 config := `{"default-ipc-mode": "` + mode + `"}` 283 file := fs.NewFile(t, "test-daemon-ipc-config", fs.WithContent(config)) 284 defer file.Remove() 285 286 testDaemonIpcPrivateShareable(t, mustExist, "--config-file", file.Path()) 287 } 288 289 // TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended. 290 func TestDaemonIpcModePrivateFromConfig(t *testing.T) { 291 skip.If(t, testEnv.IsRemoteDaemon) 292 293 testDaemonIpcFromConfig(t, "private", false) 294 } 295 296 // TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended. 297 func TestDaemonIpcModeShareableFromConfig(t *testing.T) { 298 skip.If(t, testEnv.IsRemoteDaemon) 299 300 testDaemonIpcFromConfig(t, "shareable", true) 301 } 302 303 // TestIpcModeOlderClient checks that older client gets shareable IPC mode 304 // by default, even when the daemon default is private. 305 func TestIpcModeOlderClient(t *testing.T) { 306 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "requires a daemon with DefaultIpcMode: private") 307 c := testEnv.APIClient() 308 skip.If(t, versions.LessThan(c.ClientVersion(), "1.40"), "requires client API >= 1.40") 309 310 t.Parallel() 311 312 ctx := context.Background() 313 314 // pre-check: default ipc mode in daemon is private 315 cID := container.Create(ctx, t, c, container.WithAutoRemove) 316 317 inspect, err := c.ContainerInspect(ctx, cID) 318 assert.NilError(t, err) 319 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "private")) 320 321 // main check: using older client creates "shareable" container 322 c = request.NewAPIClient(t, client.WithVersion("1.39")) 323 cID = container.Create(ctx, t, c, container.WithAutoRemove) 324 325 inspect, err = c.ContainerInspect(ctx, cID) 326 assert.NilError(t, err) 327 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "shareable")) 328 }