Browse code

Fix volumes-from re-applying on each start

Fixes #9709
In cases where the volumes-from container is removed and the consuming
container is restarted, docker was trying to re-apply volumes from that
now missing container, which is uneccessary since the volumes are
already applied.

Also cleaned up the volumes-from parsing function, which was doing way more than
it should have been.

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

Brian Goff authored on 2014/12/18 23:57:36
Showing 3 changed files
... ...
@@ -92,9 +92,10 @@ type Container struct {
92 92
 	VolumesRW  map[string]bool
93 93
 	hostConfig *runconfig.HostConfig
94 94
 
95
-	activeLinks  map[string]*links.Link
96
-	monitor      *containerMonitor
97
-	execCommands *execStore
95
+	activeLinks        map[string]*links.Link
96
+	monitor            *containerMonitor
97
+	execCommands       *execStore
98
+	AppliedVolumesFrom map[string]struct{}
98 99
 }
99 100
 
100 101
 func (container *Container) FromDisk() error {
... ...
@@ -214,20 +214,61 @@ func parseBindMountSpec(spec string) (string, string, bool, error) {
214 214
 	return path, mountToPath, writable, nil
215 215
 }
216 216
 
217
+func parseVolumesFromSpec(spec string) (string, string, error) {
218
+	specParts := strings.SplitN(spec, ":", 2)
219
+	if len(specParts) == 0 {
220
+		return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec)
221
+	}
222
+
223
+	var (
224
+		id   = specParts[0]
225
+		mode = "rw"
226
+	)
227
+	if len(specParts) == 2 {
228
+		mode = specParts[1]
229
+		if !validMountMode(mode) {
230
+			return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode)
231
+		}
232
+	}
233
+	return id, mode, nil
234
+}
235
+
217 236
 func (container *Container) applyVolumesFrom() error {
218 237
 	volumesFrom := container.hostConfig.VolumesFrom
238
+	if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil {
239
+		container.AppliedVolumesFrom = make(map[string]struct{})
240
+	}
219 241
 
220
-	mountGroups := make([]map[string]*Mount, 0, len(volumesFrom))
242
+	mountGroups := make(map[string][]*Mount)
221 243
 
222 244
 	for _, spec := range volumesFrom {
223
-		mountGroup, err := parseVolumesFromSpec(container.daemon, spec)
245
+		id, mode, err := parseVolumesFromSpec(spec)
224 246
 		if err != nil {
225 247
 			return err
226 248
 		}
227
-		mountGroups = append(mountGroups, mountGroup)
249
+		if _, exists := container.AppliedVolumesFrom[id]; exists {
250
+			// Don't try to apply these since they've already been applied
251
+			continue
252
+		}
253
+
254
+		c := container.daemon.Get(id)
255
+		if c == nil {
256
+			return fmt.Errorf("container %s not found, impossible to mount its volumes", id)
257
+		}
258
+
259
+		var (
260
+			fromMounts = c.VolumeMounts()
261
+			mounts     []*Mount
262
+		)
263
+
264
+		for _, mnt := range fromMounts {
265
+			mnt.Writable = mnt.Writable && (mode == "rw")
266
+			mounts = append(mounts, mnt)
267
+		}
268
+		mountGroups[id] = mounts
228 269
 	}
229 270
 
230
-	for _, mounts := range mountGroups {
271
+	for id, mounts := range mountGroups {
231 272
 		for _, mnt := range mounts {
232 273
 			mnt.from = mnt.container
233 274
 			mnt.container = container
... ...
@@ -235,6 +276,7 @@ func (container *Container) applyVolumesFrom() error {
235 235
 				return err
236 236
 			}
237 237
 		}
238
+		container.AppliedVolumesFrom[id] = struct{}{}
238 239
 	}
239 240
 	return nil
240 241
 }
... ...
@@ -284,36 +326,6 @@ func (container *Container) setupMounts() error {
284 284
 	return nil
285 285
 }
286 286
 
287
-func parseVolumesFromSpec(daemon *Daemon, spec string) (map[string]*Mount, error) {
288
-	specParts := strings.SplitN(spec, ":", 2)
289
-	if len(specParts) == 0 {
290
-		return nil, fmt.Errorf("Malformed volumes-from specification: %s", spec)
291
-	}
292
-
293
-	c := daemon.Get(specParts[0])
294
-	if c == nil {
295
-		return nil, fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0])
296
-	}
297
-
298
-	mounts := c.VolumeMounts()
299
-
300
-	if len(specParts) == 2 {
301
-		mode := specParts[1]
302
-		if !validMountMode(mode) {
303
-			return nil, fmt.Errorf("Invalid mode for volumes-from: %s", mode)
304
-		}
305
-
306
-		// Set the mode for the inheritted volume
307
-		for _, mnt := range mounts {
308
-			// Ensure that if the inherited volume is not writable, that we don't make
309
-			// it writable here
310
-			mnt.Writable = mnt.Writable && (mode == "rw")
311
-		}
312
-	}
313
-
314
-	return mounts, nil
315
-}
316
-
317 287
 func (container *Container) VolumeMounts() map[string]*Mount {
318 288
 	mounts := make(map[string]*Mount)
319 289
 
... ...
@@ -428,6 +428,7 @@ func TestRunVolumesMountedAsReadonly(t *testing.T) {
428 428
 }
429 429
 
430 430
 func TestRunVolumesFromInReadonlyMode(t *testing.T) {
431
+	defer deleteAllContainers()
431 432
 	cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true")
432 433
 	if _, err := runCommand(cmd); err != nil {
433 434
 		t.Fatal(err)
... ...
@@ -438,13 +439,12 @@ func TestRunVolumesFromInReadonlyMode(t *testing.T) {
438 438
 		t.Fatalf("run should fail because volume is ro: exit code %d", code)
439 439
 	}
440 440
 
441
-	deleteAllContainers()
442
-
443 441
 	logDone("run - volumes from as readonly mount")
444 442
 }
445 443
 
446 444
 // Regression test for #1201
447 445
 func TestRunVolumesFromInReadWriteMode(t *testing.T) {
446
+	defer deleteAllContainers()
448 447
 	cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true")
449 448
 	if _, err := runCommand(cmd); err != nil {
450 449
 		t.Fatal(err)
... ...
@@ -456,7 +456,7 @@ func TestRunVolumesFromInReadWriteMode(t *testing.T) {
456 456
 	}
457 457
 
458 458
 	cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:bar", "busybox", "touch", "/test/file")
459
-	if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "Invalid mode for volumes-from: bar") {
459
+	if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "invalid mode for volumes-from: bar") {
460 460
 		t.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out)
461 461
 	}
462 462
 
... ...
@@ -465,12 +465,11 @@ func TestRunVolumesFromInReadWriteMode(t *testing.T) {
465 465
 		t.Fatalf("running --volumes-from parent failed with output: %q\nerror: %v", out, err)
466 466
 	}
467 467
 
468
-	deleteAllContainers()
469
-
470 468
 	logDone("run - volumes from as read write mount")
471 469
 }
472 470
 
473 471
 func TestVolumesFromGetsProperMode(t *testing.T) {
472
+	defer deleteAllContainers()
474 473
 	cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test:/test:ro", "busybox", "true")
475 474
 	if _, err := runCommand(cmd); err != nil {
476 475
 		t.Fatal(err)
... ...
@@ -491,8 +490,6 @@ func TestVolumesFromGetsProperMode(t *testing.T) {
491 491
 		t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `ro`")
492 492
 	}
493 493
 
494
-	deleteAllContainers()
495
-
496 494
 	logDone("run - volumes from ignores `rw` if inherrited volume is `ro`")
497 495
 }
498 496
 
... ...
@@ -3058,3 +3055,31 @@ func TestRunContainerWithReadonlyRootfs(t *testing.T) {
3058 3058
 	}
3059 3059
 	logDone("run - read only rootfs")
3060 3060
 }
3061
+
3062
+func TestRunVolumesFromRestartAfterRemoved(t *testing.T) {
3063
+	defer deleteAllContainers()
3064
+
3065
+	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "voltest", "-v", "/foo", "busybox"))
3066
+	if err != nil {
3067
+		t.Fatal(out, err)
3068
+	}
3069
+
3070
+	out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "restarter", "--volumes-from", "voltest", "busybox", "top"))
3071
+	if err != nil {
3072
+		t.Fatal(out, err)
3073
+	}
3074
+
3075
+	// Remove the main volume container and restart the consuming container
3076
+	out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "rm", "-f", "voltest"))
3077
+	if err != nil {
3078
+		t.Fatal(out, err)
3079
+	}
3080
+
3081
+	// This should not fail since the volumes-from were already applied
3082
+	out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "restart", "restarter"))
3083
+	if err != nil {
3084
+		t.Fatalf("expected container to restart successfully: %v\n%s", err, out)
3085
+	}
3086
+
3087
+	logDone("run - can restart a volumes-from container after producer is removed")
3088
+}