Browse code

Fix volumes-from/bind-mounts passed in on start

Fixes #9628
Slightly reverts #8683, HostConfig on start is _not_ deprecated.

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

Brian Goff authored on 2014/12/13 01:01:05
Showing 3 changed files
... ...
@@ -24,6 +24,7 @@ type Mount struct {
24 24
 	volume      *volumes.Volume
25 25
 	Writable    bool
26 26
 	copyData    bool
27
+	from        *Container
27 28
 }
28 29
 
29 30
 func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
... ...
@@ -42,9 +43,6 @@ func (container *Container) prepareVolumes() error {
42 42
 	if container.Volumes == nil || len(container.Volumes) == 0 {
43 43
 		container.Volumes = make(map[string]string)
44 44
 		container.VolumesRW = make(map[string]bool)
45
-		if err := container.applyVolumesFrom(); err != nil {
46
-			return err
47
-		}
48 45
 	}
49 46
 
50 47
 	return container.createVolumes()
... ...
@@ -73,13 +71,27 @@ func (container *Container) createVolumes() error {
73 73
 		}
74 74
 	}
75 75
 
76
-	return nil
76
+	// On every start, this will apply any new `VolumesFrom` entries passed in via HostConfig, which may override volumes set in `create`
77
+	return container.applyVolumesFrom()
77 78
 }
78 79
 
79 80
 func (m *Mount) initialize() error {
80 81
 	// No need to initialize anything since it's already been initialized
81
-	if _, exists := m.container.Volumes[m.MountToPath]; exists {
82
-		return nil
82
+	if hostPath, exists := m.container.Volumes[m.MountToPath]; exists {
83
+		// If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create
84
+		// We need to make sure bind-mounts/volumes-from passed on start can override existing ones.
85
+		if !m.volume.IsBindMount && m.from == nil {
86
+			return nil
87
+		}
88
+		if m.volume.Path == hostPath {
89
+			return nil
90
+		}
91
+
92
+		// Make sure we remove these old volumes we don't actually want now.
93
+		// Ignore any errors here since this is just cleanup, maybe someone volumes-from'd this volume
94
+		v := m.container.daemon.volumes.Get(hostPath)
95
+		v.RemoveContainer(m.container.ID)
96
+		m.container.daemon.volumes.Delete(v.Path)
83 97
 	}
84 98
 
85 99
 	// This is the full path to container fs + mntToPath
... ...
@@ -217,6 +229,7 @@ func (container *Container) applyVolumesFrom() error {
217 217
 
218 218
 	for _, mounts := range mountGroups {
219 219
 		for _, mnt := range mounts {
220
+			mnt.from = mnt.container
220 221
 			mnt.container = container
221 222
 			if err := mnt.initialize(); err != nil {
222 223
 				return err
... ...
@@ -61,12 +61,6 @@ You can set the new container's MAC address explicitly.
61 61
 **New!**
62 62
 Volumes are now initialized when the container is created.
63 63
 
64
-`POST /containers/(id)/start`
65
-
66
-**New!**
67
-Passing the container's `HostConfig` on start is now deprecated.  You should
68
-set this when creating the container.
69
-
70 64
 `POST /containers/(id)/copy`
71 65
 
72 66
 **New!**
... ...
@@ -4,7 +4,10 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io"
7
+	"io/ioutil"
8
+	"os"
7 9
 	"os/exec"
10
+	"strings"
8 11
 	"testing"
9 12
 
10 13
 	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
... ...
@@ -120,3 +123,131 @@ func TestContainerApiGetChanges(t *testing.T) {
120 120
 
121 121
 	logDone("container REST API - check GET containers/changes")
122 122
 }
123
+
124
+func TestContainerApiStartVolumeBinds(t *testing.T) {
125
+	defer deleteAllContainers()
126
+	name := "testing"
127
+	config := map[string]interface{}{
128
+		"Image":   "busybox",
129
+		"Volumes": map[string]struct{}{"/tmp": {}},
130
+	}
131
+
132
+	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
133
+		t.Fatal(err)
134
+	}
135
+
136
+	bindPath, err := ioutil.TempDir(os.TempDir(), "test")
137
+	if err != nil {
138
+		t.Fatal(err)
139
+	}
140
+
141
+	config = map[string]interface{}{
142
+		"Binds": []string{bindPath + ":/tmp"},
143
+	}
144
+	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
145
+		t.Fatal(err)
146
+	}
147
+
148
+	pth, err := inspectFieldMap(name, "Volumes", "/tmp")
149
+	if err != nil {
150
+		t.Fatal(err)
151
+	}
152
+
153
+	if pth != bindPath {
154
+		t.Fatalf("expected volume host path to be %s, got %s", bindPath, pth)
155
+	}
156
+
157
+	logDone("container REST API - check volume binds on start")
158
+}
159
+
160
+func TestContainerApiStartVolumesFrom(t *testing.T) {
161
+	defer deleteAllContainers()
162
+	volName := "voltst"
163
+	volPath := "/tmp"
164
+
165
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
166
+		t.Fatal(out, err)
167
+	}
168
+
169
+	name := "testing"
170
+	config := map[string]interface{}{
171
+		"Image":   "busybox",
172
+		"Volumes": map[string]struct{}{volPath: {}},
173
+	}
174
+
175
+	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
176
+		t.Fatal(err)
177
+	}
178
+
179
+	config = map[string]interface{}{
180
+		"VolumesFrom": []string{volName},
181
+	}
182
+	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
183
+		t.Fatal(err)
184
+	}
185
+
186
+	pth, err := inspectFieldMap(name, "Volumes", volPath)
187
+	if err != nil {
188
+		t.Fatal(err)
189
+	}
190
+	pth2, err := inspectFieldMap(volName, "Volumes", volPath)
191
+	if err != nil {
192
+		t.Fatal(err)
193
+	}
194
+
195
+	if pth != pth2 {
196
+		t.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
197
+	}
198
+
199
+	logDone("container REST API - check VolumesFrom on start")
200
+}
201
+
202
+// Ensure that volumes-from has priority over binds/anything else
203
+// This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start
204
+func TestVolumesFromHasPriority(t *testing.T) {
205
+	defer deleteAllContainers()
206
+	volName := "voltst"
207
+	volPath := "/tmp"
208
+
209
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
210
+		t.Fatal(out, err)
211
+	}
212
+
213
+	name := "testing"
214
+	config := map[string]interface{}{
215
+		"Image":   "busybox",
216
+		"Volumes": map[string]struct{}{volPath: {}},
217
+	}
218
+
219
+	if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
220
+		t.Fatal(err)
221
+	}
222
+
223
+	bindPath, err := ioutil.TempDir(os.TempDir(), "test")
224
+	if err != nil {
225
+		t.Fatal(err)
226
+	}
227
+
228
+	config = map[string]interface{}{
229
+		"VolumesFrom": []string{volName},
230
+		"Binds":       []string{bindPath + ":/tmp"},
231
+	}
232
+	if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
233
+		t.Fatal(err)
234
+	}
235
+
236
+	pth, err := inspectFieldMap(name, "Volumes", volPath)
237
+	if err != nil {
238
+		t.Fatal(err)
239
+	}
240
+	pth2, err := inspectFieldMap(volName, "Volumes", volPath)
241
+	if err != nil {
242
+		t.Fatal(err)
243
+	}
244
+
245
+	if pth != pth2 {
246
+		t.Fatalf("expected volume host path to be %s, got %s", pth, pth2)
247
+	}
248
+
249
+	logDone("container REST API - check VolumesFrom has priority")
250
+}