github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/integration/container/ipcmode_linux_test.go (about) 1 package container // import "github.com/docker/docker/integration/container" 2 3 import ( 4 "bufio" 5 "context" 6 "io/ioutil" 7 "os" 8 "regexp" 9 "strings" 10 "testing" 11 12 "github.com/docker/docker/api/types" 13 containertypes "github.com/docker/docker/api/types/container" 14 "github.com/docker/docker/api/types/versions" 15 "github.com/docker/docker/client" 16 "github.com/docker/docker/integration/internal/container" 17 "github.com/docker/docker/internal/test/daemon" 18 "github.com/docker/docker/internal/test/request" 19 "gotest.tools/assert" 20 is "gotest.tools/assert/cmp" 21 "gotest.tools/fs" 22 "gotest.tools/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 120 testIpcNonePrivateShareable(t, "shareable", true, true) 121 } 122 123 // testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios 124 func testIpcContainer(t *testing.T, donorMode string, mustWork bool) { 125 t.Helper() 126 127 defer setupTest(t)() 128 129 cfg := containertypes.Config{ 130 Image: "busybox", 131 Cmd: []string{"top"}, 132 } 133 hostCfg := containertypes.HostConfig{ 134 IpcMode: containertypes.IpcMode(donorMode), 135 } 136 ctx := context.Background() 137 client := testEnv.APIClient() 138 139 // create and start the "donor" container 140 resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") 141 assert.NilError(t, err) 142 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 143 name1 := resp.ID 144 145 err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{}) 146 assert.NilError(t, err) 147 148 // create and start the second container 149 hostCfg.IpcMode = containertypes.IpcMode("container:" + name1) 150 resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") 151 assert.NilError(t, err) 152 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 153 name2 := resp.ID 154 155 err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{}) 156 if !mustWork { 157 // start should fail with a specific error 158 assert.Check(t, is.ErrorContains(err, "non-shareable IPC")) 159 // no more checks to perform here 160 return 161 } 162 163 // start should succeed 164 assert.NilError(t, err) 165 166 // check that IPC is shared 167 // 1. create a file in the first container 168 _, err = container.Exec(ctx, client, name1, []string{"sh", "-c", "printf covfefe > /dev/shm/bar"}) 169 assert.NilError(t, err) 170 // 2. check it's the same file in the second one 171 result, err := container.Exec(ctx, client, name2, []string{"cat", "/dev/shm/bar"}) 172 assert.NilError(t, err) 173 out := result.Combined() 174 assert.Check(t, is.Equal(true, regexp.MustCompile("^covfefe$").MatchString(out))) 175 } 176 177 // TestAPIIpcModeShareableAndPrivate checks that 178 // 1) a container created with --ipc container:ID can use IPC of another shareable container. 179 // 2) a container created with --ipc container:ID can NOT use IPC of another private container. 180 func TestAPIIpcModeShareableAndContainer(t *testing.T) { 181 skip.If(t, testEnv.IsRemoteDaemon) 182 183 testIpcContainer(t, "shareable", true) 184 185 testIpcContainer(t, "private", false) 186 } 187 188 /* TestAPIIpcModeHost checks that a container created with --ipc host 189 * can use IPC of the host system. 190 */ 191 func TestAPIIpcModeHost(t *testing.T) { 192 skip.If(t, testEnv.IsRemoteDaemon) 193 skip.If(t, testEnv.IsUserNamespace) 194 195 cfg := containertypes.Config{ 196 Image: "busybox", 197 Cmd: []string{"top"}, 198 } 199 hostCfg := containertypes.HostConfig{ 200 IpcMode: containertypes.IpcMode("host"), 201 } 202 ctx := context.Background() 203 204 client := testEnv.APIClient() 205 resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") 206 assert.NilError(t, err) 207 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 208 name := resp.ID 209 210 err = client.ContainerStart(ctx, name, types.ContainerStartOptions{}) 211 assert.NilError(t, err) 212 213 // check that IPC is shared 214 // 1. create a file inside container 215 _, err = container.Exec(ctx, client, name, []string{"sh", "-c", "printf covfefe > /dev/shm/." + name}) 216 assert.NilError(t, err) 217 // 2. check it's the same on the host 218 bytes, err := ioutil.ReadFile("/dev/shm/." + name) 219 assert.NilError(t, err) 220 assert.Check(t, is.Equal("covfefe", string(bytes))) 221 // 3. clean up 222 _, err = container.Exec(ctx, client, name, []string{"rm", "-f", "/dev/shm/." + name}) 223 assert.NilError(t, err) 224 } 225 226 // testDaemonIpcPrivateShareable is a helper function to test "private" and "shareable" daemon default ipc modes. 227 func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...string) { 228 defer setupTest(t)() 229 230 d := daemon.New(t) 231 d.StartWithBusybox(t, arg...) 232 defer d.Stop(t) 233 234 c := d.NewClientT(t) 235 236 cfg := containertypes.Config{ 237 Image: "busybox", 238 Cmd: []string{"top"}, 239 } 240 ctx := context.Background() 241 242 resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, "") 243 assert.NilError(t, err) 244 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 245 246 err = c.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) 247 assert.NilError(t, err) 248 249 // get major:minor pair for /dev/shm from container's /proc/self/mountinfo 250 cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo" 251 result, err := container.Exec(ctx, c, resp.ID, []string{"sh", "-c", cmd}) 252 assert.NilError(t, err) 253 mm := result.Combined() 254 assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm))) 255 256 shared, err := testIpcCheckDevExists(mm) 257 assert.NilError(t, err) 258 t.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, shared: %v, mustBeShared: %v\n", mm, shared, mustBeShared) 259 assert.Check(t, is.Equal(shared, mustBeShared)) 260 } 261 262 // TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended. 263 func TestDaemonIpcModeShareable(t *testing.T) { 264 skip.If(t, testEnv.IsRemoteDaemon) 265 266 testDaemonIpcPrivateShareable(t, true, "--default-ipc-mode", "shareable") 267 } 268 269 // TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended. 270 func TestDaemonIpcModePrivate(t *testing.T) { 271 skip.If(t, testEnv.IsRemoteDaemon) 272 273 testDaemonIpcPrivateShareable(t, false, "--default-ipc-mode", "private") 274 } 275 276 // used to check if an IpcMode given in config works as intended 277 func testDaemonIpcFromConfig(t *testing.T, mode string, mustExist bool) { 278 config := `{"default-ipc-mode": "` + mode + `"}` 279 file := fs.NewFile(t, "test-daemon-ipc-config", fs.WithContent(config)) 280 defer file.Remove() 281 282 testDaemonIpcPrivateShareable(t, mustExist, "--config-file", file.Path()) 283 } 284 285 // TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended. 286 func TestDaemonIpcModePrivateFromConfig(t *testing.T) { 287 skip.If(t, testEnv.IsRemoteDaemon) 288 289 testDaemonIpcFromConfig(t, "private", false) 290 } 291 292 // TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended. 293 func TestDaemonIpcModeShareableFromConfig(t *testing.T) { 294 skip.If(t, testEnv.IsRemoteDaemon) 295 296 testDaemonIpcFromConfig(t, "shareable", true) 297 } 298 299 // TestIpcModeOlderClient checks that older client gets shareable IPC mode 300 // by default, even when the daemon default is private. 301 func TestIpcModeOlderClient(t *testing.T) { 302 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "requires a daemon with DefaultIpcMode: private") 303 c := testEnv.APIClient() 304 skip.If(t, versions.LessThan(c.ClientVersion(), "1.40"), "requires client API >= 1.40") 305 306 t.Parallel() 307 308 ctx := context.Background() 309 310 // pre-check: default ipc mode in daemon is private 311 cID := container.Create(ctx, t, c, container.WithAutoRemove) 312 313 inspect, err := c.ContainerInspect(ctx, cID) 314 assert.NilError(t, err) 315 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "private")) 316 317 // main check: using older client creates "shareable" container 318 c = request.NewAPIClient(t, client.WithVersion("1.39")) 319 cID = container.Create(ctx, t, c, container.WithAutoRemove) 320 321 inspect, err = c.ContainerInspect(ctx, cID) 322 assert.NilError(t, err) 323 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "shareable")) 324 }