This persists the "propagated mount" for plugins outside the main
rootfs. This enables `docker plugin upgrade` to not remove potentially
important data during upgrade rather than forcing plugin authors to hard
code a host path to persist data to.
Also migrates old plugins that have a propagated mount which is in the
rootfs on daemon startup.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -118,6 +118,8 @@ Config provides the base accessible fields for working with V0 plugin format |
| 118 | 118 |
- **`propagatedMount`** *string* |
| 119 | 119 |
|
| 120 | 120 |
path to be mounted as rshared, so that mounts under that path are visible to docker. This is useful for volume plugins. |
| 121 |
+ This path will be bind-mounted outisde of the plugin rootfs so it's contents |
|
| 122 |
+ are preserved on upgrade. |
|
| 121 | 123 |
|
| 122 | 124 |
- **`env`** *PluginEnv array* |
| 123 | 125 |
|
| ... | ... |
@@ -434,6 +434,9 @@ func (s *DockerSuite) TestPluginUpgrade(c *check.C) {
|
| 434 | 434 |
pluginV2 := "cpuguy83/docker-volume-driver-plugin-local:v2" |
| 435 | 435 |
|
| 436 | 436 |
dockerCmd(c, "plugin", "install", "--grant-all-permissions", plugin) |
| 437 |
+ dockerCmd(c, "volume", "create", "--driver", plugin, "bananas") |
|
| 438 |
+ dockerCmd(c, "run", "--rm", "-v", "bananas:/apple", "busybox", "sh", "-c", "touch /apple/core") |
|
| 439 |
+ |
|
| 437 | 440 |
out, _, err := dockerCmdWithError("plugin", "upgrade", "--grant-all-permissions", plugin, pluginV2)
|
| 438 | 441 |
c.Assert(err, checker.NotNil, check.Commentf(out)) |
| 439 | 442 |
c.Assert(out, checker.Contains, "disabled before upgrading") |
| ... | ... |
@@ -445,7 +448,7 @@ func (s *DockerSuite) TestPluginUpgrade(c *check.C) {
|
| 445 | 445 |
_, err = os.Stat(filepath.Join(testEnv.DockerBasePath(), "plugins", id, "rootfs", "v2")) |
| 446 | 446 |
c.Assert(os.IsNotExist(err), checker.True, check.Commentf(out)) |
| 447 | 447 |
|
| 448 |
- dockerCmd(c, "plugin", "disable", plugin) |
|
| 448 |
+ dockerCmd(c, "plugin", "disable", "-f", plugin) |
|
| 449 | 449 |
dockerCmd(c, "plugin", "upgrade", "--grant-all-permissions", "--skip-remote-check", plugin, pluginV2) |
| 450 | 450 |
|
| 451 | 451 |
// make sure "v2" file exists |
| ... | ... |
@@ -453,4 +456,6 @@ func (s *DockerSuite) TestPluginUpgrade(c *check.C) {
|
| 453 | 453 |
c.Assert(err, checker.IsNil) |
| 454 | 454 |
|
| 455 | 455 |
dockerCmd(c, "plugin", "enable", plugin) |
| 456 |
+ dockerCmd(c, "volume", "inspect", "bananas") |
|
| 457 |
+ dockerCmd(c, "run", "--rm", "-v", "bananas:/apple", "busybox", "sh", "-c", "ls -lh /apple/core") |
|
| 456 | 458 |
} |
| ... | ... |
@@ -13,6 +13,7 @@ import ( |
| 13 | 13 |
"os" |
| 14 | 14 |
"path" |
| 15 | 15 |
"path/filepath" |
| 16 |
+ "sort" |
|
| 16 | 17 |
"strings" |
| 17 | 18 |
|
| 18 | 19 |
"github.com/Sirupsen/logrus" |
| ... | ... |
@@ -25,6 +26,7 @@ import ( |
| 25 | 25 |
"github.com/docker/docker/image" |
| 26 | 26 |
"github.com/docker/docker/layer" |
| 27 | 27 |
"github.com/docker/docker/pkg/chrootarchive" |
| 28 |
+ "github.com/docker/docker/pkg/mount" |
|
| 28 | 29 |
"github.com/docker/docker/pkg/pools" |
| 29 | 30 |
"github.com/docker/docker/pkg/progress" |
| 30 | 31 |
"github.com/docker/docker/plugin/v2" |
| ... | ... |
@@ -597,6 +599,9 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
|
| 597 | 597 |
id := p.GetID() |
| 598 | 598 |
pm.config.Store.Remove(p) |
| 599 | 599 |
pluginDir := filepath.Join(pm.config.Root, id) |
| 600 |
+ if err := recursiveUnmount(pm.config.Root); err != nil {
|
|
| 601 |
+ logrus.WithField("dir", pm.config.Root).WithField("id", id).Warn(err)
|
|
| 602 |
+ } |
|
| 600 | 603 |
if err := os.RemoveAll(pluginDir); err != nil {
|
| 601 | 604 |
logrus.Warnf("unable to remove %q from plugin remove: %v", pluginDir, err)
|
| 602 | 605 |
} |
| ... | ... |
@@ -604,6 +609,43 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
|
| 604 | 604 |
return nil |
| 605 | 605 |
} |
| 606 | 606 |
|
| 607 |
+func getMounts(root string) ([]string, error) {
|
|
| 608 |
+ infos, err := mount.GetMounts() |
|
| 609 |
+ if err != nil {
|
|
| 610 |
+ return nil, errors.Wrap(err, "failed to read mount table while performing recursive unmount") |
|
| 611 |
+ } |
|
| 612 |
+ |
|
| 613 |
+ var mounts []string |
|
| 614 |
+ for _, m := range infos {
|
|
| 615 |
+ if strings.HasPrefix(m.Mountpoint, root) {
|
|
| 616 |
+ mounts = append(mounts, m.Mountpoint) |
|
| 617 |
+ } |
|
| 618 |
+ } |
|
| 619 |
+ |
|
| 620 |
+ return mounts, nil |
|
| 621 |
+} |
|
| 622 |
+ |
|
| 623 |
+func recursiveUnmount(root string) error {
|
|
| 624 |
+ mounts, err := getMounts(root) |
|
| 625 |
+ if err != nil {
|
|
| 626 |
+ return err |
|
| 627 |
+ } |
|
| 628 |
+ |
|
| 629 |
+ // sort in reverse-lexicographic order so the root mount will always be last |
|
| 630 |
+ sort.Sort(sort.Reverse(sort.StringSlice(mounts))) |
|
| 631 |
+ |
|
| 632 |
+ for i, m := range mounts {
|
|
| 633 |
+ if err := mount.Unmount(m); err != nil {
|
|
| 634 |
+ if i == len(mounts)-1 {
|
|
| 635 |
+ return errors.Wrapf(err, "error performing recursive unmount on %s", root) |
|
| 636 |
+ } |
|
| 637 |
+ logrus.WithError(err).WithField("mountpoint", m).Warn("could not unmount")
|
|
| 638 |
+ } |
|
| 639 |
+ } |
|
| 640 |
+ |
|
| 641 |
+ return nil |
|
| 642 |
+} |
|
| 643 |
+ |
|
| 607 | 644 |
// Set sets plugin args |
| 608 | 645 |
func (pm *Manager) Set(name string, args []string) error {
|
| 609 | 646 |
p, err := pm.config.Store.GetV2Plugin(name) |
| ... | ... |
@@ -145,6 +145,10 @@ func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
|
| 145 | 145 |
if err := mount.Unmount(p.PropagatedMount); err != nil {
|
| 146 | 146 |
logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
|
| 147 | 147 |
} |
| 148 |
+ propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") |
|
| 149 |
+ if err := mount.Unmount(propRoot); err != nil {
|
|
| 150 |
+ logrus.Warn("Could not unmount %s: %v", propRoot, err)
|
|
| 151 |
+ } |
|
| 148 | 152 |
} |
| 149 | 153 |
|
| 150 | 154 |
if restart {
|
| ... | ... |
@@ -193,6 +197,27 @@ func (pm *Manager) reload() error { // todo: restore
|
| 193 | 193 |
for _, typ := range p.PluginObj.Config.Interface.Types {
|
| 194 | 194 |
if (typ.Capability == "volumedriver" || typ.Capability == "graphdriver") && typ.Prefix == "docker" && strings.HasPrefix(typ.Version, "1.") {
|
| 195 | 195 |
if p.PluginObj.Config.PropagatedMount != "" {
|
| 196 |
+ propRoot := filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") |
|
| 197 |
+ |
|
| 198 |
+ // check if we need to migrate an older propagated mount from before |
|
| 199 |
+ // these mounts were stored outside the plugin rootfs |
|
| 200 |
+ if _, err := os.Stat(propRoot); os.IsNotExist(err) {
|
|
| 201 |
+ if _, err := os.Stat(p.PropagatedMount); err == nil {
|
|
| 202 |
+ // make sure nothing is mounted here |
|
| 203 |
+ // don't care about errors |
|
| 204 |
+ mount.Unmount(p.PropagatedMount) |
|
| 205 |
+ if err := os.Rename(p.PropagatedMount, propRoot); err != nil {
|
|
| 206 |
+ logrus.WithError(err).WithField("dir", propRoot).Error("error migrating propagated mount storage")
|
|
| 207 |
+ } |
|
| 208 |
+ if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil {
|
|
| 209 |
+ logrus.WithError(err).WithField("dir", p.PropagatedMount).Error("error migrating propagated mount storage")
|
|
| 210 |
+ } |
|
| 211 |
+ } |
|
| 212 |
+ } |
|
| 213 |
+ |
|
| 214 |
+ if err := os.MkdirAll(propRoot, 0755); err != nil {
|
|
| 215 |
+ logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err)
|
|
| 216 |
+ } |
|
| 196 | 217 |
// TODO: sanitize PropagatedMount and prevent breakout |
| 197 | 218 |
p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount) |
| 198 | 219 |
if err := os.MkdirAll(p.PropagatedMount, 0755); err != nil {
|
| ... | ... |
@@ -40,9 +40,20 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
|
| 40 | 40 |
pm.cMap[p] = c |
| 41 | 41 |
pm.mu.Unlock() |
| 42 | 42 |
|
| 43 |
+ var propRoot string |
|
| 43 | 44 |
if p.PropagatedMount != "" {
|
| 44 |
- if err := mount.MakeRShared(p.PropagatedMount); err != nil {
|
|
| 45 |
- return errors.WithStack(err) |
|
| 45 |
+ propRoot = filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount") |
|
| 46 |
+ |
|
| 47 |
+ if err := os.MkdirAll(propRoot, 0755); err != nil {
|
|
| 48 |
+ logrus.Errorf("failed to create PropagatedMount directory at %s: %v", propRoot, err)
|
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ if err := mount.MakeRShared(propRoot); err != nil {
|
|
| 52 |
+ return errors.Wrap(err, "error setting up propagated mount dir") |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ if err := mount.Mount(propRoot, p.PropagatedMount, "none", "rbind"); err != nil {
|
|
| 56 |
+ return errors.Wrap(err, "error creating mount for propagated mount") |
|
| 46 | 57 |
} |
| 47 | 58 |
} |
| 48 | 59 |
|
| ... | ... |
@@ -55,6 +66,9 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
|
| 55 | 55 |
if err := mount.Unmount(p.PropagatedMount); err != nil {
|
| 56 | 56 |
logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
|
| 57 | 57 |
} |
| 58 |
+ if err := mount.Unmount(propRoot); err != nil {
|
|
| 59 |
+ logrus.Warnf("Could not unmount %s: %v", propRoot, err)
|
|
| 60 |
+ } |
|
| 58 | 61 |
} |
| 59 | 62 |
return errors.WithStack(err) |
| 60 | 63 |
} |