Browse code

Merge pull request #34087 from kolyshkin/ipcmode

Implement private and shareable ipc modes

Sebastiaan van Stijn authored on 2017/08/14 22:52:21
Showing 22 changed files
... ...
@@ -665,7 +665,17 @@ definitions:
665 665
               type: "string"
666 666
           IpcMode:
667 667
             type: "string"
668
-            description: "IPC namespace to use for the container."
668
+            description: |
669
+                    IPC sharing mode for the container. Possible values are:
670
+
671
+                    - `"none"`: own private IPC namespace, with /dev/shm not mounted
672
+                    - `"private"`: own private IPC namespace
673
+                    - `"shareable"`: own private IPC namespace, with a possibility to share it with other containers
674
+                    - `"container:<name|id>"`: join another (shareable) container's IPC namespace
675
+                    - `"host"`: use the host system's IPC namespace
676
+
677
+                    If not specified, daemon default is used, which can either be `"private"`
678
+                    or `"shareable"`, depending on daemon version and configuration.
669 679
           Cgroup:
670 680
             type: "string"
671 681
             description: "Cgroup to use for the container."
... ...
@@ -23,41 +23,46 @@ func (i Isolation) IsDefault() bool {
23 23
 // IpcMode represents the container ipc stack.
24 24
 type IpcMode string
25 25
 
26
-// IsPrivate indicates whether the container uses its private ipc stack.
26
+// IsPrivate indicates whether the container uses its own private ipc namespace which can not be shared.
27 27
 func (n IpcMode) IsPrivate() bool {
28
-	return !(n.IsHost() || n.IsContainer())
28
+	return n == "private"
29 29
 }
30 30
 
31
-// IsHost indicates whether the container uses the host's ipc stack.
31
+// IsHost indicates whether the container shares the host's ipc namespace.
32 32
 func (n IpcMode) IsHost() bool {
33 33
 	return n == "host"
34 34
 }
35 35
 
36
-// IsContainer indicates whether the container uses a container's ipc stack.
36
+// IsShareable indicates whether the container's ipc namespace can be shared with another container.
37
+func (n IpcMode) IsShareable() bool {
38
+	return n == "shareable"
39
+}
40
+
41
+// IsContainer indicates whether the container uses another container's ipc namespace.
37 42
 func (n IpcMode) IsContainer() bool {
38 43
 	parts := strings.SplitN(string(n), ":", 2)
39 44
 	return len(parts) > 1 && parts[0] == "container"
40 45
 }
41 46
 
42
-// Valid indicates whether the ipc stack is valid.
47
+// IsNone indicates whether container IpcMode is set to "none".
48
+func (n IpcMode) IsNone() bool {
49
+	return n == "none"
50
+}
51
+
52
+// IsEmpty indicates whether container IpcMode is empty
53
+func (n IpcMode) IsEmpty() bool {
54
+	return n == ""
55
+}
56
+
57
+// Valid indicates whether the ipc mode is valid.
43 58
 func (n IpcMode) Valid() bool {
44
-	parts := strings.Split(string(n), ":")
45
-	switch mode := parts[0]; mode {
46
-	case "", "host":
47
-	case "container":
48
-		if len(parts) != 2 || parts[1] == "" {
49
-			return false
50
-		}
51
-	default:
52
-		return false
53
-	}
54
-	return true
59
+	return n.IsEmpty() || n.IsNone() || n.IsPrivate() || n.IsHost() || n.IsShareable() || n.IsContainer()
55 60
 }
56 61
 
57 62
 // Container returns the name of the container ipc stack is going to be used.
58 63
 func (n IpcMode) Container() string {
59 64
 	parts := strings.SplitN(string(n), ":", 2)
60
-	if len(parts) > 1 {
65
+	if len(parts) > 1 && parts[0] == "container" {
61 66
 		return parts[1]
62 67
 	}
63 68
 	return ""
... ...
@@ -47,6 +47,7 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
47 47
 	flags.StringVar(&conf.SeccompProfile, "seccomp-profile", "", "Path to seccomp profile")
48 48
 	flags.Var(&conf.ShmSize, "default-shm-size", "Default shm size for containers")
49 49
 	flags.BoolVar(&conf.NoNewPrivileges, "no-new-privileges", false, "Set no-new-privileges by default for new containers")
50
+	flags.StringVar(&conf.IpcMode, "default-ipc-mode", config.DefaultIpcMode, `Default mode for containers ipc ("shareable" | "private")`)
50 51
 
51 52
 	attachExperimentalFlags(conf, flags)
52 53
 }
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"io/ioutil"
8 8
 	"os"
9 9
 	"path/filepath"
10
-	"strings"
11 10
 
12 11
 	"github.com/docker/docker/api/types"
13 12
 	containertypes "github.com/docker/docker/api/types/container"
... ...
@@ -19,6 +18,7 @@ import (
19 19
 	"github.com/docker/docker/pkg/system"
20 20
 	"github.com/docker/docker/volume"
21 21
 	"github.com/opencontainers/selinux/go-selinux/label"
22
+	"github.com/pkg/errors"
22 23
 	"github.com/sirupsen/logrus"
23 24
 	"golang.org/x/sys/unix"
24 25
 )
... ...
@@ -171,48 +171,48 @@ func (container *Container) HasMountFor(path string) bool {
171 171
 	return exists
172 172
 }
173 173
 
174
-// UnmountIpcMounts uses the provided unmount function to unmount shm and mqueue if they were mounted
175
-func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
176
-	if container.HostConfig.IpcMode.IsContainer() || container.HostConfig.IpcMode.IsHost() {
177
-		return
174
+// UnmountIpcMount uses the provided unmount function to unmount shm if it was mounted
175
+func (container *Container) UnmountIpcMount(unmount func(pth string) error) error {
176
+	if container.HasMountFor("/dev/shm") {
177
+		return nil
178 178
 	}
179 179
 
180
-	var warnings []string
181
-
182
-	if !container.HasMountFor("/dev/shm") {
183
-		shmPath, err := container.ShmResourcePath()
184
-		if err != nil {
185
-			logrus.Error(err)
186
-			warnings = append(warnings, err.Error())
187
-		} else if shmPath != "" {
188
-			if err := unmount(shmPath); err != nil && !os.IsNotExist(err) {
189
-				if mounted, mErr := mount.Mounted(shmPath); mounted || mErr != nil {
190
-					warnings = append(warnings, fmt.Sprintf("failed to umount %s: %v", shmPath, err))
191
-				}
192
-			}
193
-
194
-		}
180
+	// container.ShmPath should not be used here as it may point
181
+	// to the host's or other container's /dev/shm
182
+	shmPath, err := container.ShmResourcePath()
183
+	if err != nil {
184
+		return err
195 185
 	}
196
-
197
-	if len(warnings) > 0 {
198
-		logrus.Warnf("failed to cleanup ipc mounts:\n%v", strings.Join(warnings, "\n"))
186
+	if shmPath == "" {
187
+		return nil
199 188
 	}
189
+	if err = unmount(shmPath); err != nil && !os.IsNotExist(err) {
190
+		if mounted, mErr := mount.Mounted(shmPath); mounted || mErr != nil {
191
+			return errors.Wrapf(err, "umount %s", shmPath)
192
+		}
193
+	}
194
+	return nil
200 195
 }
201 196
 
202 197
 // IpcMounts returns the list of IPC mounts
203 198
 func (container *Container) IpcMounts() []Mount {
204 199
 	var mounts []Mount
205 200
 
206
-	if !container.HasMountFor("/dev/shm") {
207
-		label.SetFileLabel(container.ShmPath, container.MountLabel)
208
-		mounts = append(mounts, Mount{
209
-			Source:      container.ShmPath,
210
-			Destination: "/dev/shm",
211
-			Writable:    true,
212
-			Propagation: string(volume.DefaultPropagationMode),
213
-		})
201
+	if container.HasMountFor("/dev/shm") {
202
+		return mounts
203
+	}
204
+	if container.ShmPath == "" {
205
+		return mounts
214 206
 	}
215 207
 
208
+	label.SetFileLabel(container.ShmPath, container.MountLabel)
209
+	mounts = append(mounts, Mount{
210
+		Source:      container.ShmPath,
211
+		Destination: "/dev/shm",
212
+		Writable:    true,
213
+		Propagation: string(volume.DefaultPropagationMode),
214
+	})
215
+
216 216
 	return mounts
217 217
 }
218 218
 
... ...
@@ -24,9 +24,10 @@ type ExitStatus struct {
24 24
 	ExitCode int
25 25
 }
26 26
 
27
-// UnmountIpcMounts unmounts Ipc related mounts.
27
+// UnmountIpcMount unmounts Ipc related mounts.
28 28
 // This is a NOOP on windows.
29
-func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
29
+func (container *Container) UnmountIpcMount(unmount func(pth string) error) error {
30
+	return nil
30 31
 }
31 32
 
32 33
 // IpcMounts returns the list of Ipc related mounts.
... ...
@@ -513,6 +513,11 @@ func Validate(config *Config) error {
513 513
 		}
514 514
 	}
515 515
 
516
+	// validate platform-specific settings
517
+	if err := config.ValidatePlatformConfig(); err != nil {
518
+		return err
519
+	}
520
+
516 521
 	return nil
517 522
 }
518 523
 
... ...
@@ -27,3 +27,8 @@ type BridgeConfig struct {
27 27
 func (conf *Config) IsSwarmCompatible() error {
28 28
 	return nil
29 29
 }
30
+
31
+// ValidatePlatformConfig checks if any platform-specific configuration settings are invalid.
32
+func (conf *Config) ValidatePlatformConfig() error {
33
+	return nil
34
+}
... ...
@@ -5,10 +5,16 @@ package config
5 5
 import (
6 6
 	"fmt"
7 7
 
8
+	containertypes "github.com/docker/docker/api/types/container"
8 9
 	"github.com/docker/docker/opts"
9 10
 	units "github.com/docker/go-units"
10 11
 )
11 12
 
13
+const (
14
+	// DefaultIpcMode is default for container's IpcMode, if not set otherwise
15
+	DefaultIpcMode = "shareable" // TODO: change to private
16
+)
17
+
12 18
 // Config defines the configuration of a docker daemon.
13 19
 // It includes json tags to deserialize configuration from a file
14 20
 // using the same names that the flags in the command line uses.
... ...
@@ -31,6 +37,7 @@ type Config struct {
31 31
 	SeccompProfile       string                   `json:"seccomp-profile,omitempty"`
32 32
 	ShmSize              opts.MemBytes            `json:"default-shm-size,omitempty"`
33 33
 	NoNewPrivileges      bool                     `json:"no-new-privileges,omitempty"`
34
+	IpcMode              string                   `json:"default-ipc-mode,omitempty"`
34 35
 }
35 36
 
36 37
 // BridgeConfig stores all the bridge driver specific
... ...
@@ -61,3 +68,21 @@ func (conf *Config) IsSwarmCompatible() error {
61 61
 	}
62 62
 	return nil
63 63
 }
64
+
65
+func verifyDefaultIpcMode(mode string) error {
66
+	const hint = "Use \"shareable\" or \"private\"."
67
+
68
+	dm := containertypes.IpcMode(mode)
69
+	if !dm.Valid() {
70
+		return fmt.Errorf("Default IPC mode setting (%v) is invalid. "+hint, dm)
71
+	}
72
+	if dm != "" && !dm.IsPrivate() && !dm.IsShareable() {
73
+		return fmt.Errorf("IPC mode \"%v\" is not supported as default value. "+hint, dm)
74
+	}
75
+	return nil
76
+}
77
+
78
+// ValidatePlatformConfig checks if any platform-specific configuration settings are invalid.
79
+func (conf *Config) ValidatePlatformConfig() error {
80
+	return verifyDefaultIpcMode(conf.IpcMode)
81
+}
... ...
@@ -50,3 +50,8 @@ func (conf *Config) GetExecRoot() string {
50 50
 func (conf *Config) IsSwarmCompatible() error {
51 51
 	return nil
52 52
 }
53
+
54
+// ValidatePlatformConfig checks if any platform-specific configuration settings are invalid.
55
+func (conf *Config) ValidatePlatformConfig() error {
56
+	return nil
57
+}
... ...
@@ -57,13 +57,27 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
57 57
 	return env, nil
58 58
 }
59 59
 
60
-func (daemon *Daemon) getIpcContainer(container *container.Container) (*container.Container, error) {
61
-	containerID := container.HostConfig.IpcMode.Container()
62
-	container, err := daemon.GetContainer(containerID)
60
+func (daemon *Daemon) getIpcContainer(id string) (*container.Container, error) {
61
+	errMsg := "can't join IPC of container " + id
62
+	// Check the container exists
63
+	container, err := daemon.GetContainer(id)
63 64
 	if err != nil {
64
-		return nil, errors.Wrapf(err, "cannot join IPC of a non running container: %s", container.ID)
65
+		return nil, errors.Wrap(err, errMsg)
65 66
 	}
66
-	return container, daemon.checkContainer(container, containerIsRunning, containerIsNotRestarting)
67
+	// Check the container is running and not restarting
68
+	if err := daemon.checkContainer(container, containerIsRunning, containerIsNotRestarting); err != nil {
69
+		return nil, errors.Wrap(err, errMsg)
70
+	}
71
+	// Check the container ipc is shareable
72
+	if st, err := os.Stat(container.ShmPath); err != nil || !st.IsDir() {
73
+		if err == nil || os.IsNotExist(err) {
74
+			return nil, errors.New(errMsg + ": non-shareable IPC")
75
+		}
76
+		// stat() failed?
77
+		return nil, errors.Wrap(err, errMsg+": unexpected error from stat "+container.ShmPath)
78
+	}
79
+
80
+	return container, nil
67 81
 }
68 82
 
69 83
 func (daemon *Daemon) getPidContainer(container *container.Container) (*container.Container, error) {
... ...
@@ -90,25 +104,33 @@ func containerIsNotRestarting(c *container.Container) error {
90 90
 }
91 91
 
92 92
 func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
93
-	var err error
94
-
95
-	c.ShmPath, err = c.ShmResourcePath()
96
-	if err != nil {
97
-		return err
98
-	}
93
+	ipcMode := c.HostConfig.IpcMode
99 94
 
100
-	if c.HostConfig.IpcMode.IsContainer() {
101
-		ic, err := daemon.getIpcContainer(c)
95
+	switch {
96
+	case ipcMode.IsContainer():
97
+		ic, err := daemon.getIpcContainer(ipcMode.Container())
102 98
 		if err != nil {
103 99
 			return err
104 100
 		}
105 101
 		c.ShmPath = ic.ShmPath
106
-	} else if c.HostConfig.IpcMode.IsHost() {
102
+
103
+	case ipcMode.IsHost():
107 104
 		if _, err := os.Stat("/dev/shm"); err != nil {
108 105
 			return fmt.Errorf("/dev/shm is not mounted, but must be for --ipc=host")
109 106
 		}
110 107
 		c.ShmPath = "/dev/shm"
111
-	} else {
108
+
109
+	case ipcMode.IsPrivate(), ipcMode.IsNone():
110
+		// c.ShmPath will/should not be used, so make it empty.
111
+		// Container's /dev/shm mount comes from OCI spec.
112
+		c.ShmPath = ""
113
+
114
+	case ipcMode.IsEmpty():
115
+		// A container was created by an older version of the daemon.
116
+		// The default behavior used to be what is now called "shareable".
117
+		fallthrough
118
+
119
+	case ipcMode.IsShareable():
112 120
 		rootIDs := daemon.idMappings.RootPair()
113 121
 		if !c.HasMountFor("/dev/shm") {
114 122
 			shmPath, err := c.ShmResourcePath()
... ...
@@ -120,19 +142,18 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
120 120
 				return err
121 121
 			}
122 122
 
123
-			shmSize := int64(daemon.configStore.ShmSize)
124
-			if c.HostConfig.ShmSize != 0 {
125
-				shmSize = c.HostConfig.ShmSize
126
-			}
127
-			shmproperty := "mode=1777,size=" + strconv.FormatInt(shmSize, 10)
123
+			shmproperty := "mode=1777,size=" + strconv.FormatInt(c.HostConfig.ShmSize, 10)
128 124
 			if err := unix.Mount("shm", shmPath, "tmpfs", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), label.FormatMountLabel(shmproperty, c.GetMountLabel())); err != nil {
129 125
 				return fmt.Errorf("mounting shm tmpfs: %s", err)
130 126
 			}
131 127
 			if err := os.Chown(shmPath, rootIDs.UID, rootIDs.GID); err != nil {
132 128
 				return err
133 129
 			}
130
+			c.ShmPath = shmPath
134 131
 		}
135 132
 
133
+	default:
134
+		return fmt.Errorf("invalid IPC mode: %v", ipcMode)
136 135
 	}
137 136
 
138 137
 	return nil
... ...
@@ -308,7 +308,8 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
308 308
 
309 309
 // reloadPlatform updates configuration with platform specific options
310 310
 // and updates the passed attributes
311
-func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) {
311
+func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) error {
312
+	return nil
312 313
 }
313 314
 
314 315
 // verifyDaemonSettings performs validation of daemon config struct
... ...
@@ -276,6 +276,15 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
276 276
 			hostConfig.ShmSize = int64(daemon.configStore.ShmSize)
277 277
 		}
278 278
 	}
279
+	// Set default IPC mode, if unset for container
280
+	if hostConfig.IpcMode.IsEmpty() {
281
+		m := config.DefaultIpcMode
282
+		if daemon.configStore != nil {
283
+			m = daemon.configStore.IpcMode
284
+		}
285
+		hostConfig.IpcMode = containertypes.IpcMode(m)
286
+	}
287
+
279 288
 	var err error
280 289
 	opts, err := daemon.generateSecurityOpt(hostConfig)
281 290
 	if err != nil {
... ...
@@ -581,7 +590,11 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
581 581
 
582 582
 // reloadPlatform updates configuration with platform specific options
583 583
 // and updates the passed attributes
584
-func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) {
584
+func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) error {
585
+	if err := conf.ValidatePlatformConfig(); err != nil {
586
+		return err
587
+	}
588
+
585 589
 	if conf.IsValueSet("runtimes") {
586 590
 		daemon.configStore.Runtimes = conf.Runtimes
587 591
 		// Always set the default one
... ...
@@ -596,6 +609,10 @@ func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]
596 596
 		daemon.configStore.ShmSize = conf.ShmSize
597 597
 	}
598 598
 
599
+	if conf.IpcMode != "" {
600
+		daemon.configStore.IpcMode = conf.IpcMode
601
+	}
602
+
599 603
 	// Update attributes
600 604
 	var runtimeList bytes.Buffer
601 605
 	for name, rt := range daemon.configStore.Runtimes {
... ...
@@ -608,6 +625,9 @@ func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]
608 608
 	attributes["runtimes"] = runtimeList.String()
609 609
 	attributes["default-runtime"] = daemon.configStore.DefaultRuntime
610 610
 	attributes["default-shm-size"] = fmt.Sprintf("%d", daemon.configStore.ShmSize)
611
+	attributes["default-ipc-mode"] = daemon.configStore.IpcMode
612
+
613
+	return nil
611 614
 }
612 615
 
613 616
 // verifyDaemonSettings performs validation of daemon config struct
... ...
@@ -231,7 +231,8 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
231 231
 
232 232
 // reloadPlatform updates configuration with platform specific options
233 233
 // and updates the passed attributes
234
-func (daemon *Daemon) reloadPlatform(config *config.Config, attributes map[string]string) {
234
+func (daemon *Daemon) reloadPlatform(config *config.Config, attributes map[string]string) error {
235
+	return nil
235 236
 }
236 237
 
237 238
 // verifyDaemonSettings performs validation of daemon config struct
... ...
@@ -301,10 +301,13 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error
301 301
 		}
302 302
 		setNamespace(s, ns)
303 303
 	}
