Browse code

Test cases for new ipc modes

These test cases cover various arguments for docker create/run --ipc
option, as well as daemon's --default-ipc-mode cli option and
configuration file parameter.

For the description of container IPC modes, see previous commit.

To run these:

TESTFLAGS='-check.f IpcMode' make test-integration-cli

[v2: simplify TestDaemonEvents(), add default-ipc-mode presense check]
[v3: add TestDaemonIpcModeVSRestart]
[v4: ipcmode test now uses client lib instead of CLI (except for exec)]
[v5: nitpicks in comments]
[v6: add test case for "none"; fix a typo; simplify TestDaemonEvents() more]

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Kir Kolyshkin authored on 2017/07/20 03:24:54
Showing 3 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,222 @@
0
+// build +linux
1
+package main
2
+
3
+import (
4
+	"bufio"
5
+	"fmt"
6
+	"io/ioutil"
7
+	"os"
8
+	"strings"
9
+
10
+	"github.com/docker/docker/api/types"
11
+	"github.com/docker/docker/api/types/container"
12
+	"github.com/docker/docker/integration-cli/checker"
13
+	"github.com/docker/docker/integration-cli/cli"
14
+	"github.com/docker/docker/integration-cli/request"
15
+	"github.com/go-check/check"
16
+	"golang.org/x/net/context"
17
+)
18
+
19
+/* testIpcCheckDevExists checks whether a given mount (identified by its
20
+ * major:minor pair from /proc/self/mountinfo) exists on the host system.
21
+ *
22
+ * The format of /proc/self/mountinfo is like:
23
+ *
24
+ * 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw
25
+ *       ^^^^\
26
+ *            - this is the minor:major we look for
27
+ */
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
+
35
+	s := bufio.NewScanner(f)
36
+	for s.Scan() {
37
+		fields := strings.Fields(s.Text())
38
+		if len(fields) < 7 {
39
+			continue
40
+		}
41
+		if fields[2] == mm {
42
+			return true, nil
43
+		}
44
+	}
45
+
46
+	if err := s.Err(); err != nil {
47
+		return false, err
48
+	}
49
+
50
+	return false, nil
51
+}
52
+
53
+// testIpcNonePrivateShareable is a helper function to test "none",
54
+// "private" and "shareable" modes.
55
+func testIpcNonePrivateShareable(c *check.C, mode string, mustBeMounted bool, mustBeShared bool) {
56
+	cfg := container.Config{
57
+		Image: "busybox",
58
+		Cmd:   []string{"top"},
59
+	}
60
+	hostCfg := container.HostConfig{
61
+		IpcMode: container.IpcMode(mode),
62
+	}
63
+	ctx := context.Background()
64
+
65
+	client, err := request.NewClient()
66
+	c.Assert(err, checker.IsNil)
67
+
68
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
69
+	c.Assert(err, checker.IsNil)
70
+	c.Assert(len(resp.Warnings), checker.Equals, 0)
71
+
72
+	err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
73
+	c.Assert(err, checker.IsNil)
74
+
75
+	// get major:minor pair for /dev/shm from container's /proc/self/mountinfo
76
+	cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
77
+	mm := cli.DockerCmd(c, "exec", "-i", resp.ID, "sh", "-c", cmd).Combined()
78
+	if !mustBeMounted {
79
+		c.Assert(mm, checker.Equals, "")
80
+		// no more checks to perform
81
+		return
82
+	}
83
+	c.Assert(mm, checker.Matches, "^[0-9]+:[0-9]+$")
84
+
85
+	shared, err := testIpcCheckDevExists(mm)
86
+	c.Assert(err, checker.IsNil)
87
+	c.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared)
88
+	c.Assert(shared, checker.Equals, mustBeShared)
89
+}
90
+
91
+/* TestAPIIpcModeNone 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
+ */
95
+func (s *DockerSuite) TestAPIIpcModeNone(c *check.C) {
96
+	testRequires(c, DaemonIsLinux)
97
+	testIpcNonePrivateShareable(c, "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
+ */
105
+func (s *DockerSuite) TestAPIIpcModePrivate(c *check.C) {
106
+	testRequires(c, DaemonIsLinux)
107
+	testIpcNonePrivateShareable(c, "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
+ */
115
+func (s *DockerSuite) TestAPIIpcModeShareable(c *check.C) {
116
+	testRequires(c, DaemonIsLinux)
117
+	testIpcNonePrivateShareable(c, "shareable", true, true)
118
+}
119
+
120
+// testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios
121
+func testIpcContainer(s *DockerSuite, c *check.C, donorMode string, mustWork bool) {
122
+	cfg := container.Config{
123
+		Image: "busybox",
124
+		Cmd:   []string{"top"},
125
+	}
126
+	hostCfg := container.HostConfig{
127
+		IpcMode: container.IpcMode(donorMode),
128
+	}
129
+	ctx := context.Background()
130
+
131
+	client, err := request.NewClient()
132
+	c.Assert(err, checker.IsNil)
133
+
134
+	// create and start the "donor" container
135
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
136
+	c.Assert(err, checker.IsNil)
137
+	c.Assert(len(resp.Warnings), checker.Equals, 0)
138
+	name1 := resp.ID
139
+
140
+	err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{})
141
+	c.Assert(err, checker.IsNil)
142
+
143
+	// create and start the second container
144
+	hostCfg.IpcMode = container.IpcMode("container:" + name1)
145
+	resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
146
+	c.Assert(err, checker.IsNil)
147
+	c.Assert(len(resp.Warnings), checker.Equals, 0)
148
+	name2 := resp.ID
149
+
150
+	err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{})
151
+	if !mustWork {
152
+		// start should fail with a specific error
153
+		c.Assert(err, checker.NotNil)
154
+		c.Assert(fmt.Sprintf("%v", err), checker.Contains, "non-shareable IPC")
155
+		// no more checks to perform here
156
+		return
157
+	}
158
+
159
+	// start should succeed
160
+	c.Assert(err, checker.IsNil)
161
+
162
+	// check that IPC is shared
163
+	// 1. create a file in the first container
164
+	cli.DockerCmd(c, "exec", name1, "sh", "-c", "printf covfefe > /dev/shm/bar")
165
+	// 2. check it's the same file in the second one
166
+	out := cli.DockerCmd(c, "exec", "-i", name2, "cat", "/dev/shm/bar").Combined()
167
+	c.Assert(out, checker.Matches, "^covfefe$")
168
+}
169
+
170
+/* TestAPIIpcModeShareableAndContainer checks that a container created with
171
+ * --ipc container:ID can use IPC of another shareable container.
172
+ */
173
+func (s *DockerSuite) TestAPIIpcModeShareableAndContainer(c *check.C) {
174
+	testRequires(c, DaemonIsLinux)
175
+	testIpcContainer(s, c, "shareable", true)
176
+}
177
+
178
+/* TestAPIIpcModePrivateAndContainer checks that a container created with
179
+ * --ipc container:ID can NOT use IPC of another private container.
180
+ */
181
+func (s *DockerSuite) TestAPIIpcModePrivateAndContainer(c *check.C) {
182
+	testRequires(c, DaemonIsLinux)
183
+	testIpcContainer(s, c, "private", false)
184
+}
185
+
186
+/* TestAPIIpcModeHost checks that a container created with --ipc host
187
+ * can use IPC of the host system.
188
+ */
189
+func (s *DockerSuite) TestAPIIpcModeHost(c *check.C) {
190
+	testRequires(c, DaemonIsLinux)
191
+
192
+	cfg := container.Config{
193
+		Image: "busybox",
194
+		Cmd:   []string{"top"},
195
+	}
196
+	hostCfg := container.HostConfig{
197
+		IpcMode: container.IpcMode("host"),
198
+	}
199
+	ctx := context.Background()
200
+
201
+	client, err := request.NewClient()
202
+	c.Assert(err, checker.IsNil)
203
+
204
+	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
205
+	c.Assert(err, checker.IsNil)
206
+	c.Assert(len(resp.Warnings), checker.Equals, 0)
207
+	name := resp.ID
208
+
209
+	err = client.ContainerStart(ctx, name, types.ContainerStartOptions{})
210
+	c.Assert(err, checker.IsNil)
211
+
212
+	// check that IPC is shared
213
+	// 1. create a file inside container
214
+	cli.DockerCmd(c, "exec", name, "sh", "-c", "printf covfefe > /dev/shm/."+name)
215
+	// 2. check it's the same on the host
216
+	bytes, err := ioutil.ReadFile("/dev/shm/." + name)
217
+	c.Assert(err, checker.IsNil)
218
+	c.Assert(string(bytes), checker.Matches, "^covfefe$")
219
+	// 3. clean up
220
+	cli.DockerCmd(c, "exec", name, "rm", "-f", "/dev/shm/."+name)
221
+}
... ...
@@ -2985,6 +2985,165 @@ func (s *DockerDaemonSuite) TestShmSizeReload(c *check.C) {
2985 2985
 	c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size))
2986 2986
 }
