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>
(cherry picked from commit e8307b868de9f19bb97f5cafcd727df5c5f501be)
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -114,6 +114,8 @@ Config provides the base accessible fields for working with V0 plugin format |
| 114 | 114 |
- **`propagatedMount`** *string* |
| 115 | 115 |
|
| 116 | 116 |
path to be mounted as rshared, so that mounts under that path are visible to docker. This is useful for volume plugins. |
| 117 |
+ This path will be bind-mounted outisde of the plugin rootfs so it's contents |
|
| 118 |
+ are preserved on upgrade. |
|
| 117 | 119 |
|
| 118 | 120 |
- **`env`** *PluginEnv array* |
| 119 | 121 |
|
| ... | ... |
@@ -366,6 +366,9 @@ func (s *DockerSuite) TestPluginUpgrade(c *check.C) {
|
| 366 | 366 |
pluginV2 := "cpuguy83/docker-volume-driver-plugin-local:v2" |
| 367 | 367 |
|
| 368 | 368 |
dockerCmd(c, "plugin", "install", "--grant-all-permissions", plugin) |
| 369 |
+ dockerCmd(c, "volume", "create", "--driver", plugin, "bananas") |
|
| 370 |
+ dockerCmd(c, "run", "--rm", "-v", "bananas:/apple", "busybox", "sh", "-c", "touch /apple/core") |
|
| 371 |
+ |
|
| 369 | 372 |
out, _, err := dockerCmdWithError("plugin", "upgrade", "--grant-all-permissions", plugin, pluginV2)
|
| 370 | 373 |
c.Assert(err, checker.NotNil, check.Commentf(out)) |
| 371 | 374 |
c.Assert(out, checker.Contains, "disabled before upgrading") |
| ... | ... |
@@ -377,7 +380,7 @@ func (s *DockerSuite) TestPluginUpgrade(c *check.C) {
|
| 377 | 377 |
_, err = os.Stat(filepath.Join(dockerBasePath, "plugins", id, "rootfs", "v2")) |
| 378 | 378 |
c.Assert(os.IsNotExist(err), checker.True, check.Commentf(out)) |
| 379 | 379 |
|
| 380 |
- dockerCmd(c, "plugin", "disable", plugin) |
|
| 380 |
+ dockerCmd(c, "plugin", "disable", "-f", plugin) |
|
| 381 | 381 |
dockerCmd(c, "plugin", "upgrade", "--grant-all-permissions", "--skip-remote-check", plugin, pluginV2) |
| 382 | 382 |
|
| 383 | 383 |
// make sure "v2" file exists |
| ... | ... |
@@ -385,4 +388,6 @@ func (s *DockerSuite) TestPluginUpgrade(c *check.C) {
|
| 385 | 385 |
c.Assert(err, checker.IsNil) |
| 386 | 386 |
|
| 387 | 387 |
dockerCmd(c, "plugin", "enable", plugin) |
| 388 |
+ dockerCmd(c, "volume", "inspect", "bananas") |
|
| 389 |
+ dockerCmd(c, "run", "--rm", "-v", "bananas:/apple", "busybox", "sh", "-c", "ls -lh /apple/core") |
|
| 388 | 390 |
} |
| ... | ... |
@@ -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" |
| ... | ... |
@@ -560,6 +562,9 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
|
| 560 | 560 |
id := p.GetID() |
| 561 | 561 |
pm.config.Store.Remove(p) |
| 562 | 562 |
pluginDir := filepath.Join(pm.config.Root, id) |
| 563 |
+ if err := recursiveUnmount(pm.config.Root); err != nil {
|
|
| 564 |
+ logrus.WithField("dir", pm.config.Root).WithField("id", id).Warn(err)
|
|
| 565 |
+ } |
|
| 563 | 566 |
if err := os.RemoveAll(pluginDir); err != nil {
|
| 564 | 567 |
logrus.Warnf("unable to remove %q from plugin remove: %v", pluginDir, err)
|
| 565 | 568 |
} |
| ... | ... |
@@ -567,6 +572,43 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
|
| 567 | 567 |
return nil |
| 568 | 568 |
} |
| 569 | 569 |
|
| 570 |
+func getMounts(root string) ([]string, error) {
|
|
| 571 |
+ infos, err := mount.GetMounts() |
|
| 572 |
+ if err != nil {
|
|
| 573 |
+ return nil, errors.Wrap(err, "failed to read mount table while performing recursive unmount") |
|
| 574 |
+ } |
|
| 575 |
+ |
|
| 576 |
+ var mounts []string |
|
| 577 |
+ for _, m := range infos {
|
|
| 578 |
+ if strings.HasPrefix(m.Mountpoint, root) {
|
|
| 579 |
+ mounts = append(mounts, m.Mountpoint) |
|
| 580 |
+ } |
|
| 581 |
+ } |
|
| 582 |
+ |
|
| 583 |
+ return mounts, nil |
|
| 584 |
+} |
|
| 585 |
+ |
|
| 586 |
+func recursiveUnmount(root string) error {
|
|
| 587 |
+ mounts, err := getMounts(root) |
|
| 588 |
+ if err != nil {
|
|
| 589 |
+ return err |
|
| 590 |
+ } |
|
| 591 |
+ |
|
| 592 |
+ // sort in reverse-lexicographic order so the root mount will always be last |
|
| 593 |
+ sort.Sort(sort.Reverse(sort.StringSlice(mounts))) |
|
| 594 |
+ |
|
| 595 |
+ for i, m := range mounts {
|
|
| 596 |
+ if err := mount.Unmount(m); err != nil {
|
|
| 597 |
+ if i == len(mounts)-1 {
|
|
| 598 |
+ return errors.Wrapf(err, "error performing recursive unmount on %s", root) |
|
| 599 |
+ } |
|
| 600 |
+ logrus.WithError(err).WithField("mountpoint", m).Warn("could not unmount")
|
|
| 601 |
+ } |
|
| 602 |
+ } |
|
| 603 |
+ |
|
| 604 |
+ return nil |
|
| 605 |
+} |
|
| 606 |
+ |
|
| 570 | 607 |
// Set sets plugin args |
| 571 | 608 |
func (pm *Manager) Set(name string, args []string) error {
|
| 572 | 609 |
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 |
} |