Browse code

Make propagated mount persist outside rootfs

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>

Brian Goff authored on 2017/02/03 13:08:35
Showing 5 changed files
... ...
@@ -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
 	}