304
+
304 305
 	// ipc
305
-	if c.HostConfig.IpcMode.IsContainer() {
306
+	ipcMode := c.HostConfig.IpcMode
307
+	switch {
308
+	case ipcMode.IsContainer():
306 309
 		ns := specs.LinuxNamespace{Type: "ipc"}
307
-		ic, err := daemon.getIpcContainer(c)
310
+		ic, err := daemon.getIpcContainer(ipcMode.Container())
308 311
 		if err != nil {
309 312
 			return err
310 313
 		}
... ...
@@ -316,12 +319,19 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error
316 316
 			nsUser.Path = fmt.Sprintf("/proc/%d/ns/user", ic.State.GetPID())
317 317
 			setNamespace(s, nsUser)
318 318
 		}
319
-	} else if c.HostConfig.IpcMode.IsHost() {
319
+	case ipcMode.IsHost():
320 320
 		oci.RemoveNamespace(s, specs.LinuxNamespaceType("ipc"))
321
-	} else {
321
+	case ipcMode.IsEmpty():
322
+		// A container was created by an older version of the daemon.
323
+		// The default behavior used to be what is now called "shareable".
324
+		fallthrough
325
+	case ipcMode.IsPrivate(), ipcMode.IsShareable(), ipcMode.IsNone():
322 326
 		ns := specs.LinuxNamespace{Type: "ipc"}
323 327
 		setNamespace(s, ns)
328
+	default:
329
+		return fmt.Errorf("Invalid IPC mode: %v", ipcMode)
324 330
 	}
