mirror of https://github.com/docker/docs.git
Merge pull request #10422 from cpuguy83/cleanup_daemon_volumes
Cleanup daemon/volumes
This commit is contained in:
commit
505788deb2
|
@ -14,17 +14,14 @@ import (
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/volumes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Mount struct {
|
type volumeMount struct {
|
||||||
MountToPath string
|
containerPath string
|
||||||
container *Container
|
hostPath string
|
||||||
volume *volumes.Volume
|
writable bool
|
||||||
Writable bool
|
copyData bool
|
||||||
copyData bool
|
from string
|
||||||
from *Container
|
|
||||||
isBind bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *Container) prepareVolumes() error {
|
func (container *Container) prepareVolumes() error {
|
||||||
|
@ -33,9 +30,110 @@ func (container *Container) prepareVolumes() error {
|
||||||
container.VolumesRW = make(map[string]bool)
|
container.VolumesRW = make(map[string]bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(container.hostConfig.VolumesFrom) > 0 && container.AppliedVolumesFrom == nil {
|
||||||
|
container.AppliedVolumesFrom = make(map[string]struct{})
|
||||||
|
}
|
||||||
return container.createVolumes()
|
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
|
// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
|
||||||
func (container *Container) sortedVolumeMounts() []string {
|
func (container *Container) sortedVolumeMounts() []string {
|
||||||
var mountPaths []string
|
var mountPaths []string
|
||||||
|
@ -47,58 +145,6 @@ func (container *Container) sortedVolumeMounts() []string {
|
||||||
return mountPaths
|
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{} {
|
func (container *Container) VolumePaths() map[string]struct{} {
|
||||||
var paths = make(map[string]struct{})
|
var paths = make(map[string]struct{})
|
||||||
for _, path := range container.Volumes {
|
for _, path := range container.Volumes {
|
||||||
|
@ -139,97 +185,30 @@ func (container *Container) derefVolumes() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) {
|
func parseBindMountSpec(spec string) (*volumeMount, error) {
|
||||||
var mounts = make(map[string]*Mount)
|
arr := strings.Split(spec, ":")
|
||||||
// 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, ":")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
mnt := &volumeMount{}
|
||||||
switch len(arr) {
|
switch len(arr) {
|
||||||
case 2:
|
case 2:
|
||||||
path = arr[0]
|
mnt.hostPath = arr[0]
|
||||||
mountToPath = arr[1]
|
mnt.containerPath = arr[1]
|
||||||
writable = true
|
mnt.writable = true
|
||||||
case 3:
|
case 3:
|
||||||
path = arr[0]
|
mnt.hostPath = arr[0]
|
||||||
mountToPath = arr[1]
|
mnt.containerPath = arr[1]
|
||||||
writable = validMountMode(arr[2]) && arr[2] == "rw"
|
mnt.writable = validMountMode(arr[2]) && arr[2] == "rw"
|
||||||
default:
|
default:
|
||||||
return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
|
return nil, fmt.Errorf("Invalid volume specification: %s", spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filepath.IsAbs(path) {
|
if !filepath.IsAbs(mnt.hostPath) {
|
||||||
return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
|
return nil, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", mnt.hostPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
path = filepath.Clean(path)
|
mnt.hostPath = filepath.Clean(mnt.hostPath)
|
||||||
mountToPath = filepath.Clean(mountToPath)
|
mnt.containerPath = filepath.Clean(mnt.containerPath)
|
||||||
return path, mountToPath, writable, nil
|
return mnt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseVolumesFromSpec(spec string) (string, string, error) {
|
func parseVolumesFromSpec(spec string) (string, string, error) {
|
||||||
|
@ -251,54 +230,6 @@ func parseVolumesFromSpec(spec string) (string, string, error) {
|
||||||
return id, mode, nil
|
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 {
|
func validMountMode(mode string) bool {
|
||||||
validModes := map[string]bool{
|
validModes := map[string]bool{
|
||||||
"rw": true,
|
"rw": true,
|
||||||
|
@ -344,13 +275,17 @@ func (container *Container) setupMounts() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *Container) VolumeMounts() map[string]*Mount {
|
func (container *Container) volumeMounts() map[string]*volumeMount {
|
||||||
mounts := make(map[string]*Mount)
|
mounts := make(map[string]*volumeMount)
|
||||||
|
|
||||||
for mountToPath, path := range container.Volumes {
|
for containerPath, path := range container.Volumes {
|
||||||
if v := container.daemon.volumes.Get(path); v != nil {
|
v := container.daemon.volumes.Get(path)
|
||||||
mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
|
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
|
return mounts
|
||||||
|
|
Loading…
Reference in New Issue