Browse code

Cleanup daemon/volumes

- Mount struct now called volumeMount
- Merged volume creation for each volume type (volumes-from, binds, normal
volumes) so this only happens in once place
- Simplified container copy of volumes (for when `docker cp` is a
volume)

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

Brian Goff authored on 2014/12/17 00:06:45
Showing 2 changed files
... ...
@@ -14,17 +14,14 @@ import (
14 14
 	"github.com/docker/docker/pkg/mount"
15 15
 	"github.com/docker/docker/pkg/symlink"
16 16
 	"github.com/docker/docker/pkg/system"
17
-	"github.com/docker/docker/volumes"
18 17
 )
19 18
 
20
-type Mount struct {
21
-	MountToPath string
22
-	container   *Container
23
-	volume      *volumes.Volume
24
-	Writable    bool
25
-	copyData    bool
26
-	from        *Container
27
-	isBind      bool
19
+type volumeMount struct {
20
+	containerPath string
21
+	hostPath      string
22
+	writable      bool
23
+	copyData      bool
24
+	from          string
28 25
 }
29 26
 
30 27
 func (container *Container) prepareVolumes() error {
... ...
@@ -33,70 +30,119 @@ func (container *Container) prepareVolumes() error {
33 33
 		container.VolumesRW = make(map[string]bool)
34 34
 	}
35 35
 
36
+	if len(container.hostConfig.VolumesFrom) > 0 && container.AppliedVolumesFrom == nil {
37
+		container.AppliedVolumesFrom = make(map[string]struct{})
38
+	}
36 39
 	return container.createVolumes()
37 40
 }
38 41
 
39
-// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
40
-func (container *Container) sortedVolumeMounts() []string {
41
-	var mountPaths []string
42
-	for path := range container.Volumes {
43
-		mountPaths = append(mountPaths, path)
42
+func (container *Container) createVolumes() error {
43
+	mounts := make(map[string]*volumeMount)
44
+
45
+	// get the normal volumes
46
+	for path := range container.Config.Volumes {
47
+		path = filepath.Clean(path)
48
+		// skip if there is already a volume for this container path
49
+		if _, exists := container.Volumes[path]; exists {
50
+			continue
51
+		}
52
+
53
+		realPath, err := container.getResourcePath(path)
54
+		if err != nil {
55
+			return err
56
+		}
57
+		if stat, err := os.Stat(realPath); err == nil {
58
+			if !stat.IsDir() {
59
+				return fmt.Errorf("can't mount to container path, file exists - %s", path)
60
+			}
61
+		}
62
+
63
+		mnt := &volumeMount{
64
+			containerPath: path,
65
+			writable:      true,
66
+			copyData:      true,
67
+		}
68
+		mounts[mnt.containerPath] = mnt
44 69
 	}
45 70
 
46
-	sort.Strings(mountPaths)
47
-	return mountPaths
48
-}
71
+	// Get all the bind mounts
72
+	// track bind paths separately due to #10618
73
+	bindPaths := make(map[string]struct{})
74
+	for _, spec := range container.hostConfig.Binds {
75
+		mnt, err := parseBindMountSpec(spec)
76
+		if err != nil {
77
+			return err
78
+		}
49 79
 
50
-func (container *Container) createVolumes() error {
51
-	mounts, err := container.parseVolumeMountConfig()
52
-	if err != nil {
53
-		return err
80
+		// #10618
81
+		if _, exists := bindPaths[mnt.containerPath]; exists {
82
+			return fmt.Errorf("Duplicate volume mount %s", mnt.containerPath)
83
+		}
84
+
85
+		bindPaths[mnt.containerPath] = struct{}{}
86
+		mounts[mnt.containerPath] = mnt
54 87
 	}
55 88
 
56
-	for _, mnt := range mounts {
57
-		if err := mnt.initialize(); err != nil {
89
+	// Get volumes from
90
+	for _, from := range container.hostConfig.VolumesFrom {
91
+		cID, mode, err := parseVolumesFromSpec(from)
92
+		if err != nil {
58 93
 			return err
59 94
 		}
95
+		if _, exists := container.AppliedVolumesFrom[cID]; exists {
96
+			// skip since it's already been applied
97
+			continue
98
+		}
99
+
100
+		c, err := container.daemon.Get(cID)
101
+		if err != nil {
102
+			return fmt.Errorf("container %s not found, impossible to mount its volumes", cID)
103
+		}
104
+
105
+		for _, mnt := range c.volumeMounts() {
106
+			mnt.writable = mnt.writable && (mode == "rw")
107
+			mnt.from = cID
108
+			mounts[mnt.containerPath] = mnt
109
+		}
60 110
 	}
61 111
 
62
-	// On every start, this will apply any new `VolumesFrom` entries passed in via HostConfig, which may override volumes set in `create`
63
-	return container.applyVolumesFrom()
64
-}
112
+	for _, mnt := range mounts {
113
+		containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, mnt.containerPath), container.basefs)
114
+		if err != nil {
115
+			return err
116
+		}
65 117
 
66
-func (m *Mount) initialize() error {
67
-	// No need to initialize anything since it's already been initialized
68
-	if hostPath, exists := m.container.Volumes[m.MountToPath]; exists {
69
-		// If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create
70
-		// We need to make sure bind-mounts/volumes-from passed on start can override existing ones.
71
-		if (!m.volume.IsBindMount && !m.isBind) && m.from == nil {
72
-			return nil
118
+		// Create the actual volume
119
+		v, err := container.daemon.volumes.FindOrCreateVolume(mnt.hostPath, mnt.writable)
120
+		if err != nil {
121
+			return err
73 122
 		}
74
-		if m.volume.Path == hostPath {
75
-			return nil
123
+
124
+		container.VolumesRW[mnt.containerPath] = mnt.writable
125
+		container.Volumes[mnt.containerPath] = v.Path
126
+		v.AddContainer(container.ID)
127
+		if mnt.from != "" {
128
+			container.AppliedVolumesFrom[mnt.from] = struct{}{}
76 129
 		}
77 130
 
78
-		// Make sure we remove these old volumes we don't actually want now.
79
-		// Ignore any errors here since this is just cleanup, maybe someone volumes-from'd this volume
80
-		if v := m.container.daemon.volumes.Get(hostPath); v != nil {
81
-			v.RemoveContainer(m.container.ID)
82
-			m.container.daemon.volumes.Delete(v.Path)
131
+		if mnt.writable && mnt.copyData {
132
+			// Copy whatever is in the container at the containerPath to the volume
133
+			copyExistingContents(containerMntPath, v.Path)
83 134
 		}
84 135
 	}
85 136
 
86
-	// This is the full path to container fs + mntToPath
87
-	containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(m.container.basefs, m.MountToPath), m.container.basefs)
88
-	if err != nil {
89
-		return err
90
-	}
91
-	m.container.VolumesRW[m.MountToPath] = m.Writable
92
-	m.container.Volumes[m.MountToPath] = m.volume.Path
93
-	m.volume.AddContainer(m.container.ID)
94
-	if m.Writable && m.copyData {
95
-		// Copy whatever is in the container at the mntToPath to the volume
96
-		copyExistingContents(containerMntPath, m.volume.Path)
137
+	return nil
138
+}
139
+
140
+// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
141
+func (container *Container) sortedVolumeMounts() []string {
142
+	var mountPaths []string
143
+	for path := range container.Volumes {
144
+		mountPaths = append(mountPaths, path)
97 145
 	}
98 146
 
99
-	return nil
147
+	sort.Strings(mountPaths)
148
+	return mountPaths
100 149
 }
101 150
 
102 151
 func (container *Container) VolumePaths() map[string]struct{} {
... ...
@@ -139,97 +185,30 @@ func (container *Container) derefVolumes() {
139 139
 	}
140 140
 }
141 141
 
142
-func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) {
143
-	var mounts = make(map[string]*Mount)
144
-	// Get all the bind mounts
145
-	for _, spec := range container.hostConfig.Binds {
146
-		path, mountToPath, writable, err := parseBindMountSpec(spec)
147
-		if err != nil {
148
-			return nil, err
149
-		}
150
-		// Check if a bind mount has already been specified for the same container path
151
-		if m, exists := mounts[mountToPath]; exists {
152
-			return nil, fmt.Errorf("Duplicate volume %q: %q already in use, mounted from %q", path, mountToPath, m.volume.Path)
153
-		}
154
-		// Check if a volume already exists for this and use it
155
-		vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
156
-		if err != nil {
157
-			return nil, err
158
-		}
159
-		mounts[mountToPath] = &Mount{
160
-			container:   container,
161
-			volume:      vol,
162
-			MountToPath: mountToPath,
163
-			Writable:    writable,
164
-			isBind:      true, // in case the volume itself is a normal volume, but is being mounted in as a bindmount here
165
-		}
166
-	}
167
-
168
-	// Get the rest of the volumes
169
-	for path := range container.Config.Volumes {
170
-		// Check if this is already added as a bind-mount
171
-		path = filepath.Clean(path)
172
-		if _, exists := mounts[path]; exists {
173
-			continue
174
-		}
175
-
176
-		// Check if this has already been created
177
-		if _, exists := container.Volumes[path]; exists {
178
-			continue
179
-		}
180
-		realPath, err := container.getResourcePath(path)
181
-		if err != nil {
182
-			return nil, fmt.Errorf("failed to evaluate the absolute path of symlink")
183
-		}
184
-		if stat, err := os.Stat(realPath); err == nil {
185
-			if !stat.IsDir() {
186
-				return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath)
187
-			}
188
-		}
189
-
190
-		vol, err := container.daemon.volumes.FindOrCreateVolume("", true)
191
-		if err != nil {
192
-			return nil, err
193
-		}
194
-		mounts[path] = &Mount{
195
-			container:   container,
196
-			MountToPath: path,
197
-			volume:      vol,
198
-			Writable:    true,
199
-			copyData:    true,
200
-		}
201
-	}
202
-
203
-	return mounts, nil
204
-}
205
-
206
-func parseBindMountSpec(spec string) (string, string, bool, error) {
207
-	var (
208
-		path, mountToPath string
209
-		writable          bool
210
-		arr               = strings.Split(spec, ":")
211
-	)
142
+func parseBindMountSpec(spec string) (*volumeMount, error) {
143
+	arr := strings.Split(spec, ":")
212 144
 
145
+	mnt := &volumeMount{}
213 146
 	switch len(arr) {
214 147
 	case 2:
215
-		path = arr[0]
216
-		mountToPath = arr[1]
217
-		writable = true
148
+		mnt.hostPath = arr[0]
149
+		mnt.containerPath = arr[1]
150
+		mnt.writable = true
218 151
 	case 3:
219
-		path = arr[0]
220
-		mountToPath = arr[1]
221
-		writable = validMountMode(arr[2]) && arr[2] == "rw"
152
+		mnt.hostPath = arr[0]
153
+		mnt.containerPath = arr[1]
154
+		mnt.writable = validMountMode(arr[2]) && arr[2] == "rw"
222 155
 	default:
223
-		return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
156
+		return nil, fmt.Errorf("Invalid volume specification: %s", spec)
224 157
 	}
225 158
 
226
-	if !filepath.IsAbs(path) {
227
-		return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
159
+	if !filepath.IsAbs(mnt.hostPath) {
160
+		return nil, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", mnt.hostPath)
228 161
 	}
229 162
 
230
-	path = filepath.Clean(path)
231
-	mountToPath = filepath.Clean(mountToPath)
232
-	return path, mountToPath, writable, nil
163
+	mnt.hostPath = filepath.Clean(mnt.hostPath)
164
+	mnt.containerPath = filepath.Clean(mnt.containerPath)
165
+	return mnt, nil
233 166
 }
234 167
 
235 168
 func parseVolumesFromSpec(spec string) (string, string, error) {
... ...
@@ -251,54 +230,6 @@ func parseVolumesFromSpec(spec string) (string, string, error) {
251 251
 	return id, mode, nil
252 252
 }
253 253
 
254
-func (container *Container) applyVolumesFrom() error {
255
-	volumesFrom := container.hostConfig.VolumesFrom
256
-	if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil {
257
-		container.AppliedVolumesFrom = make(map[string]struct{})
258
-	}
259
-
260
-	mountGroups := make(map[string][]*Mount)
261
-
262
-	for _, spec := range volumesFrom {
263
-		id, mode, err := parseVolumesFromSpec(spec)
264
-		if err != nil {
265
-			return err
266
-		}
267
-		if _, exists := container.AppliedVolumesFrom[id]; exists {
268
-			// Don't try to apply these since they've already been applied
269
-			continue
270
-		}
271
-
272
-		c, err := container.daemon.Get(id)
273
-		if err != nil {
274
-			return fmt.Errorf("Could not apply volumes of non-existent container %q.", id)
275
-		}
276
-
277
-		var (
278
-			fromMounts = c.VolumeMounts()
279
-			mounts     []*Mount
280
-		)
281
-
282
-		for _, mnt := range fromMounts {
283
-			mnt.Writable = mnt.Writable && (mode == "rw")
284
-			mounts = append(mounts, mnt)
285
-		}
286
-		mountGroups[id] = mounts
287
-	}
288
-
289
-	for id, mounts := range mountGroups {
290
-		for _, mnt := range mounts {
291
-			mnt.from = mnt.container
292
-			mnt.container = container
293
-			if err := mnt.initialize(); err != nil {
294
-				return err
295
-			}
296
-		}
297
-		container.AppliedVolumesFrom[id] = struct{}{}
298
-	}
299
-	return nil
300
-}
301
-
302 254
 func validMountMode(mode string) bool {
303 255
 	validModes := map[string]bool{
304 256
 		"rw": true,
... ...
@@ -344,13 +275,17 @@ func (container *Container) setupMounts() error {
344 344
 	return nil
345 345
 }
346 346
 
347
-func (container *Container) VolumeMounts() map[string]*Mount {
348
-	mounts := make(map[string]*Mount)
347
+func (container *Container) volumeMounts() map[string]*volumeMount {
348
+	mounts := make(map[string]*volumeMount)
349 349
 
350
-	for mountToPath, path := range container.Volumes {
351
-		if v := container.daemon.volumes.Get(path); v != nil {
352
-			mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
350
+	for containerPath, path := range container.Volumes {
351
+		v := container.daemon.volumes.Get(path)
352
+		if v == nil {
353
+			// This should never happen
354
+			logrus.Debugf("reference by container %s to non-existent volume path %s", container.ID, path)
355
+			continue
353 356
 		}
357
+		mounts[containerPath] = &volumeMount{hostPath: path, containerPath: containerPath, writable: container.VolumesRW[containerPath]}
354 358
 	}
355 359
 
356 360
 	return mounts
... ...
@@ -4504,7 +4504,7 @@ func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) {
4504 4504
 
4505 4505
 	_, err := buildImage(name, `
4506 4506
 		FROM busybox
4507
-		
4507
+
4508 4508
 		ENV SOME_VAR a.b.c
4509 4509
 
4510 4510
 		RUN [ "$SOME_VAR"       = 'a.b.c' ]