2987 2987
 
2988
+// this is used to test both "private" and "shareable" daemon default ipc modes
2989
+func testDaemonIpcPrivateShareable(d *daemon.Daemon, c *check.C, mustExist bool) {
2990
+	name := "test-ipcmode"
2991
+	_, err := d.Cmd("run", "-d", "--name", name, "busybox", "top")
2992
+	c.Assert(err, checker.IsNil)
2993
+
2994
+	// get major:minor pair for /dev/shm from container's /proc/self/mountinfo
2995
+	cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
2996
+	mm, err := d.Cmd("exec", "-i", name, "sh", "-c", cmd)
2997
+	c.Assert(err, checker.IsNil)
2998
+	c.Assert(mm, checker.Matches, "^[0-9]+:[0-9]+$")
2999
+
3000
+	exists, err := testIpcCheckDevExists(mm)
3001
+	c.Assert(err, checker.IsNil)
3002
+	c.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, exists: %v, mustExist: %v\n", mm, exists, mustExist)
3003
+	c.Assert(exists, checker.Equals, mustExist)
3004
+}
3005
+
3006
+// TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended.
3007
+func (s *DockerDaemonSuite) TestDaemonIpcModeShareable(c *check.C) {
3008
+	testRequires(c, DaemonIsLinux)
3009
+
3010
+	s.d.StartWithBusybox(c, "--default-ipc-mode", "shareable")
3011
+	testDaemonIpcPrivateShareable(s.d, c, true)
3012
+}
3013
+
3014
+// TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended.
3015
+func (s *DockerDaemonSuite) TestDaemonIpcModePrivate(c *check.C) {
3016
+	testRequires(c, DaemonIsLinux)
3017
+
3018
+	s.d.StartWithBusybox(c, "--default-ipc-mode", "private")
3019
+	testDaemonIpcPrivateShareable(s.d, c, false)
3020
+}
3021
+
3022
+// used to check if an IpcMode given in config works as intended
3023
+func testDaemonIpcFromConfig(s *DockerDaemonSuite, c *check.C, mode string, mustExist bool) {
3024
+	f, err := ioutil.TempFile("", "test-daemon-ipc-config")
3025
+	c.Assert(err, checker.IsNil)
3026
+	defer os.Remove(f.Name())
3027
+
3028
+	config := `{"default-ipc-mode": "` + mode + `"}`
3029
+	_, err = f.WriteString(config)
3030
+	c.Assert(f.Close(), checker.IsNil)
3031
+	c.Assert(err, checker.IsNil)
3032
+
3033
+	s.d.StartWithBusybox(c, "--config-file", f.Name())
3034
+	testDaemonIpcPrivateShareable(s.d, c, mustExist)
3035
+}
3036
+
3037
+// TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended.
3038
+func (s *DockerDaemonSuite) TestDaemonIpcModePrivateFromConfig(c *check.C) {
3039
+	testDaemonIpcFromConfig(s, c, "private", false)
3040
+}
3041
+
3042
+// TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended.
3043
+func (s *DockerDaemonSuite) TestDaemonIpcModeShareableFromConfig(c *check.C) {
3044
+	testDaemonIpcFromConfig(s, c, "shareable", true)
3045
+}
3046
+
3047
+func testDaemonStartIpcMode(c *check.C, from, mode string, valid bool) {
3048
+	testRequires(c, DaemonIsLinux)
3049
+
3050
+	d := daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
3051
+		Experimental: testEnv.ExperimentalDaemon(),
3052
+	})
3053
+	c.Logf("Checking IpcMode %s set from %s\n", mode, from)
3054
+	var serr error
3055
+	switch from {
3056
+	case "config":
3057
+		f, err := ioutil.TempFile("", "test-daemon-ipc-config")
3058
+		c.Assert(err, checker.IsNil)
3059
+		defer os.Remove(f.Name())
3060
+		config := `{"default-ipc-mode": "` + mode + `"}`
3061
+		_, err = f.WriteString(config)
3062
+		c.Assert(f.Close(), checker.IsNil)
3063
+		c.Assert(err, checker.IsNil)
3064
+
3065
+		serr = d.StartWithError("--config-file", f.Name())
3066
+	case "cli":
3067
+		serr = d.StartWithError("--default-ipc-mode", mode)
3068
+	default:
3069
+		c.Fatalf("testDaemonStartIpcMode: invalid 'from' argument")
3070
+	}
3071
+	if serr == nil {
3072
+		d.Stop(c)
3073
+	}
3074
+
3075
+	if valid {
3076
+		c.Assert(serr, check.IsNil)
3077
+	} else {
3078
+		c.Assert(serr, check.NotNil)
3079
+		icmd.RunCommand("grep", "-E", "IPC .* is (invalid|not supported)", d.LogFileName()).Assert(c, icmd.Success)
3080
+	}
3081
+}
3082
+
3083
+// TestDaemonStartWithIpcModes checks that daemon starts fine given correct
3084
+// arguments for default IPC mode, and bails out with incorrect ones.
3085
+// Both CLI option (--default-ipc-mode) and config parameter are tested.
3086
+func (s *DockerDaemonSuite) TestDaemonStartWithIpcModes(c *check.C) {
3087
+	ipcModes := []struct {
3088
+		mode  string
3089
+		valid bool
3090
+	}{
3091
+		{"private", true},
3092
+		{"shareable", true},
3093
+
3094
+		{"host", false},
3095
+		{"container:123", false},
3096
+		{"nosuchmode", false},
3097
+	}
3098
+
3099
+	for _, from := range []string{"config", "cli"} {
3100
+		for _, m := range ipcModes {
3101
+			testDaemonStartIpcMode(c, from, m.mode, m.valid)
3102
+		}
3103
+	}
3104
+}
3105
+
3106
+// TestDaemonRestartIpcMode makes sure a container keeps its ipc mode
3107
+// (derived from daemon default) even after the daemon is restarted
3108
+// with a different default ipc mode.
3109
+func (s *DockerDaemonSuite) TestDaemonRestartIpcMode(c *check.C) {
3110
+	f, err := ioutil.TempFile("", "test-daemon-ipc-config-restart")
3111
+	c.Assert(err, checker.IsNil)
3112
+	file := f.Name()
3113
+	defer os.Remove(file)
3114
+	c.Assert(f.Close(), checker.IsNil)
3115
+
3116
+	config := []byte(`{"default-ipc-mode": "private"}`)
3117
+	c.Assert(ioutil.WriteFile(file, config, 0644), checker.IsNil)
3118
+	s.d.StartWithBusybox(c, "--config-file", file)
3119
+
3120
+	// check the container is created with private ipc mode as per daemon default
3121
+	name := "ipc1"
3122
+	_, err = s.d.Cmd("run", "-d", "--name", name, "--restart=always", "busybox", "top")
3123
+	c.Assert(err, checker.IsNil)
3124
+	m, err := s.d.InspectField(name, ".HostConfig.IpcMode")
3125
+	c.Assert(err, check.IsNil)
3126
+	c.Assert(m, checker.Equals, "private")
3127
+
3128
+	// restart the daemon with shareable default ipc mode
3129
+	config = []byte(`{"default-ipc-mode": "shareable"}`)
3130
+	c.Assert(ioutil.WriteFile(file, config, 0644), checker.IsNil)
3131
+	s.d.Restart(c, "--config-file", file)
3132
+
3133
+	// check the container is still having private ipc mode
3134
+	m, err = s.d.InspectField(name, ".HostConfig.IpcMode")
3135
+	c.Assert(err, check.IsNil)
3136
+	c.Assert(m, checker.Equals, "private")
3137
+
3138
+	// check a new container is created with shareable ipc mode as per new daemon default
3139
+	name = "ipc2"
3140
+	_, err = s.d.Cmd("run", "-d", "--name", name, "busybox", "top")
3141
+	c.Assert(err, checker.IsNil)
3142
+	m, err = s.d.InspectField(name, ".HostConfig.IpcMode")
3143
+	c.Assert(err, check.IsNil)
3144
+	c.Assert(m, checker.Equals, "shareable")
3145
+}
3146
+
2988 3147
 // TestFailedPluginRemove makes sure that a failed plugin remove does not block
