This generates an ID string for calls to Mount/Unmount, allowing drivers
to differentiate between two callers of `Mount` and `Unmount`.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -12,6 +12,7 @@ import ( |
| 12 | 12 |
|
| 13 | 13 |
"github.com/Sirupsen/logrus" |
| 14 | 14 |
"github.com/docker/docker/pkg/chrootarchive" |
| 15 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 15 | 16 |
"github.com/docker/docker/pkg/symlink" |
| 16 | 17 |
"github.com/docker/docker/pkg/system" |
| 17 | 18 |
"github.com/docker/docker/utils" |
| ... | ... |
@@ -181,11 +182,17 @@ func (container *Container) CopyImagePathContent(v volume.Volume, destination st |
| 181 | 181 |
return err |
| 182 | 182 |
} |
| 183 | 183 |
|
| 184 |
- path, err := v.Mount() |
|
| 184 |
+ id := stringid.GenerateNonCryptoID() |
|
| 185 |
+ path, err := v.Mount(id) |
|
| 185 | 186 |
if err != nil {
|
| 186 | 187 |
return err |
| 187 | 188 |
} |
| 188 |
- defer v.Unmount() |
|
| 189 |
+ |
|
| 190 |
+ defer func() {
|
|
| 191 |
+ if err := v.Unmount(id); err != nil {
|
|
| 192 |
+ logrus.Warnf("error while unmounting volume %s: %v", v.Name(), err)
|
|
| 193 |
+ } |
|
| 194 |
+ }() |
|
| 189 | 195 |
return copyExistingContents(rootfs, path) |
| 190 | 196 |
} |
| 191 | 197 |
|
| ... | ... |
@@ -328,9 +335,10 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun |
| 328 | 328 |
} |
| 329 | 329 |
|
| 330 | 330 |
if volumeMount.Volume != nil {
|
| 331 |
- if err := volumeMount.Volume.Unmount(); err != nil {
|
|
| 331 |
+ if err := volumeMount.Volume.Unmount(volumeMount.ID); err != nil {
|
|
| 332 | 332 |
return err |
| 333 | 333 |
} |
| 334 |
+ volumeMount.ID = "" |
|
| 334 | 335 |
|
| 335 | 336 |
attributes := map[string]string{
|
| 336 | 337 |
"driver": volumeMount.Volume.DriverName(), |
| ... | ... |
@@ -115,7 +115,8 @@ Respond with a string error if an error occurred. |
| 115 | 115 |
**Request**: |
| 116 | 116 |
```json |
| 117 | 117 |
{
|
| 118 |
- "Name": "volume_name" |
|
| 118 |
+ "Name": "volume_name", |
|
| 119 |
+ "ID": "b87d7442095999a92b65b3d9691e697b61713829cc0ffd1bb72e4ccd51aa4d6c" |
|
| 119 | 120 |
} |
| 120 | 121 |
``` |
| 121 | 122 |
|
| ... | ... |
@@ -124,6 +125,8 @@ name. This is called once per container start. If the same volume_name is reques |
| 124 | 124 |
more than once, the plugin may need to keep track of each new mount request and provision |
| 125 | 125 |
at the first mount request and deprovision at the last corresponding unmount request. |
| 126 | 126 |
|
| 127 |
+`ID` is a unqiue ID for the caller that is requesting the mount. |
|
| 128 |
+ |
|
| 127 | 129 |
**Response**: |
| 128 | 130 |
```json |
| 129 | 131 |
{
|
| ... | ... |
@@ -162,7 +165,8 @@ available, and/or a string error if an error occurred. |
| 162 | 162 |
**Request**: |
| 163 | 163 |
```json |
| 164 | 164 |
{
|
| 165 |
- "Name": "volume_name" |
|
| 165 |
+ "Name": "volume_name", |
|
| 166 |
+ "ID": "b87d7442095999a92b65b3d9691e697b61713829cc0ffd1bb72e4ccd51aa4d6c" |
|
| 166 | 167 |
} |
| 167 | 168 |
``` |
| 168 | 169 |
|
| ... | ... |
@@ -170,6 +174,8 @@ Indication that Docker no longer is using the named volume. This is called once |
| 170 | 170 |
per container stop. Plugin may deduce that it is safe to deprovision it at |
| 171 | 171 |
this point. |
| 172 | 172 |
|
| 173 |
+`ID` is a unqiue ID for the caller that is requesting the mount. |
|
| 174 |
+ |
|
| 173 | 175 |
**Response**: |
| 174 | 176 |
```json |
| 175 | 177 |
{
|
| ... | ... |
@@ -61,6 +61,7 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
| 61 | 61 |
type pluginRequest struct {
|
| 62 | 62 |
Name string |
| 63 | 63 |
Opts map[string]string |
| 64 |
+ ID string |
|
| 64 | 65 |
} |
| 65 | 66 |
|
| 66 | 67 |
type pluginResp struct {
|
| ... | ... |
@@ -204,6 +205,11 @@ func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
|
| 204 | 204 |
return |
| 205 | 205 |
} |
| 206 | 206 |
|
| 207 |
+ if err := ioutil.WriteFile(filepath.Join(p, "mountID"), []byte(pr.ID), 0644); err != nil {
|
|
| 208 |
+ send(w, err) |
|
| 209 |
+ return |
|
| 210 |
+ } |
|
| 211 |
+ |
|
| 207 | 212 |
send(w, &pluginResp{Mountpoint: p})
|
| 208 | 213 |
}) |
| 209 | 214 |
|
| ... | ... |
@@ -476,3 +482,12 @@ func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverPathCalls(c *check.C |
| 476 | 476 |
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") |
| 477 | 477 |
c.Assert(s.ec.paths, checker.Equals, 1) |
| 478 | 478 |
} |
| 479 |
+ |
|
| 480 |
+func (s *DockerExternalVolumeSuite) TestExternalVolumeDriverMountID(c *check.C) {
|
|
| 481 |
+ err := s.d.StartWithBusybox() |
|
| 482 |
+ c.Assert(err, checker.IsNil) |
|
| 483 |
+ |
|
| 484 |
+ out, err := s.d.Cmd("run", "--rm", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
|
|
| 485 |
+ c.Assert(err, checker.IsNil, check.Commentf(out)) |
|
| 486 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") |
|
| 487 |
+} |
| ... | ... |
@@ -42,10 +42,17 @@ var templFuncs = template.FuncMap{
|
| 42 | 42 |
"marshalType": marshalType, |
| 43 | 43 |
"isErr": isErr, |
| 44 | 44 |
"lower": strings.ToLower, |
| 45 |
- "title": strings.Title, |
|
| 45 |
+ "title": title, |
|
| 46 | 46 |
"tag": buildTag, |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 |
+func title(s string) string {
|
|
| 50 |
+ if strings.ToLower(s) == "id" {
|
|
| 51 |
+ return "ID" |
|
| 52 |
+ } |
|
| 53 |
+ return strings.Title(s) |
|
| 54 |
+} |
|
| 55 |
+ |
|
| 49 | 56 |
var generatedTempl = template.Must(template.New("rpc_cient").Funcs(templFuncs).Parse(`
|
| 50 | 57 |
// generated code - DO NOT EDIT |
| 51 | 58 |
{{ range $k, $v := .BuildTags }}
|
| ... | ... |
@@ -101,14 +101,14 @@ func (a *volumeAdapter) CachedPath() string {
|
| 101 | 101 |
return a.eMount |
| 102 | 102 |
} |
| 103 | 103 |
|
| 104 |
-func (a *volumeAdapter) Mount() (string, error) {
|
|
| 104 |
+func (a *volumeAdapter) Mount(id string) (string, error) {
|
|
| 105 | 105 |
var err error |
| 106 |
- a.eMount, err = a.proxy.Mount(a.name) |
|
| 106 |
+ a.eMount, err = a.proxy.Mount(a.name, id) |
|
| 107 | 107 |
return a.eMount, err |
| 108 | 108 |
} |
| 109 | 109 |
|
| 110 |
-func (a *volumeAdapter) Unmount() error {
|
|
| 111 |
- err := a.proxy.Unmount(a.name) |
|
| 110 |
+func (a *volumeAdapter) Unmount(id string) error {
|
|
| 111 |
+ err := a.proxy.Unmount(a.name, id) |
|
| 112 | 112 |
if err == nil {
|
| 113 | 113 |
a.eMount = "" |
| 114 | 114 |
} |
| ... | ... |
@@ -38,9 +38,9 @@ type volumeDriver interface {
|
| 38 | 38 |
// Get the mountpoint of the given volume |
| 39 | 39 |
Path(name string) (mountpoint string, err error) |
| 40 | 40 |
// Mount the given volume and return the mountpoint |
| 41 |
- Mount(name string) (mountpoint string, err error) |
|
| 41 |
+ Mount(name, id string) (mountpoint string, err error) |
|
| 42 | 42 |
// Unmount the given volume |
| 43 |
- Unmount(name string) (err error) |
|
| 43 |
+ Unmount(name, id string) (err error) |
|
| 44 | 44 |
// List lists all the volumes known to the driver |
| 45 | 45 |
List() (volumes list, err error) |
| 46 | 46 |
// Get retrieves the volume with the requested name |
| ... | ... |
@@ -97,6 +97,7 @@ func (pp *volumeDriverProxy) Path(name string) (mountpoint string, err error) {
|
| 97 | 97 |
|
| 98 | 98 |
type volumeDriverProxyMountRequest struct {
|
| 99 | 99 |
Name string |
| 100 |
+ ID string |
|
| 100 | 101 |
} |
| 101 | 102 |
|
| 102 | 103 |
type volumeDriverProxyMountResponse struct {
|
| ... | ... |
@@ -104,13 +105,14 @@ type volumeDriverProxyMountResponse struct {
|
| 104 | 104 |
Err string |
| 105 | 105 |
} |
| 106 | 106 |
|
| 107 |
-func (pp *volumeDriverProxy) Mount(name string) (mountpoint string, err error) {
|
|
| 107 |
+func (pp *volumeDriverProxy) Mount(name string, id string) (mountpoint string, err error) {
|
|
| 108 | 108 |
var ( |
| 109 | 109 |
req volumeDriverProxyMountRequest |
| 110 | 110 |
ret volumeDriverProxyMountResponse |
| 111 | 111 |
) |
| 112 | 112 |
|
| 113 | 113 |
req.Name = name |
| 114 |
+ req.ID = id |
|
| 114 | 115 |
if err = pp.Call("VolumeDriver.Mount", req, &ret); err != nil {
|
| 115 | 116 |
return |
| 116 | 117 |
} |
| ... | ... |
@@ -126,19 +128,21 @@ func (pp *volumeDriverProxy) Mount(name string) (mountpoint string, err error) {
|
| 126 | 126 |
|
| 127 | 127 |
type volumeDriverProxyUnmountRequest struct {
|
| 128 | 128 |
Name string |
| 129 |
+ ID string |
|
| 129 | 130 |
} |
| 130 | 131 |
|
| 131 | 132 |
type volumeDriverProxyUnmountResponse struct {
|
| 132 | 133 |
Err string |
| 133 | 134 |
} |
| 134 | 135 |
|
| 135 |
-func (pp *volumeDriverProxy) Unmount(name string) (err error) {
|
|
| 136 |
+func (pp *volumeDriverProxy) Unmount(name string, id string) (err error) {
|
|
| 136 | 137 |
var ( |
| 137 | 138 |
req volumeDriverProxyUnmountRequest |
| 138 | 139 |
ret volumeDriverProxyUnmountResponse |
| 139 | 140 |
) |
| 140 | 141 |
|
| 141 | 142 |
req.Name = name |
| 143 |
+ req.ID = id |
|
| 142 | 144 |
if err = pp.Call("VolumeDriver.Unmount", req, &ret); err != nil {
|
| 143 | 145 |
return |
| 144 | 146 |
} |
| ... | ... |
@@ -68,7 +68,7 @@ func TestVolumeRequestError(t *testing.T) {
|
| 68 | 68 |
t.Fatalf("Unexpected error: %v\n", err)
|
| 69 | 69 |
} |
| 70 | 70 |
|
| 71 |
- _, err = driver.Mount("volume")
|
|
| 71 |
+ _, err = driver.Mount("volume", "123")
|
|
| 72 | 72 |
if err == nil {
|
| 73 | 73 |
t.Fatal("Expected error, was nil")
|
| 74 | 74 |
} |
| ... | ... |
@@ -77,7 +77,7 @@ func TestVolumeRequestError(t *testing.T) {
|
| 77 | 77 |
t.Fatalf("Unexpected error: %v\n", err)
|
| 78 | 78 |
} |
| 79 | 79 |
|
| 80 |
- err = driver.Unmount("volume")
|
|
| 80 |
+ err = driver.Unmount("volume", "123")
|
|
| 81 | 81 |
if err == nil {
|
| 82 | 82 |
t.Fatal("Expected error, was nil")
|
| 83 | 83 |
} |
| ... | ... |
@@ -287,7 +287,7 @@ func (v *localVolume) Path() string {
|
| 287 | 287 |
} |
| 288 | 288 |
|
| 289 | 289 |
// Mount implements the localVolume interface, returning the data location. |
| 290 |
-func (v *localVolume) Mount() (string, error) {
|
|
| 290 |
+func (v *localVolume) Mount(id string) (string, error) {
|
|
| 291 | 291 |
v.m.Lock() |
| 292 | 292 |
defer v.m.Unlock() |
| 293 | 293 |
if v.opts != nil {
|
| ... | ... |
@@ -303,7 +303,7 @@ func (v *localVolume) Mount() (string, error) {
|
| 303 | 303 |
} |
| 304 | 304 |
|
| 305 | 305 |
// Umount is for satisfying the localVolume interface and does not do anything in this driver. |
| 306 |
-func (v *localVolume) Unmount() error {
|
|
| 306 |
+func (v *localVolume) Unmount(id string) error {
|
|
| 307 | 307 |
v.m.Lock() |
| 308 | 308 |
defer v.m.Unlock() |
| 309 | 309 |
if v.opts != nil {
|
| ... | ... |
@@ -181,12 +181,12 @@ func TestCreateWithOpts(t *testing.T) {
|
| 181 | 181 |
} |
| 182 | 182 |
v := vol.(*localVolume) |
| 183 | 183 |
|
| 184 |
- dir, err := v.Mount() |
|
| 184 |
+ dir, err := v.Mount("1234")
|
|
| 185 | 185 |
if err != nil {
|
| 186 | 186 |
t.Fatal(err) |
| 187 | 187 |
} |
| 188 | 188 |
defer func() {
|
| 189 |
- if err := v.Unmount(); err != nil {
|
|
| 189 |
+ if err := v.Unmount("1234"); err != nil {
|
|
| 190 | 190 |
t.Fatal(err) |
| 191 | 191 |
} |
| 192 | 192 |
}() |
| ... | ... |
@@ -225,14 +225,14 @@ func TestCreateWithOpts(t *testing.T) {
|
| 225 | 225 |
} |
| 226 | 226 |
|
| 227 | 227 |
// test double mount |
| 228 |
- if _, err := v.Mount(); err != nil {
|
|
| 228 |
+ if _, err := v.Mount("1234"); err != nil {
|
|
| 229 | 229 |
t.Fatal(err) |
| 230 | 230 |
} |
| 231 | 231 |
if v.active.count != 2 {
|
| 232 | 232 |
t.Fatalf("Expected active mount count to be 2, got %d", v.active.count)
|
| 233 | 233 |
} |
| 234 | 234 |
|
| 235 |
- if err := v.Unmount(); err != nil {
|
|
| 235 |
+ if err := v.Unmount("1234"); err != nil {
|
|
| 236 | 236 |
t.Fatal(err) |
| 237 | 237 |
} |
| 238 | 238 |
if v.active.count != 1 {
|
| ... | ... |
@@ -19,10 +19,10 @@ func (NoopVolume) DriverName() string { return "noop" }
|
| 19 | 19 |
func (NoopVolume) Path() string { return "noop" }
|
| 20 | 20 |
|
| 21 | 21 |
// Mount mounts the volume in the container |
| 22 |
-func (NoopVolume) Mount() (string, error) { return "noop", nil }
|
|
| 22 |
+func (NoopVolume) Mount(_ string) (string, error) { return "noop", nil }
|
|
| 23 | 23 |
|
| 24 | 24 |
// Unmount unmounts the volume from the container |
| 25 |
-func (NoopVolume) Unmount() error { return nil }
|
|
| 25 |
+func (NoopVolume) Unmount(_ string) error { return nil }
|
|
| 26 | 26 |
|
| 27 | 27 |
// Status proivdes low-level details about the volume |
| 28 | 28 |
func (NoopVolume) Status() map[string]interface{} { return nil }
|
| ... | ... |
@@ -48,10 +48,10 @@ func (f FakeVolume) DriverName() string { return f.driverName }
|
| 48 | 48 |
func (FakeVolume) Path() string { return "fake" }
|
| 49 | 49 |
|
| 50 | 50 |
// Mount mounts the volume in the container |
| 51 |
-func (FakeVolume) Mount() (string, error) { return "fake", nil }
|
|
| 51 |
+func (FakeVolume) Mount(_ string) (string, error) { return "fake", nil }
|
|
| 52 | 52 |
|
| 53 | 53 |
// Unmount unmounts the volume from the container |
| 54 |
-func (FakeVolume) Unmount() error { return nil }
|
|
| 54 |
+func (FakeVolume) Unmount(_ string) error { return nil }
|
|
| 55 | 55 |
|
| 56 | 56 |
// Status proivdes low-level details about the volume |
| 57 | 57 |
func (FakeVolume) Status() map[string]interface{} { return nil }
|
| ... | ... |
@@ -5,6 +5,8 @@ import ( |
| 5 | 5 |
"os" |
| 6 | 6 |
"runtime" |
| 7 | 7 |
"strings" |
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 8 | 10 |
) |
| 9 | 11 |
|
| 10 | 12 |
// DefaultDriverName is the driver name used for the driver |
| ... | ... |
@@ -35,9 +37,9 @@ type Volume interface {
|
| 35 | 35 |
Path() string |
| 36 | 36 |
// Mount mounts the volume and returns the absolute path to |
| 37 | 37 |
// where it can be consumed. |
| 38 |
- Mount() (string, error) |
|
| 38 |
+ Mount(id string) (string, error) |
|
| 39 | 39 |
// Unmount unmounts the volume when it is no longer in use. |
| 40 |
- Unmount() error |
|
| 40 |
+ Unmount(id string) error |
|
| 41 | 41 |
// Status returns low-level status information about a volume |
| 42 | 42 |
Status() map[string]interface{}
|
| 43 | 43 |
} |
| ... | ... |
@@ -64,13 +66,19 @@ type MountPoint struct {
|
| 64 | 64 |
// Use a pointer here so we can tell if the user set this value explicitly |
| 65 | 65 |
// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated |
| 66 | 66 |
CopyData bool `json:"-"` |
| 67 |
+ // ID is the opaque ID used to pass to the volume driver. |
|
| 68 |
+ // This should be set by calls to `Mount` and unset by calls to `Unmount` |
|
| 69 |
+ ID string |
|
| 67 | 70 |
} |
| 68 | 71 |
|
| 69 | 72 |
// Setup sets up a mount point by either mounting the volume if it is |
| 70 | 73 |
// configured, or creating the source directory if supplied. |
| 71 | 74 |
func (m *MountPoint) Setup() (string, error) {
|
| 72 | 75 |
if m.Volume != nil {
|
| 73 |
- return m.Volume.Mount() |
|
| 76 |
+ if m.ID == "" {
|
|
| 77 |
+ m.ID = stringid.GenerateNonCryptoID() |
|
| 78 |
+ } |
|
| 79 |
+ return m.Volume.Mount(m.ID) |
|
| 74 | 80 |
} |
| 75 | 81 |
if len(m.Source) > 0 {
|
| 76 | 82 |
if _, err := os.Stat(m.Source); err != nil {
|