Browse code

Use container Mounts API for Swarm containers.

Instead of converting nicely typed service mounts into untyped `Binds`
when creating containers, use the new `Mounts` API which is a 1-1
mapping between service mounts and container mounts.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2016/10/31 22:53:01
Showing 3 changed files
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	enginecontainer "github.com/docker/docker/api/types/container"
15 15
 	"github.com/docker/docker/api/types/events"
16 16
 	"github.com/docker/docker/api/types/filters"
17
+	enginemount "github.com/docker/docker/api/types/mount"
17 18
 	"github.com/docker/docker/api/types/network"
18 19
 	volumetypes "github.com/docker/docker/api/types/volume"
19 20
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
... ...
@@ -191,7 +192,6 @@ func (c *containerConfig) config() *enginecontainer.Config {
191 191
 		Hostname:     c.spec().Hostname,
192 192
 		WorkingDir:   c.spec().Dir,
193 193
 		Image:        c.image(),
194
-		Volumes:      c.volumes(),
195 194
 		ExposedPorts: c.exposedPorts(),
196 195
 		Healthcheck:  c.healthcheck(),
197 196
 	}
... ...
@@ -243,49 +243,79 @@ func (c *containerConfig) labels() map[string]string {
243 243
 	return labels
244 244
 }
245 245
 
