Browse code

Fix: Failed Start breaks VolumesFrom

Running parseVolumesFromSpec on all VolumesFrom specs before initialize
any mounts endures that we don't leave container.Volumes in an
inconsistent (partially initialized) if one of out mount groups is not
available (e.g. the container we're trying to mount from does not
exist).

Keeping container.Volumes in a consistent state ensures that next time
we Start() the container, it'll run prepareVolumes() again.

The attached test demonstrates that when a container fails to start due
to a missing container specified in VolumesFrom, it "remembers" a Volume
that worked.

Fixes: #8726

Signed-off-by: Thomas Orozco <thomas@orozco.fr>

Thomas Orozco authored on 2014/10/23 21:50:06
Showing 2 changed files
... ...
@@ -204,15 +204,20 @@ func parseBindMountSpec(spec string) (string, string, bool, error) {
204 204
 func (container *Container) applyVolumesFrom() error {
205 205
 	volumesFrom := container.hostConfig.VolumesFrom
206 206
 
207
+	mountGroups := make([]map[string]*Mount, 0, len(volumesFrom))
208
+
207 209
 	for _, spec := range volumesFrom {
208
-		mounts, err := parseVolumesFromSpec(container.daemon, spec)
210
+		mountGroup, err := parseVolumesFromSpec(container.daemon, spec)
209 211
 		if err != nil {
210 212
 			return err
211 213
 		}
214
+		mountGroups = append(mountGroups, mountGroup)
215
+	}
212 216
 
217
+	for _, mounts := range mountGroups {
213 218
 		for _, mnt := range mounts {
214 219
 			mnt.container = container
215
-			if err = mnt.initialize(); err != nil {
220
+			if err := mnt.initialize(); err != nil {
216 221
 				return err
217 222
 			}
218 223
 		}
... ...
@@ -109,3 +109,31 @@ func TestStartRecordError(t *testing.T) {
109 109
 
110 110
 	logDone("start - set state error when start fails")
111 111
 }
112
+
113
+// gh#8726: a failed Start() breaks --volumes-from on subsequent Start()'s
114
+func TestStartVolumesFromFailsCleanly(t *testing.T) {
115
+	defer deleteAllContainers()
116
+
117
+	// Create the first data volume
118
+	cmd(t, "run", "-d", "--name", "data_before", "-v", "/foo", "busybox")
119
+
120
+	// Expect this to fail because the data test after contaienr doesn't exist yet
121
+	if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "consumer", "--volumes-from", "data_before", "--volumes-from", "data_after", "busybox")); err == nil {
122
+		t.Fatal("Expected error but got none")
123
+	}
124
+
125
+	// Create the second data volume
126
+	cmd(t, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox")
127
+
128
+	// Now, all the volumes should be there
129
+	cmd(t, "start", "consumer")
130
+
131
+	// Check that we have the volumes we want
132
+	out, _, _ := cmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer")
133
+	n_volumes := strings.Trim(out, " \r\n'")
134
+	if n_volumes != "2" {
135
+		t.Fatalf("Missing volumes: expected 2, got %s", n_volumes)
136
+	}
137
+
138
+	logDone("start - missing containers in --volumes-from did not affect subsequent runs")
139
+}