Merge pull request #17034 from rhvgoyal/volume-propagation

Capability to specify per volume mount propagation mode
Upstream-commit: ce0b1841c82b6972d96654e083f813944e72443f
Component: engine
This commit is contained in:
Brian Goff
2015-12-15 12:14:41 -05:00
20 changed files with 662 additions and 92 deletions
@@ -26,9 +26,8 @@ type Mount struct {
Source string `json:"source"`
Destination string `json:"destination"`
Writable bool `json:"writable"`
Private bool `json:"private"`
Slave bool `json:"slave"`
Data string `json:"data"`
Propagation string `json:"mountpropagation"`
}
// Resources contains all resource configs for a driver.
@@ -125,6 +124,11 @@ type Command struct {
UTS *UTS `json:"uts"`
}
// SetRootPropagation sets the root mount propagation mode.
func SetRootPropagation(config *configs.Config, propagation int) {
config.RootPropagation = propagation
}
// InitContainer is the initialization of a container config.
// It returns the initial configs for a container. It's mostly
// defined by the default template.
@@ -137,7 +141,9 @@ func InitContainer(c *Command) *configs.Config {
container.Devices = c.AutoCreatedDevices
container.Rootfs = c.Rootfs
container.Readonlyfs = c.ReadonlyRootfs
container.RootPropagation = mount.RPRIVATE
// This can be overridden later by driver during mount setup based
// on volume options
SetRootPropagation(container, mount.RPRIVATE)
// check to see if we are running in ramdisk to disable pivot root
container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
@@ -12,6 +12,7 @@ import (
derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/volume"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/devices"
@@ -278,6 +279,116 @@ func (d *Driver) setupRlimits(container *configs.Config, c *execdriver.Command)
}
}
// If rootfs mount propagation is RPRIVATE, that means all the volumes are
// going to be private anyway. There is no need to apply per volume
// propagation on top. This is just an optimzation so that cost of per volume
// propagation is paid only if user decides to make some volume non-private
// which will force rootfs mount propagation to be non RPRIVATE.
func checkResetVolumePropagation(container *configs.Config) {
if container.RootPropagation != mount.RPRIVATE {
return
}
for _, m := range container.Mounts {
m.PropagationFlags = nil
}
}
func getMountInfo(mountinfo []*mount.Info, dir string) *mount.Info {
for _, m := range mountinfo {
if m.Mountpoint == dir {
return m
}
}
return nil
}
// Get the source mount point of directory passed in as argument. Also return
// optional fields.
func getSourceMount(source string) (string, string, error) {
// Ensure any symlinks are resolved.
sourcePath, err := filepath.EvalSymlinks(source)
if err != nil {
return "", "", err
}
mountinfos, err := mount.GetMounts()
if err != nil {
return "", "", err
}
mountinfo := getMountInfo(mountinfos, sourcePath)
if mountinfo != nil {
return sourcePath, mountinfo.Optional, nil
}
path := sourcePath
for {
path = filepath.Dir(path)
mountinfo = getMountInfo(mountinfos, path)
if mountinfo != nil {
return path, mountinfo.Optional, nil
}
if path == "/" {
break
}
}
// If we are here, we did not find parent mount. Something is wrong.
return "", "", fmt.Errorf("Could not find source mount of %s", source)
}
// Ensure mount point on which path is mouted, is shared.
func ensureShared(path string) error {
sharedMount := false
sourceMount, optionalOpts, err := getSourceMount(path)
if err != nil {
return err
}
// Make sure source mount point is shared.
optsSplit := strings.Split(optionalOpts, " ")
for _, opt := range optsSplit {
if strings.HasPrefix(opt, "shared:") {
sharedMount = true
break
}
}
if !sharedMount {
return fmt.Errorf("Path %s is mounted on %s but it is not a shared mount.", path, sourceMount)
}
return nil
}
// Ensure mount point on which path is mounted, is either shared or slave.
func ensureSharedOrSlave(path string) error {
sharedMount := false
slaveMount := false
sourceMount, optionalOpts, err := getSourceMount(path)
if err != nil {
return err
}
// Make sure source mount point is shared.
optsSplit := strings.Split(optionalOpts, " ")
for _, opt := range optsSplit {
if strings.HasPrefix(opt, "shared:") {
sharedMount = true
break
} else if strings.HasPrefix(opt, "master:") {
slaveMount = true
break
}
}
if !sharedMount && !slaveMount {
return fmt.Errorf("Path %s is mounted on %s but it is not a shared or slave mount.", path, sourceMount)
}
return nil
}
func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) error {
userMounts := make(map[string]struct{})
for _, m := range c.Mounts {
@@ -298,6 +409,15 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e
}
container.Mounts = defaultMounts
mountPropagationMap := map[string]int{
"private": mount.PRIVATE,
"rprivate": mount.RPRIVATE,
"shared": mount.SHARED,
"rshared": mount.RSHARED,
"slave": mount.SLAVE,
"rslave": mount.RSLAVE,
}
for _, m := range c.Mounts {
for _, cm := range container.Mounts {
if cm.Destination == m.Destination {
@@ -319,31 +439,65 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e
}
}
container.Mounts = append(container.Mounts, &configs.Mount{
Source: m.Source,
Destination: m.Destination,
Data: data,
Device: "tmpfs",
Flags: flags,
PremountCmds: genTmpfsPremountCmd(c.TmpDir, fulldest, m.Destination),
PostmountCmds: genTmpfsPostmountCmd(c.TmpDir, fulldest, m.Destination),
Source: m.Source,
Destination: m.Destination,
Data: data,
Device: "tmpfs",
Flags: flags,
PremountCmds: genTmpfsPremountCmd(c.TmpDir, fulldest, m.Destination),
PostmountCmds: genTmpfsPostmountCmd(c.TmpDir, fulldest, m.Destination),
PropagationFlags: []int{mountPropagationMap[volume.DefaultPropagationMode]},
})
continue
}
flags := syscall.MS_BIND | syscall.MS_REC
var pFlag int
if !m.Writable {
flags |= syscall.MS_RDONLY
}
if m.Slave {
flags |= syscall.MS_SLAVE
// Determine property of RootPropagation based on volume
// properties. If a volume is shared, then keep root propagtion
// shared. This should work for slave and private volumes too.
//
// For slave volumes, it can be either [r]shared/[r]slave.
//
// For private volumes any root propagation value should work.
pFlag = mountPropagationMap[m.Propagation]
if pFlag == mount.SHARED || pFlag == mount.RSHARED {
if err := ensureShared(m.Source); err != nil {
return err
}
rootpg := container.RootPropagation
if rootpg != mount.SHARED && rootpg != mount.RSHARED {
execdriver.SetRootPropagation(container, mount.SHARED)
}
} else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE {
if err := ensureSharedOrSlave(m.Source); err != nil {
return err
}
rootpg := container.RootPropagation
if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
execdriver.SetRootPropagation(container, mount.RSLAVE)
}
}
container.Mounts = append(container.Mounts, &configs.Mount{
mount := &configs.Mount{
Source: m.Source,
Destination: m.Destination,
Device: "bind",
Flags: flags,
})
}
if pFlag != 0 {
mount.PropagationFlags = []int{pFlag}
}
container.Mounts = append(container.Mounts, mount)
}
checkResetVolumePropagation(container)
return nil
}