246
-// volumes gets placed into the Volumes field on the containerConfig.
247
-func (c *containerConfig) volumes() map[string]struct{} {
248
-	r := make(map[string]struct{})
249
-	// Volumes *only* creates anonymous volumes. The rest is mixed in with
250
-	// binds, which aren't actually binds. Basically, any volume that
251
-	// results in a single component must be added here.
252
-	//
253
-	// This is reversed engineered from the behavior of the engine API.
246
+func (c *containerConfig) mounts() []enginemount.Mount {
247
+	var r []enginemount.Mount
254 248
 	for _, mount := range c.spec().Mounts {
255
-		if mount.Type == api.MountTypeVolume && mount.Source == "" {
256
-			r[mount.Target] = struct{}{}
257
-		}
249
+		r = append(r, convertMount(mount))
258 250
 	}
259 251
 	return r
260 252
 }
261 253
 
262
-func (c *containerConfig) tmpfs() map[string]string {
263
-	r := make(map[string]string)
264
-
265
-	for _, spec := range c.spec().Mounts {
266
-		if spec.Type != api.MountTypeTmpfs {
267
-			continue
268
-		}
254
+func convertMount(m api.Mount) enginemount.Mount {
255
+	mount := enginemount.Mount{
256
+		Source:   m.Source,
257
+		Target:   m.Target,
258
+		ReadOnly: m.ReadOnly,
259
+	}
269 260
 
270
-		r[spec.Target] = getMountMask(&spec)
261
+	switch m.Type {
262
+	case api.MountTypeBind:
263
+		mount.Type = enginemount.TypeBind
264
+	case api.MountTypeVolume:
265
+		mount.Type = enginemount.TypeVolume
266
+	case api.MountTypeTmpfs:
267
+		mount.Type = enginemount.TypeTmpfs
271 268
 	}
272 269
 
273
-	return r
274
-}
270
+	if m.BindOptions != nil {
271
+		mount.BindOptions = &enginemount.BindOptions{}
272
+		switch m.BindOptions.Propagation {
273
+		case api.MountPropagationRPrivate:
274
+			mount.BindOptions.Propagation = enginemount.PropagationRPrivate
275
+		case api.MountPropagationPrivate:
276
+			mount.BindOptions.Propagation = enginemount.PropagationPrivate
277
+		case api.MountPropagationRSlave:
278
+			mount.BindOptions.Propagation = enginemount.PropagationRSlave
279
+		case api.MountPropagationSlave:
280
+			mount.BindOptions.Propagation = enginemount.PropagationSlave
281
+		case api.MountPropagationRShared:
282
+			mount.BindOptions.Propagation = enginemount.PropagationRShared
283
+		case api.MountPropagationShared:
284
+			mount.BindOptions.Propagation = enginemount.PropagationShared
285
+		}
286
+	}
275 287
 
276
-func (c *containerConfig) binds() []string {
277
-	var r []string
278
-	for _, mount := range c.spec().Mounts {
279
-		if mount.Type == api.MountTypeBind || (mount.Type == api.MountTypeVolume && mount.Source != "") {
280
-			spec := fmt.Sprintf("%s:%s", mount.Source, mount.Target)
281
-			mask := getMountMask(&mount)
282
-			if mask != "" {
283
-				spec = fmt.Sprintf("%s:%s", spec, mask)
288
+	if m.VolumeOptions != nil {
289
+		mount.VolumeOptions = &enginemount.VolumeOptions{
290
+			NoCopy: m.VolumeOptions.NoCopy,
291
+		}
292
+		if m.VolumeOptions.Labels != nil {
293
+			mount.VolumeOptions.Labels = make(map[string]string, len(m.VolumeOptions.Labels))
294
+			for k, v := range m.VolumeOptions.Labels {
295
+				mount.VolumeOptions.Labels[k] = v
296
+			}
297
+		}
298
+		if m.VolumeOptions.DriverConfig != nil {
299
+			mount.VolumeOptions.DriverConfig = &enginemount.Driver{
300
+				Name: m.VolumeOptions.DriverConfig.Name,
301
+			}
302
+			if m.VolumeOptions.DriverConfig.Options != nil {
303
+				mount.VolumeOptions.DriverConfig.Options = make(map[string]string, len(m.VolumeOptions.DriverConfig.Options))
304
+				for k, v := range m.VolumeOptions.DriverConfig.Options {
305
+					mount.VolumeOptions.DriverConfig.Options[k] = v
306
+				}
284 307
 			}
285
-			r = append(r, spec)
286 308
 		}
287 309
 	}
288
-	return r
310
+
311
+	if m.TmpfsOptions != nil {
312
+		mount.TmpfsOptions = &enginemount.TmpfsOptions{
313
+			SizeBytes: m.TmpfsOptions.SizeBytes,
314
+			Mode:      m.TmpfsOptions.Mode,
315
+		}
316
+	}
317
+
318
+	return mount
289 319
 }
290 320
 
291 321
 func (c *containerConfig) healthcheck() *enginecontainer.HealthConfig {
... ...
@@ -303,88 +333,12 @@ func (c *containerConfig) healthcheck() *enginecontainer.HealthConfig {
303 303
 	}
304 304
 }
305 305
 
306
-func getMountMask(m *api.Mount) string {
307
-	var maskOpts []string
308
-	if m.ReadOnly {
309
-		maskOpts = append(maskOpts, "ro")
310
-	}
311
-
312
-	switch m.Type {
313
-	case api.MountTypeVolume:
314
-		if m.VolumeOptions != nil && m.VolumeOptions.NoCopy {
315
-			maskOpts = append(maskOpts, "nocopy")
316
-		}
317
-	case api.MountTypeBind:
318
-		if m.BindOptions == nil {
319
-			break
320
-		}
321
-
322
-		switch m.BindOptions.Propagation {
323
-		case api.MountPropagationPrivate:
324
-			maskOpts = append(maskOpts, "private")
325
-		case api.MountPropagationRPrivate:
326
-			maskOpts = append(maskOpts, "rprivate")
327
-		case api.MountPropagationShared:
328
-			maskOpts = append(maskOpts, "shared")
329
-		case api.MountPropagationRShared:
330
-			maskOpts = append(maskOpts, "rshared")
331
-		case api.MountPropagationSlave:
332
-			maskOpts = append(maskOpts, "slave")
333
-		case api.MountPropagationRSlave:
334
-			maskOpts = append(maskOpts, "rslave")
335
-		}
336
-	case api.MountTypeTmpfs:
337
-		if m.TmpfsOptions == nil {
338
-			break
339
-		}
340
-
341
-		if m.TmpfsOptions.Mode != 0 {
342
-			maskOpts = append(maskOpts, fmt.Sprintf("mode=%o", m.TmpfsOptions.Mode))
343
-		}
344
-
345
-		if m.TmpfsOptions.SizeBytes != 0 {
346
-			// calculate suffix here, making this linux specific, but that is
347
-			// okay, since API is that way anyways.
348
-
349
-			// we do this by finding the suffix that divides evenly into the
350
-			// value, returing the value itself, with no suffix, if it fails.
351
-			//
352
-			// For the most part, we don't enforce any semantic to this values.
353
-			// The operating system will usually align this and enforce minimum
354
-			// and maximums.
355
-			var (
356
-				size   = m.TmpfsOptions.SizeBytes
357
-				suffix string
358
-			)
359
-			for _, r := range []struct {
360
-				suffix  string
361
-				divisor int64
362
-			}{
363
-				{"g", 1 << 30},
364
-				{"m", 1 << 20},
365
-				{"k", 1 << 10},
366
-			} {
367
-				if size%r.divisor == 0 {
368
-					size = size / r.divisor
369
-					suffix = r.suffix
370
-					break
371
-				}
372
-			}
373
-
374
-			maskOpts = append(maskOpts, fmt.Sprintf("size=%d%s", size, suffix))
375
-		}
376
-	}
377
-
378
-	return strings.Join(maskOpts, ",")
379
-}
380
-
381 306
 func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
382 307
 	hc := &enginecontainer.HostConfig{
383 308
 		Resources:    c.resources(),
384
-		Binds:        c.binds(),
385
-		Tmpfs:        c.tmpfs(),
386 309
 		GroupAdd:     c.spec().Groups,
387 310
 		PortBindings: c.portBindings(),
311
+		Mounts:       c.mounts(),
388 312
 	}
389 313
 
390 314
 	if c.spec().DNSConfig != nil {
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"strings"
9 9
 
10 10
 	"github.com/docker/docker/api/types"
11
+	"github.com/docker/docker/api/types/mount"
11 12
 	"github.com/docker/docker/api/types/swarm"
12 13
 	"github.com/docker/docker/pkg/integration/checker"
13 14
 	"github.com/go-check/check"
... ...
@@ -15,7 +16,7 @@ import (
15 15
 
16 16
 func (s *DockerSwarmSuite) TestServiceCreateMountVolume(c *check.C) {
17 17
 	d := s.AddDaemon(c, true, true)
18
-	out, err := d.Cmd("service", "create", "--mount", "type=volume,source=foo,target=/foo", "busybox", "top")
18
+	out, err := d.Cmd("service", "create", "--mount", "type=volume,source=foo,target=/foo,volume-nocopy", "busybox", "top")
19 19
 	c.Assert(err, checker.IsNil, check.Commentf(out))
20 20
 	id := strings.TrimSpace(out)
21 21
 
... ...
@@ -33,6 +34,21 @@ func (s *DockerSwarmSuite) TestServiceCreateMountVolume(c *check.C) {
33 33
 		return task.NodeID != "" && task.Status.ContainerStatus.ContainerID != "", nil
34 34
 	}, checker.Equals, true)
35 35
 
36
+	// check container mount config
37
+	out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .HostConfig.Mounts}}", task.Status.ContainerStatus.ContainerID)
38
+	c.Assert(err, checker.IsNil, check.Commentf(out))
39
+
40
+	var mountConfig []mount.Mount
41
+	c.Assert(json.Unmarshal([]byte(out), &mountConfig), checker.IsNil)
42
+	c.Assert(mountConfig, checker.HasLen, 1)
43
+
44
+	c.Assert(mountConfig[0].Source, checker.Equals, "foo")
45
+	c.Assert(mountConfig[0].Target, checker.Equals, "/foo")
46
+	c.Assert(mountConfig[0].Type, checker.Equals, mount.TypeVolume)
47
+	c.Assert(mountConfig[0].VolumeOptions, checker.NotNil)
48
+	c.Assert(mountConfig[0].VolumeOptions.NoCopy, checker.True)
49
+
50
+	// check container mounts actual
36 51
 	out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .Mounts}}", task.Status.ContainerStatus.ContainerID)
37 52
 	c.Assert(err, checker.IsNil, check.Commentf(out))
38 53
 
... ...
@@ -40,6 +56,7 @@ func (s *DockerSwarmSuite) TestServiceCreateMountVolume(c *check.C) {
40 40
 	c.Assert(json.Unmarshal([]byte(out), &mounts), checker.IsNil)
41 41
 	c.Assert(mounts, checker.HasLen, 1)
42 42
 
43
+	c.Assert(mounts[0].Type, checker.Equals, mount.TypeVolume)
43 44
 	c.Assert(mounts[0].Name, checker.Equals, "foo")
44 45
 	c.Assert(mounts[0].Destination, checker.Equals, "/foo")
45 46
 	c.Assert(mounts[0].RW, checker.Equals, true)
... ...
@@ -103,3 +120,53 @@ func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTarget(c *check.C) {
103 103
 	c.Assert(refs[0].File, checker.Not(checker.IsNil))
104 104
 	c.Assert(refs[0].File.Name, checker.Equals, testTarget)
105 105
 }
106
+
107
+func (s *DockerSwarmSuite) TestServiceCreateMountTmpfs(c *check.C) {
108
+	d := s.AddDaemon(c, true, true)
109
+	out, err := d.Cmd("service", "create", "--mount", "type=tmpfs,target=/foo", "busybox", "sh", "-c", "mount | grep foo; tail -f /dev/null")
110
+	c.Assert(err, checker.IsNil, check.Commentf(out))
111
+	id := strings.TrimSpace(out)
112
+
113
+	var tasks []swarm.Task
114
+	waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) {
115
+		tasks = d.getServiceTasks(c, id)
116
+		return len(tasks) > 0, nil
117
+	}, checker.Equals, true)
118
+
119
+	task := tasks[0]
120
+	waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) {
121
+		if task.NodeID == "" || task.Status.ContainerStatus.ContainerID == "" {
122
+			task = d.getTask(c, task.ID)
123
+		}
124
+		return task.NodeID != "" && task.Status.ContainerStatus.ContainerID != "", nil
125
+	}, checker.Equals, true)
126
+
127
+	// check container mount config
128
+	out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .HostConfig.Mounts}}", task.Status.ContainerStatus.ContainerID)
129
+	c.Assert(err, checker.IsNil, check.Commentf(out))
130
+
131
+	var mountConfig []mount.Mount
132
+	c.Assert(json.Unmarshal([]byte(out), &mountConfig), checker.IsNil)
133
+	c.Assert(mountConfig, checker.HasLen, 1)
134
+
135
+	c.Assert(mountConfig[0].Source, checker.Equals, "")
136
+	c.Assert(mountConfig[0].Target, checker.Equals, "/foo")
137
+	c.Assert(mountConfig[0].Type, checker.Equals, mount.TypeTmpfs)
138
+
139
+	// check container mounts actual
140
+	out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .Mounts}}", task.Status.ContainerStatus.ContainerID)
141
+	c.Assert(err, checker.IsNil, check.Commentf(out))
142
+
143
+	var mounts []types.MountPoint
144
+	c.Assert(json.Unmarshal([]byte(out), &mounts), checker.IsNil)
145
+	c.Assert(mounts, checker.HasLen, 1)
146
+
147
+	c.Assert(mounts[0].Type, checker.Equals, mount.TypeTmpfs)
148
+	c.Assert(mounts[0].Name, checker.Equals, "")
149
+	c.Assert(mounts[0].Destination, checker.Equals, "/foo")
150
+	c.Assert(mounts[0].RW, checker.Equals, true)
151
+
152
+	out, err = s.nodeCmd(c, task.NodeID, "logs", task.Status.ContainerStatus.ContainerID)
153
+	c.Assert(err, checker.IsNil, check.Commentf(out))
154
+	c.Assert(strings.TrimSpace(out), checker.HasPrefix, "tmpfs on /foo type tmpfs")
155
+}
... ...
@@ -11,8 +11,6 @@ import (
11 11
 
12 12
 // ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
13 13
 // for mount(2).
14
-// The logic is copy-pasted from daemon/cluster/executer/container.getMountMask.
15
-// It will be deduplicated when we migrated the cluster to the new mount scheme.
16 14
 func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) {
17 15
 	var rawOpts []string
18 16
 	if readOnly {