2989 3148
 // the daemon from starting
2990 3149
 func (s *DockerDaemonSuite) TestFailedPluginRemove(c *check.C) {
... ...
@@ -428,7 +428,32 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
428 428
 	out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
429 429
 	c.Assert(err, checker.IsNil)
430 430
 
431
-	c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (allow-nondistributable-artifacts=[], cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, default-shm-size=67108864, insecure-registries=[], labels=[\"bar=foo\"], live-restore=false, max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, registry-mirrors=[], runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName))
431
+	// only check for values known (daemon ID/name) or explicitly set above,
432
+	// otherwise just check for names being present.
433
+	expectedSubstrings := []string{
434
+		" daemon reload " + daemonID + " ",
435
+		"(allow-nondistributable-artifacts=[",
436
+		" cluster-advertise=, ",
437
+		" cluster-store=, ",
438
+		" cluster-store-opts={",
439
+		" debug=true, ",
440
+		" default-ipc-mode=",
441
+		" default-runtime=",
442
+		" default-shm-size=",
443
+		" insecure-registries=[",
444
+		" labels=[\"bar=foo\"], ",
445
+		" live-restore=",
446
+		" max-concurrent-downloads=1, ",
447
+		" max-concurrent-uploads=5, ",
448
+		" name=" + daemonName,
449
+		" registry-mirrors=[",
450
+		" runtimes=",
451
+		" shutdown-timeout=10)",
452
+	}
453
+
454
+	for _, s := range expectedSubstrings {
455
+		c.Assert(out, checker.Contains, s)
456
+	}
432 457
 }
433 458
 
434 459
 func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {