Browse code

Add refcount for MountPoint

This makes sure that multiple users of MountPoint pointer can
mount/unmount without affecting each other.

Before this PR, if you run a container (stay running), then do `docker
cp`, when the `docker cp` is done the MountPoint is mutated such that
when the container stops the volume driver will not get an Unmount
request. Effectively there would be two mounts with only one unmount.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2017/04/28 12:33:33
Showing 3 changed files
... ...
@@ -400,21 +400,20 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu
400 400
 func (container *Container) UnmountVolumes(volumeEventLog func(name, action string, attributes map[string]string)) error {
401 401
 	var errors []string
402 402
 	for _, volumeMount := range container.MountPoints {
403
-		// Check if the mountpoint has an ID, this is currently the best way to tell if it's actually mounted
404
-		// TODO(cpuguyh83): there should be a better way to handle this
405
-		if volumeMount.Volume != nil && volumeMount.ID != "" {
406
-			if err := volumeMount.Volume.Unmount(volumeMount.ID); err != nil {
407
-				errors = append(errors, err.Error())
408
-				continue
409
-			}
410
-			volumeMount.ID = ""
403
+		if volumeMount.Volume == nil {
404
+			continue
405
+		}
411 406
 
412
-			attributes := map[string]string{
413
-				"driver":    volumeMount.Volume.DriverName(),
414
-				"container": container.ID,
415
-			}
416
-			volumeEventLog(volumeMount.Volume.Name(), "unmount", attributes)
407
+		if err := volumeMount.Cleanup(); err != nil {
408
+			errors = append(errors, err.Error())
409
+			continue
410
+		}
411
+
412
+		attributes := map[string]string{
413
+			"driver":    volumeMount.Volume.DriverName(),
414
+			"container": container.ID,
417 415
 		}
416
+		volumeEventLog(volumeMount.Volume.Name(), "unmount", attributes)
418 417
 	}
419 418
 	if len(errors) > 0 {
420 419
 		return fmt.Errorf("error while unmounting volumes for container %s: %s", container.ID, strings.Join(errors, "; "))
... ...
@@ -616,3 +616,18 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnmountOnMountFail(c
616 616
 	out, _ = s.d.Cmd("run", "-w", "/foo", "-v", "testumount:/foo", "busybox", "true")
617 617
 	c.Assert(s.ec.unmounts, checker.Equals, 0, check.Commentf(out))
618 618
 }
619
+
620
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverUnmountOnCp(c *check.C) {
621
+	s.d.StartWithBusybox(c)
622
+	s.d.Cmd("volume", "create", "-d", "test-external-volume-driver", "--name=test")
623
+
624
+	out, _ := s.d.Cmd("run", "-d", "--name=test", "-v", "test:/foo", "busybox", "/bin/sh", "-c", "touch /test && top")
625
+	c.Assert(s.ec.mounts, checker.Equals, 1, check.Commentf(out))
626
+
627
+	out, _ = s.d.Cmd("cp", "test:/test", "/tmp/test")
628
+	c.Assert(s.ec.mounts, checker.Equals, 2, check.Commentf(out))
629
+	c.Assert(s.ec.unmounts, checker.Equals, 1, check.Commentf(out))
630
+
631
+	out, _ = s.d.Cmd("kill", "test")
632
+	c.Assert(s.ec.unmounts, checker.Equals, 2, check.Commentf(out))
633
+}
... ...
@@ -120,6 +120,28 @@ type MountPoint struct {
120 120
 
121 121
 	// Sepc is a copy of the API request that created this mount.
122 122
 	Spec mounttypes.Mount
123
+
124
+	// Track usage of this mountpoint
125
+	// Specicially needed for containers which are running and calls to `docker cp`
126
+	// because both these actions require mounting the volumes.
127
+	active int
128
+}
129
+
130
+// Cleanup frees resources used by the mountpoint
131
+func (m *MountPoint) Cleanup() error {
132
+	if m.Volume == nil || m.ID == "" {
133
+		return nil
134
+	}
135
+
136
+	if err := m.Volume.Unmount(m.ID); err != nil {
137
+		return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
138
+	}
139
+
140
+	m.active--
141
+	if m.active == 0 {
142
+		m.ID = ""
143
+	}
144
+	return nil
123 145
 }
124 146
 
125 147
 // Setup sets up a mount point by either mounting the volume if it is
... ...
@@ -147,12 +169,16 @@ func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int) (path string
147 147
 		if err != nil {
148 148
 			return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
149 149
 		}
150
+
150 151
 		m.ID = id
152
+		m.active++
151 153
 		return path, nil
152 154
 	}
155
+
153 156
 	if len(m.Source) == 0 {
154 157
 		return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
155 158
 	}
159
+
156 160
 	// system.MkdirAll() produces an error if m.Source exists and is a file (not a directory),
157 161
 	if m.Type == mounttypes.TypeBind {
158 162
 		// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)