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>
This commit is contained in:
Brian Goff 2014-12-16 10:06:45 -05:00
parent 1d48cccc99
commit f3680e7494
2 changed files with 132 additions and 197 deletions

View file

@ -14,17 +14,14 @@ import (
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/volumes"
)
type Mount struct {
MountToPath string
container *Container
volume *volumes.Volume
Writable bool
copyData bool
from *Container
isBind bool
type volumeMount struct {
containerPath string
hostPath string
writable bool
copyData bool
from string
}
func (container *Container) prepareVolumes() error {
@ -33,9 +30,110 @@ func (container *Container) prepareVolumes() error {
container.VolumesRW = make(map[string]bool)
}
if len(container.hostConfig.VolumesFrom) > 0 && container.AppliedVolumesFrom == nil {
container.AppliedVolumesFrom = make(map[string]struct{})
}
return container.createVolumes()
}
func (container *Container) createVolumes() error {
mounts := make(map[string]*volumeMount)
// get the normal volumes
for path := range container.Config.Volumes {
path = filepath.Clean(path)
// skip if there is already a volume for this container path
if _, exists := container.Volumes[path]; exists {
continue
}
realPath, err := container.getResourcePath(path)
if err != nil {
return err
}
if stat, err := os.Stat(realPath); err == nil {
if !stat.IsDir() {
return fmt.Errorf("can't mount to container path, file exists - %s", path)
}
}
mnt := &volumeMount{
containerPath: path,
writable: true,
copyData: true,
}
mounts[mnt.containerPath] = mnt
}
// Get all the bind mounts
// track bind paths separately due to #10618
bindPaths := make(map[string]struct{})
for _, spec := range container.hostConfig.Binds {
mnt, err := parseBindMountSpec(spec)
if err != nil {
return err
}
// #10618
if _, exists := bindPaths[mnt.containerPath]; exists {
return fmt.Errorf("Duplicate volume mount %s", mnt.containerPath)
}
bindPaths[mnt.containerPath] = struct{}{}
mounts[mnt.containerPath] = mnt
}
// Get volumes from
for _, from := range container.hostConfig.VolumesFrom {
cID, mode, err := parseVolumesFromSpec(from)
if err != nil {
return err
}
if _, exists := container.AppliedVolumesFrom[cID]; exists {
// skip since it's already been applied
continue
}
c, err := container.daemon.Get(cID)
if err != nil {
return fmt.Errorf("container %s not found, impossible to mount its volumes", cID)
}
for _, mnt := range c.volumeMounts() {
mnt.writable = mnt.writable && (mode == "rw")
mnt.from = cID
mounts[mnt.containerPath] = mnt
}
}
for _, mnt := range mounts {
containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, mnt.containerPath), container.basefs)
if err != nil {
return err
}
// Create the actual volume
v, err := container.daemon.volumes.FindOrCreateVolume(mnt.hostPath, mnt.writable)
if err != nil {
return err
}
container.VolumesRW[mnt.containerPath] = mnt.writable
container.Volumes[mnt.containerPath] = v.Path
v.AddContainer(container.ID)
if mnt.from != "" {
container.AppliedVolumesFrom[mnt.from] = struct{}{}
}
if mnt.writable && mnt.copyData {
// Copy whatever is in the container at the containerPath to the volume
copyExistingContents(containerMntPath, v.Path)
}
}
return nil
}
// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
func (container *Container) sortedVolumeMounts() []string {
var mountPaths []string
@ -47,58 +145,6 @@ func (container *Container) sortedVolumeMounts() []string {
return mountPaths
}
func (container *Container) createVolumes() error {
mounts, err := container.parseVolumeMountConfig()
if err != nil {
return err
}
for _, mnt := range mounts {
if err := mnt.initialize(); err != nil {
return err
}
}
// On every start, this will apply any new `VolumesFrom` entries passed in via HostConfig, which may override volumes set in `create`
return container.applyVolumesFrom()
}
func (m *Mount) initialize() error {
// No need to initialize anything since it's already been initialized
if hostPath, exists := m.container.Volumes[m.MountToPath]; exists {
// If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create
// We need to make sure bind-mounts/volumes-from passed on start can override existing ones.
if (!m.volume.IsBindMount && !m.isBind) && m.from == nil {
return nil
}
if m.volume.Path == hostPath {
return nil
}
// Make sure we remove these old volumes we don't actually want now.
// Ignore any errors here since this is just cleanup, maybe someone volumes-from'd this volume
if v := m.container.daemon.volumes.Get(hostPath); v != nil {
v.RemoveContainer(m.container.ID)
m.container.daemon.volumes.Delete(v.Path)
}
}
// This is the full path to container fs + mntToPath
containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(m.container.basefs, m.MountToPath), m.container.basefs)
if err != nil {
return err
}
m.container.VolumesRW[m.MountToPath] = m.Writable
m.container.Volumes[m.MountToPath] = m.volume.Path
m.volume.AddContainer(m.container.ID)
if m.Writable && m.copyData {
// Copy whatever is in the container at the mntToPath to the volume
copyExistingContents(containerMntPath, m.volume.Path)
}
return nil
}
func (container *Container) VolumePaths() map[string]struct{} {
var paths = make(map[string]struct{})
for _, path := range container.Volumes {
@ -139,97 +185,30 @@ func (container *Container) derefVolumes() {
}
}
func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) {
var mounts = make(map[string]*Mount)
// Get all the bind mounts
for _, spec := range container.hostConfig.Binds {
path, mountToPath, writable, err := parseBindMountSpec(spec)
if err != nil {
return nil, err
}
// Check if a bind mount has already been specified for the same container path
if m, exists := mounts[mountToPath]; exists {
return nil, fmt.Errorf("Duplicate volume %q: %q already in use, mounted from %q", path, mountToPath, m.volume.Path)
}
// Check if a volume already exists for this and use it
vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
if err != nil {
return nil, err
}
mounts[mountToPath] = &Mount{
container: container,
volume: vol,
MountToPath: mountToPath,
Writable: writable,
isBind: true, // in case the volume itself is a normal volume, but is being mounted in as a bindmount here
}
}
// Get the rest of the volumes
for path := range container.Config.Volumes {
// Check if this is already added as a bind-mount
path = filepath.Clean(path)
if _, exists := mounts[path]; exists {
continue
}
// Check if this has already been created
if _, exists := container.Volumes[path]; exists {
continue
}
realPath, err := container.getResourcePath(path)
if err != nil {
return nil, fmt.Errorf("failed to evaluate the absolute path of symlink")
}
if stat, err := os.Stat(realPath); err == nil {
if !stat.IsDir() {
return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath)
}
}
vol, err := container.daemon.volumes.FindOrCreateVolume("", true)
if err != nil {
return nil, err
}
mounts[path] = &Mount{
container: container,
MountToPath: path,
volume: vol,
Writable: true,
copyData: true,
}
}
return mounts, nil
}
func parseBindMountSpec(spec string) (string, string, bool, error) {
var (
path, mountToPath string
writable bool
arr = strings.Split(spec, ":")
)
func parseBindMountSpec(spec string) (*volumeMount, error) {
arr := strings.Split(spec, ":")
mnt := &volumeMount{}
switch len(arr) {
case 2:
path = arr[0]
mountToPath = arr[1]
writable = true
mnt.hostPath = arr[0]
mnt.containerPath = arr[1]
mnt.writable = true
case 3:
path = arr[0]
mountToPath = arr[1]
writable = validMountMode(arr[2]) && arr[2] == "rw"
mnt.hostPath = arr[0]
mnt.containerPath = arr[1]
mnt.writable = validMountMode(arr[2]) && arr[2] == "rw"
default:
return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
return nil, fmt.Errorf("Invalid volume specification: %s", spec)
}
if !filepath.IsAbs(path) {
return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
if !filepath.IsAbs(mnt.hostPath) {
return nil, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", mnt.hostPath)
}
path = filepath.Clean(path)
mountToPath = filepath.Clean(mountToPath)
return path, mountToPath, writable, nil
mnt.hostPath = filepath.Clean(mnt.hostPath)
mnt.containerPath = filepath.Clean(mnt.containerPath)
return mnt, nil
}
func parseVolumesFromSpec(spec string) (string, string, error) {
@ -251,54 +230,6 @@ func parseVolumesFromSpec(spec string) (string, string, error) {
return id, mode, nil
}
func (container *Container) applyVolumesFrom() error {
volumesFrom := container.hostConfig.VolumesFrom
if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil {
container.AppliedVolumesFrom = make(map[string]struct{})
}
mountGroups := make(map[string][]*Mount)
for _, spec := range volumesFrom {
id, mode, err := parseVolumesFromSpec(spec)
if err != nil {
return err
}
if _, exists := container.AppliedVolumesFrom[id]; exists {
// Don't try to apply these since they've already been applied
continue
}
c, err := container.daemon.Get(id)
if err != nil {
return fmt.Errorf("Could not apply volumes of non-existent container %q.", id)
}
var (
fromMounts = c.VolumeMounts()
mounts []*Mount
)
for _, mnt := range fromMounts {
mnt.Writable = mnt.Writable && (mode == "rw")
mounts = append(mounts, mnt)
}
mountGroups[id] = mounts
}
for id, mounts := range mountGroups {
for _, mnt := range mounts {
mnt.from = mnt.container
mnt.container = container
if err := mnt.initialize(); err != nil {
return err
}
}
container.AppliedVolumesFrom[id] = struct{}{}
}
return nil
}
func validMountMode(mode string) bool {
validModes := map[string]bool{
"rw": true,
@ -344,13 +275,17 @@ func (container *Container) setupMounts() error {
return nil
}
func (container *Container) VolumeMounts() map[string]*Mount {
mounts := make(map[string]*Mount)
func (container *Container) volumeMounts() map[string]*volumeMount {
mounts := make(map[string]*volumeMount)
for mountToPath, path := range container.Volumes {
if v := container.daemon.volumes.Get(path); v != nil {
mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
for containerPath, path := range container.Volumes {
v := container.daemon.volumes.Get(path)
if v == nil {
// This should never happen
logrus.Debugf("reference by container %s to non-existent volume path %s", container.ID, path)
continue
}
mounts[containerPath] = &volumeMount{hostPath: path, containerPath: containerPath, writable: container.VolumesRW[containerPath]}
}
return mounts

View file

@ -4504,7 +4504,7 @@ func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) {
_, err := buildImage(name, `
FROM busybox
ENV SOME_VAR a.b.c
RUN [ "$SOME_VAR" = 'a.b.c' ]