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