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