|
@@ -1,213 +1,116 @@
|
|
|
package daemon
|
|
|
|
|
|
import (
|
|
|
+ "encoding/json"
|
|
|
"fmt"
|
|
|
"io/ioutil"
|
|
|
"os"
|
|
|
"path/filepath"
|
|
|
- "sort"
|
|
|
"strings"
|
|
|
|
|
|
- "github.com/Sirupsen/logrus"
|
|
|
"github.com/docker/docker/daemon/execdriver"
|
|
|
"github.com/docker/docker/pkg/chrootarchive"
|
|
|
- "github.com/docker/docker/pkg/mount"
|
|
|
- "github.com/docker/docker/pkg/symlink"
|
|
|
+ "github.com/docker/docker/runconfig"
|
|
|
+ "github.com/docker/docker/volume"
|
|
|
+ volumedrivers "github.com/docker/docker/volume/drivers"
|
|
|
)
|
|
|
|
|
|
-type volumeMount struct {
|
|
|
- containerPath string
|
|
|
- hostPath string
|
|
|
- writable bool
|
|
|
- copyData bool
|
|
|
- from string
|
|
|
-}
|
|
|
-
|
|
|
-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
|
|
|
- }
|
|
|
+var localMountErr = fmt.Errorf("Invalid driver: %s driver doesn't support named volumes", volume.DefaultDriverName)
|
|
|
|
|
|
- c, err := container.daemon.Get(cID)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("container %s not found, impossible to mount its volumes", cID)
|
|
|
- }
|
|
|
+type mountPoint struct {
|
|
|
+ Name string
|
|
|
+ Destination string
|
|
|
+ Driver string
|
|
|
+ RW bool
|
|
|
+ Volume volume.Volume `json:"-"`
|
|
|
+ Source string
|
|
|
+}
|
|
|
|
|
|
- for _, mnt := range c.volumeMounts() {
|
|
|
- mnt.writable = mnt.writable && (mode == "rw")
|
|
|
- mnt.from = cID
|
|
|
- mounts[mnt.containerPath] = mnt
|
|
|
- }
|
|
|
+func (m *mountPoint) Setup() (string, error) {
|
|
|
+ if m.Volume != nil {
|
|
|
+ return m.Volume.Mount()
|
|
|
}
|
|
|
|
|
|
- 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)
|
|
|
+ if len(m.Source) > 0 {
|
|
|
+ if _, err := os.Stat(m.Source); err != nil {
|
|
|
+ if !os.IsNotExist(err) {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ if err := os.MkdirAll(m.Source, 0755); err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
}
|
|
|
+ return m.Source, nil
|
|
|
}
|
|
|
|
|
|
- return nil
|
|
|
+ return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
|
|
}
|
|
|
|
|
|
-// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
|
|
|
-func (container *Container) sortedVolumeMounts() []string {
|
|
|
- var mountPaths []string
|
|
|
- for path := range container.Volumes {
|
|
|
- mountPaths = append(mountPaths, path)
|
|
|
+func (m *mountPoint) Path() string {
|
|
|
+ if m.Volume != nil {
|
|
|
+ return m.Volume.Path()
|
|
|
}
|
|
|
|
|
|
- sort.Strings(mountPaths)
|
|
|
- return mountPaths
|
|
|
+ return m.Source
|
|
|
}
|
|
|
|
|
|
-func (container *Container) VolumePaths() map[string]struct{} {
|
|
|
- var paths = make(map[string]struct{})
|
|
|
- for _, path := range container.Volumes {
|
|
|
- paths[path] = struct{}{}
|
|
|
+func parseBindMount(spec string, config *runconfig.Config) (*mountPoint, error) {
|
|
|
+ bind := &mountPoint{
|
|
|
+ RW: true,
|
|
|
}
|
|
|
- return paths
|
|
|
-}
|
|
|
-
|
|
|
-func (container *Container) registerVolumes() {
|
|
|
- for path := range container.VolumePaths() {
|
|
|
- if v := container.daemon.volumes.Get(path); v != nil {
|
|
|
- v.AddContainer(container.ID)
|
|
|
- continue
|
|
|
- }
|
|
|
+ arr := strings.Split(spec, ":")
|
|
|
|
|
|
- // if container was created with an old daemon, this volume may not be registered so we need to make sure it gets registered
|
|
|
- writable := true
|
|
|
- if rw, exists := container.VolumesRW[path]; exists {
|
|
|
- writable = rw
|
|
|
- }
|
|
|
- v, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
|
|
|
- if err != nil {
|
|
|
- logrus.Debugf("error registering volume %s: %v", path, err)
|
|
|
- continue
|
|
|
+ switch len(arr) {
|
|
|
+ case 2:
|
|
|
+ bind.Destination = arr[1]
|
|
|
+ case 3:
|
|
|
+ bind.Destination = arr[1]
|
|
|
+ if !validMountMode(arr[2]) {
|
|
|
+ return nil, fmt.Errorf("invalid mode for volumes-from: %s", arr[2])
|
|
|
}
|
|
|
- v.AddContainer(container.ID)
|
|
|
+ bind.RW = arr[2] == "rw"
|
|
|
+ default:
|
|
|
+ return nil, fmt.Errorf("Invalid volume specification: %s", spec)
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-func (container *Container) derefVolumes() {
|
|
|
- for path := range container.VolumePaths() {
|
|
|
- vol := container.daemon.volumes.Get(path)
|
|
|
- if vol == nil {
|
|
|
- logrus.Debugf("Volume %s was not found and could not be dereferenced", path)
|
|
|
- continue
|
|
|
+ if !filepath.IsAbs(arr[0]) {
|
|
|
+ bind.Driver, bind.Name = parseNamedVolumeInfo(arr[0], config)
|
|
|
+ if bind.Driver == volume.DefaultDriverName {
|
|
|
+ return nil, localMountErr
|
|
|
}
|
|
|
- vol.RemoveContainer(container.ID)
|
|
|
+ } else {
|
|
|
+ bind.Source = filepath.Clean(arr[0])
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-func parseBindMountSpec(spec string) (*volumeMount, error) {
|
|
|
- arr := strings.Split(spec, ":")
|
|
|
+ bind.Destination = filepath.Clean(bind.Destination)
|
|
|
+ return bind, nil
|
|
|
+}
|
|
|
|
|
|
- mnt := &volumeMount{}
|
|
|
- switch len(arr) {
|
|
|
+func parseNamedVolumeInfo(info string, config *runconfig.Config) (driver string, name string) {
|
|
|
+ p := strings.SplitN(info, "/", 2)
|
|
|
+ switch len(p) {
|
|
|
case 2:
|
|
|
- mnt.hostPath = arr[0]
|
|
|
- mnt.containerPath = arr[1]
|
|
|
- mnt.writable = true
|
|
|
- case 3:
|
|
|
- mnt.hostPath = arr[0]
|
|
|
- mnt.containerPath = arr[1]
|
|
|
- mnt.writable = validMountMode(arr[2]) && arr[2] == "rw"
|
|
|
+ driver = p[0]
|
|
|
+ name = p[1]
|
|
|
default:
|
|
|
- return nil, fmt.Errorf("Invalid volume specification: %s", spec)
|
|
|
- }
|
|
|
-
|
|
|
- if !filepath.IsAbs(mnt.hostPath) {
|
|
|
- return nil, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", mnt.hostPath)
|
|
|
+ if driver = config.VolumeDriver; len(driver) == 0 {
|
|
|
+ driver = volume.DefaultDriverName
|
|
|
+ }
|
|
|
+ name = p[0]
|
|
|
}
|
|
|
|
|
|
- mnt.hostPath = filepath.Clean(mnt.hostPath)
|
|
|
- mnt.containerPath = filepath.Clean(mnt.containerPath)
|
|
|
- return mnt, nil
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
-func parseVolumesFromSpec(spec string) (string, string, error) {
|
|
|
- specParts := strings.SplitN(spec, ":", 2)
|
|
|
- if len(specParts) == 0 {
|
|
|
+func parseVolumesFrom(spec string) (string, string, error) {
|
|
|
+ if len(spec) == 0 {
|
|
|
return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec)
|
|
|
}
|
|
|
|
|
|
- var (
|
|
|
- id = specParts[0]
|
|
|
- mode = "rw"
|
|
|
- )
|
|
|
+ specParts := strings.SplitN(spec, ":", 2)
|
|
|
+ id := specParts[0]
|
|
|
+ mode := "rw"
|
|
|
+
|
|
|
if len(specParts) == 2 {
|
|
|
mode = specParts[1]
|
|
|
if !validMountMode(mode) {
|
|
@@ -222,7 +125,6 @@ func validMountMode(mode string) bool {
|
|
|
"rw": true,
|
|
|
"ro": true,
|
|
|
}
|
|
|
-
|
|
|
return validModes[mode]
|
|
|
}
|
|
|
|
|
@@ -240,34 +142,16 @@ func (container *Container) specialMounts() []execdriver.Mount {
|
|
|
return mounts
|
|
|
}
|
|
|
|
|
|
-func (container *Container) volumeMounts() map[string]*volumeMount {
|
|
|
- mounts := make(map[string]*volumeMount)
|
|
|
-
|
|
|
- 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
|
|
|
-}
|
|
|
-
|
|
|
func copyExistingContents(source, destination string) error {
|
|
|
volList, err := ioutil.ReadDir(source)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
-
|
|
|
if len(volList) > 0 {
|
|
|
srcList, err := ioutil.ReadDir(destination)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
-
|
|
|
if len(srcList) == 0 {
|
|
|
// If the source volume is empty copy files from the root into the volume
|
|
|
if err := chrootarchive.CopyWithTar(source, destination); err != nil {
|
|
@@ -275,60 +159,145 @@ func copyExistingContents(source, destination string) error {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
return copyOwnership(source, destination)
|
|
|
}
|
|
|
|
|
|
-func (container *Container) mountVolumes() error {
|
|
|
- for dest, source := range container.Volumes {
|
|
|
- v := container.daemon.volumes.Get(source)
|
|
|
- if v == nil {
|
|
|
- return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest)
|
|
|
+// registerMountPoints initializes the container mount points with the configured volumes and bind mounts.
|
|
|
+// It follows the next sequence to decide what to mount in each final destination:
|
|
|
+//
|
|
|
+// 1. Select the previously configured mount points for the containers, if any.
|
|
|
+// 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination.
|
|
|
+// 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations.
|
|
|
+func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
|
|
|
+ binds := map[string]bool{}
|
|
|
+ mountPoints := map[string]*mountPoint{}
|
|
|
+
|
|
|
+ // 1. Read already configured mount points.
|
|
|
+ for name, point := range container.MountPoints {
|
|
|
+ mountPoints[name] = point
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. Read volumes from other containers.
|
|
|
+ for _, v := range hostConfig.VolumesFrom {
|
|
|
+ containerID, mode, err := parseVolumesFrom(v)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
- destPath, err := container.GetResourcePath(dest)
|
|
|
+ c, err := daemon.Get(containerID)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- if err := mount.Mount(source, destPath, "bind", "rbind,rw"); err != nil {
|
|
|
- return fmt.Errorf("error while mounting volume %s: %v", source, err)
|
|
|
+ for _, m := range c.MountPoints {
|
|
|
+ cp := m
|
|
|
+ cp.RW = m.RW && mode != "ro"
|
|
|
+
|
|
|
+ if len(m.Source) == 0 {
|
|
|
+ v, err := createVolume(m.Name, m.Driver)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ cp.Volume = v
|
|
|
+ }
|
|
|
+
|
|
|
+ mountPoints[cp.Destination] = cp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- for _, mnt := range container.specialMounts() {
|
|
|
- destPath, err := container.GetResourcePath(mnt.Destination)
|
|
|
+ // 3. Read bind mounts
|
|
|
+ for _, b := range hostConfig.Binds {
|
|
|
+ // #10618
|
|
|
+ bind, err := parseBindMount(b, container.Config)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- if err := mount.Mount(mnt.Source, destPath, "bind", "bind,rw"); err != nil {
|
|
|
- return fmt.Errorf("error while mounting volume %s: %v", mnt.Source, err)
|
|
|
+
|
|
|
+ if binds[bind.Destination] {
|
|
|
+ return fmt.Errorf("Duplicate bind mount %s", bind.Destination)
|
|
|
}
|
|
|
+
|
|
|
+ if len(bind.Name) > 0 && len(bind.Driver) > 0 {
|
|
|
+ v, err := createVolume(bind.Name, bind.Driver)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ bind.Volume = v
|
|
|
+ }
|
|
|
+
|
|
|
+ binds[bind.Destination] = true
|
|
|
+ mountPoints[bind.Destination] = bind
|
|
|
}
|
|
|
+
|
|
|
+ container.MountPoints = mountPoints
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (container *Container) unmountVolumes() {
|
|
|
- for dest := range container.Volumes {
|
|
|
- destPath, err := container.GetResourcePath(dest)
|
|
|
- if err != nil {
|
|
|
- logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
|
|
|
- continue
|
|
|
- }
|
|
|
- if err := mount.ForceUnmount(destPath); err != nil {
|
|
|
- logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
|
|
|
- continue
|
|
|
+// verifyOldVolumesInfo ports volumes configured for the containers pre docker 1.7.
|
|
|
+// It reads the container configuration and creates valid mount points for the old volumes.
|
|
|
+func (daemon *Daemon) verifyOldVolumesInfo(container *Container) error {
|
|
|
+ jsonPath, err := container.jsonPath()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ f, err := os.Open(jsonPath)
|
|
|
+ if err != nil {
|
|
|
+ if os.IsNotExist(err) {
|
|
|
+ return nil
|
|
|
}
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
- for _, mnt := range container.specialMounts() {
|
|
|
- destPath, err := container.GetResourcePath(mnt.Destination)
|
|
|
- if err != nil {
|
|
|
- logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
|
|
|
- continue
|
|
|
- }
|
|
|
- if err := mount.ForceUnmount(destPath); err != nil {
|
|
|
- logrus.Errorf("error while unmounting volumes %s: %v", destPath, err)
|
|
|
+ type oldContVolCfg struct {
|
|
|
+ Volumes map[string]string
|
|
|
+ VolumesRW map[string]bool
|
|
|
+ }
|
|
|
+
|
|
|
+ vols := oldContVolCfg{
|
|
|
+ Volumes: make(map[string]string),
|
|
|
+ VolumesRW: make(map[string]bool),
|
|
|
+ }
|
|
|
+ if err := json.NewDecoder(f).Decode(&vols); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ for destination, hostPath := range vols.Volumes {
|
|
|
+ vfsPath := filepath.Join(daemon.root, "vfs", "dir")
|
|
|
+
|
|
|
+ if strings.HasPrefix(hostPath, vfsPath) {
|
|
|
+ id := filepath.Base(hostPath)
|
|
|
+
|
|
|
+ container.AddLocalMountPoint(id, destination, vols.VolumesRW[destination])
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ return container.ToDisk()
|
|
|
+}
|
|
|
+
|
|
|
+func createVolume(name, driverName string) (volume.Volume, error) {
|
|
|
+ vd, err := getVolumeDriver(driverName)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return vd.Create(name)
|
|
|
+}
|
|
|
+
|
|
|
+func removeVolume(v volume.Volume) error {
|
|
|
+ vd, err := getVolumeDriver(v.DriverName())
|
|
|
+ if err != nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return vd.Remove(v)
|
|
|
+}
|
|
|
+
|
|
|
+func getVolumeDriver(name string) (volume.Driver, error) {
|
|
|
+ if name == "" {
|
|
|
+ name = volume.DefaultDriverName
|
|
|
+ }
|
|
|
+ vd := volumedrivers.Lookup(name)
|
|
|
+ if vd == nil {
|
|
|
+ return nil, fmt.Errorf("Volumes Driver %s isn't registered", name)
|
|
|
+ }
|
|
|
+ return vd, nil
|
|
|
}
|