331
+
325 332
 	// pid
326 333
 	if c.HostConfig.PidMode.IsContainer() {
327 334
 		ns := specs.LinuxNamespace{Type: "pid"}
... ...
@@ -486,10 +496,16 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
486 486
 		userMounts[m.Destination] = struct{}{}
487 487
 	}
488 488
 
489
-	// Filter out mounts that are overridden by user supplied mounts
489
+	// Filter out mounts from spec
490
+	noIpc := c.HostConfig.IpcMode.IsNone()
490 491
 	var defaultMounts []specs.Mount
491 492
 	_, mountDev := userMounts["/dev"]
492 493
 	for _, m := range s.Mounts {
494
+		// filter out /dev/shm mount if case IpcMode is none
495
+		if noIpc && m.Destination == "/dev/shm" {
496
+			continue
497
+		}
498
+		// filter out mount overridden by a user supplied mount
493 499
 		if _, ok := userMounts[m.Destination]; !ok {
494 500
 			if mountDev && strings.HasPrefix(m.Destination, "/dev/") {
495 501
 				continue
... ...
@@ -589,6 +605,14 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
589 589
 		s.Linux.MaskedPaths = nil
590 590
 	}
591 591
 
592
+	// Set size for /dev/shm mount that comes from spec (IpcMode: private only)
593
+	for i, m := range s.Mounts {
594
+		if m.Destination == "/dev/shm" {
595
+			sizeOpt := "size=" + strconv.FormatInt(c.HostConfig.ShmSize, 10)
596
+			s.Mounts[i].Options = append(s.Mounts[i].Options, sizeOpt)
597
+		}
598
+	}
599
+
592 600
 	// TODO: until a kernel/mount solution exists for handling remount in a user namespace,
593 601
 	// we must clear the readonly flag for the cgroups mount (@mrunalp concurs)
594 602
 	if uidMap := daemon.idMappings.UIDs(); uidMap != nil || c.HostConfig.Privileged {
... ...
@@ -745,7 +769,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
745 745
 		return nil, err
746 746
 	}
747 747
 
748
-	ms = append(ms, c.IpcMounts()...)
748
+	if !c.HostConfig.IpcMode.IsPrivate() && !c.HostConfig.IpcMode.IsEmpty() {
749
+		ms = append(ms, c.IpcMounts()...)
750
+	}
749 751
 
750 752
 	tmpfsMounts, err := c.TmpfsMounts()
751 753
 	if err != nil {
... ...
@@ -37,7 +37,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
37 37
 		}
38 38
 	}()
39 39
 
40
-	daemon.reloadPlatform(conf, attributes)
40
+	if err := daemon.reloadPlatform(conf, attributes); err != nil {
41
+		return err
42
+	}
41 43
 	daemon.reloadDebug(conf, attributes)
42 44
 	daemon.reloadMaxConcurrentDownloadsAndUploads(conf, attributes)
43 45
 	daemon.reloadShutdownTimeout(conf, attributes)
... ...
@@ -198,7 +198,9 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
198 198
 func (daemon *Daemon) Cleanup(container *container.Container) {
199 199
 	daemon.releaseNetwork(container)
200 200
 
201
-	container.UnmountIpcMounts(detachMounted)
201
+	if err := container.UnmountIpcMount(detachMounted); err != nil {
202
+		logrus.Warnf("%s cleanup: failed to unmount IPC: %s", container.ID, err)
203
+	}
202 204
 
203 205
 	if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
204 206
 		// FIXME: remove once reference counting for graphdrivers has been refactored
... ...
@@ -17,6 +17,9 @@ keywords: "API, Docker, rcli, REST, documentation"
17 17
 
18 18
 [Docker Engine API v1.32](https://docs.docker.com/engine/api/v1.32/) documentation
19 19
 
20
+* `POST /containers/create` now accepts additional values for the
21
+  `HostConfig.IpcMode` property. New values are `private`, `shareable`,
22
+  and `none`.
20 23
 
21 24
 ## v1.31 API changes
22 25
 
23 26
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) {
... ...
@@ -117,6 +117,12 @@ func DefaultLinuxSpec() specs.Spec {
117 117
 			Source:      "mqueue",
118 118
 			Options:     []string{"nosuid", "noexec", "nodev"},
119 119
 		},
120
+		{
121
+			Destination: "/dev/shm",
122
+			Type:        "tmpfs",
123
+			Source:      "shm",
124
+			Options:     []string{"nosuid", "noexec", "nodev", "mode=1777"},
125
+		},
120 126
 	}
121 127
 	s.Process.Capabilities = &specs.LinuxCapabilities{
122 128
 		Bounding:    defaultCapabilities(),
... ...
@@ -10,6 +10,7 @@ import (
10 10
 
11 11
 	"github.com/docker/docker/api/types/container"
12 12
 	"github.com/docker/docker/pkg/sysinfo"
13
+	"github.com/stretchr/testify/assert"
13 14
 )
14 15
 
15 16
 // TODO Windows: This will need addressing for a Windows daemon.
... ...
@@ -61,43 +62,33 @@ func TestNetworkModeTest(t *testing.T) {
61 61
 }
62 62
 
63 63
 func TestIpcModeTest(t *testing.T) {
64
-	ipcModes := map[container.IpcMode][]bool{
65
-		// private, host, container, valid
66
-		"":                         {true, false, false, true},
67
-		"something:weird":          {true, false, false, false},
68
-		":weird":                   {true, false, false, true},
69
-		"host":                     {false, true, false, true},
70
-		"container:name":           {false, false, true, true},
71
-		"container:name:something": {false, false, true, false},
72
-		"container:":               {false, false, true, false},
64
+	ipcModes := map[container.IpcMode]struct {
65
+		private   bool
66
+		host      bool
67
+		container bool
68
+		shareable bool
69
+		valid     bool
70
+		ctrName   string
71
+	}{
72
+		"":                      {valid: true},
73
+		"private":               {private: true, valid: true},
74
+		"something:weird":       {},
75
+		":weird":                {},
76
+		"host":                  {host: true, valid: true},
77
+		"container":             {},
78
+		"container:":            {container: true, valid: true, ctrName: ""},
79
+		"container:name":        {container: true, valid: true, ctrName: "name"},
80
+		"container:name1:name2": {container: true, valid: true, ctrName: "name1:name2"},
81
+		"shareable":             {shareable: true, valid: true},
73 82
 	}
83
+
74 84
 	for ipcMode, state := range ipcModes {
75
-		if ipcMode.IsPrivate() != state[0] {
76
-			t.Fatalf("IpcMode.IsPrivate for %v should have been %v but was %v", ipcMode, state[0], ipcMode.IsPrivate())
77
-		}
78
-		if ipcMode.IsHost() != state[1] {
79
-			t.Fatalf("IpcMode.IsHost for %v should have been %v but was %v", ipcMode, state[1], ipcMode.IsHost())
80
-		}
81
-		if ipcMode.IsContainer() != state[2] {
82
-			t.Fatalf("IpcMode.IsContainer for %v should have been %v but was %v", ipcMode, state[2], ipcMode.IsContainer())
83
-		}
84
-		if ipcMode.Valid() != state[3] {
85
-			t.Fatalf("IpcMode.Valid for %v should have been %v but was %v", ipcMode, state[3], ipcMode.Valid())
86
-		}
87
-	}
88
-	containerIpcModes := map[container.IpcMode]string{
89
-		"":                      "",
90
-		"something":             "",
91
-		"something:weird":       "weird",
92
-		"container":             "",
93
-		"container:":            "",
94
-		"container:name":        "name",
95
-		"container:name1:name2": "name1:name2",
96
-	}
97
-	for ipcMode, container := range containerIpcModes {
98
-		if ipcMode.Container() != container {
99
-			t.Fatalf("Expected %v for %v but was %v", container, ipcMode, ipcMode.Container())
100
-		}
85
+		assert.Equal(t, state.private, ipcMode.IsPrivate(), "IpcMode.IsPrivate() parsing failed for %q", ipcMode)
86
+		assert.Equal(t, state.host, ipcMode.IsHost(), "IpcMode.IsHost()  parsing failed for %q", ipcMode)
87
+		assert.Equal(t, state.container, ipcMode.IsContainer(), "IpcMode.IsContainer()  parsing failed for %q", ipcMode)
88
+		assert.Equal(t, state.shareable, ipcMode.IsShareable(), "IpcMode.IsShareable()  parsing failed for %q", ipcMode)
89
+		assert.Equal(t, state.valid, ipcMode.Valid(), "IpcMode.Valid()  parsing failed for %q", ipcMode)
90
+		assert.Equal(t, state.ctrName, ipcMode.Container(), "IpcMode.Container() parsing failed for %q", ipcMode)
101 91
 	}
102 92
 }
103 93