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>
| ... | ... |
@@ -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 |
+} |