Browse code

Add back compat for volume drivers `Get` and `Ls`

Use a back-compat struct to handle listing volumes for volumes we know
about (because, presumably, they are being used by a container) for
volume drivers which don't yet support `List`.

Adds a fall-back for the volume driver `Get` call, which will use
`Create` when the driver returns a `404` for `Get`. The old behavior was
to always use `Create` to get a volume reference.

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

Brian Goff authored on 2016/01/21 12:31:46
Showing 5 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,215 @@
0
+// +build !windows
1
+
2
+package main
3
+
4
+import (
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"io/ioutil"
9
+	"net/http"
10
+	"net/http/httptest"
11
+	"os"
12
+	"path/filepath"
13
+	"strings"
14
+
15
+	"github.com/docker/docker/pkg/integration/checker"
16
+
17
+	"github.com/go-check/check"
18
+)
19
+
20
+func init() {
21
+	check.Suite(&DockerExternalVolumeSuiteCompatV1_1{
22
+		ds: &DockerSuite{},
23
+	})
24
+}
25
+
26
+type DockerExternalVolumeSuiteCompatV1_1 struct {
27
+	server *httptest.Server
28
+	ds     *DockerSuite
29
+	d      *Daemon
30
+	ec     *eventCounter
31
+}
32
+
33
+func (s *DockerExternalVolumeSuiteCompatV1_1) SetUpTest(c *check.C) {
34
+	s.d = NewDaemon(c)
35
+	s.ec = &eventCounter{}
36
+}
37
+
38
+func (s *DockerExternalVolumeSuiteCompatV1_1) TearDownTest(c *check.C) {
39
+	s.d.Stop()
40
+	s.ds.TearDownTest(c)
41
+}
42
+
43
+func (s *DockerExternalVolumeSuiteCompatV1_1) SetUpSuite(c *check.C) {
44
+	mux := http.NewServeMux()
45
+	s.server = httptest.NewServer(mux)
46
+
47
+	type pluginRequest struct {
48
+		Name string
49
+	}
50
+
51
+	type pluginResp struct {
52
+		Mountpoint string `json:",omitempty"`
53
+		Err        string `json:",omitempty"`
54
+	}
55
+
56
+	type vol struct {
57
+		Name       string
58
+		Mountpoint string
59
+	}
60
+	var volList []vol
61
+
62
+	read := func(b io.ReadCloser) (pluginRequest, error) {
63
+		defer b.Close()
64
+		var pr pluginRequest
65
+		if err := json.NewDecoder(b).Decode(&pr); err != nil {
66
+			return pr, err
67
+		}
68
+		return pr, nil
69
+	}
70
+
71
+	send := func(w http.ResponseWriter, data interface{}) {
72
+		switch t := data.(type) {
73
+		case error:
74
+			http.Error(w, t.Error(), 500)
75
+		case string:
76
+			w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
77
+			fmt.Fprintln(w, t)
78
+		default:
79
+			w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
80
+			json.NewEncoder(w).Encode(&data)
81
+		}
82
+	}
83
+
84
+	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
85
+		s.ec.activations++
86
+		send(w, `{"Implements": ["VolumeDriver"]}`)
87
+	})
88
+
89
+	mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
90
+		s.ec.creations++
91
+		pr, err := read(r.Body)
92
+		if err != nil {
93
+			send(w, err)
94
+			return
95
+		}
96
+		volList = append(volList, vol{Name: pr.Name})
97
+		send(w, nil)
98
+	})
99
+
100
+	mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
101
+		s.ec.removals++
102
+		pr, err := read(r.Body)
103
+		if err != nil {
104
+			send(w, err)
105
+			return
106
+		}
107
+
108
+		if err := os.RemoveAll(hostVolumePath(pr.Name)); err != nil {
109
+			send(w, &pluginResp{Err: err.Error()})
110
+			return
111
+		}
112
+
113
+		for i, v := range volList {
114
+			if v.Name == pr.Name {
115
+				if err := os.RemoveAll(hostVolumePath(v.Name)); err != nil {
116
+					send(w, fmt.Sprintf(`{"Err": "%v"}`, err))
117
+					return
118
+				}
119
+				volList = append(volList[:i], volList[i+1:]...)
120
+				break
121
+			}
122
+		}
123
+		send(w, nil)
124
+	})
125
+
126
+	mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
127
+		s.ec.paths++
128
+
129
+		pr, err := read(r.Body)
130
+		if err != nil {
131
+			send(w, err)
132
+			return
133
+		}
134
+		p := hostVolumePath(pr.Name)
135
+		send(w, &pluginResp{Mountpoint: p})
136
+	})
137
+
138
+	mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
139
+		s.ec.mounts++
140
+
141
+		pr, err := read(r.Body)
142
+		if err != nil {
143
+			send(w, err)
144
+			return
145
+		}
146
+
147
+		p := hostVolumePath(pr.Name)
148
+		if err := os.MkdirAll(p, 0755); err != nil {
149
+			send(w, &pluginResp{Err: err.Error()})
150
+			return
151
+		}
152
+
153
+		if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil {
154
+			send(w, err)
155
+			return
156
+		}
157
+
158
+		send(w, &pluginResp{Mountpoint: p})
159
+	})
160
+
161
+	mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
162
+		s.ec.unmounts++
163
+
164
+		_, err := read(r.Body)
165
+		if err != nil {
166
+			send(w, err)
167
+			return
168
+		}
169
+
170
+		send(w, nil)
171
+	})
172
+
173
+	err := os.MkdirAll("/etc/docker/plugins", 0755)
174
+	c.Assert(err, checker.IsNil)
175
+
176
+	err = ioutil.WriteFile("/etc/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644)
177
+	c.Assert(err, checker.IsNil)
178
+}
179
+
180
+func (s *DockerExternalVolumeSuiteCompatV1_1) TearDownSuite(c *check.C) {
181
+	s.server.Close()
182
+
183
+	err := os.RemoveAll("/etc/docker/plugins")
184
+	c.Assert(err, checker.IsNil)
185
+}
186
+
187
+func (s *DockerExternalVolumeSuiteCompatV1_1) TestExternalVolumeDriverCompatV1_1(c *check.C) {
188
+	err := s.d.StartWithBusybox()
189
+	c.Assert(err, checker.IsNil)
190
+
191
+	out, err := s.d.Cmd("run", "--name=test", "-v", "foo:/bar", "--volume-driver", "test-external-volume-driver", "busybox", "sh", "-c", "echo hello > /bar/hello")
192
+	c.Assert(err, checker.IsNil, check.Commentf(out))
193
+	out, err = s.d.Cmd("rm", "test")
194
+	c.Assert(err, checker.IsNil, check.Commentf(out))
195
+
196
+	out, err = s.d.Cmd("run", "--name=test2", "-v", "foo:/bar", "busybox", "cat", "/bar/hello")
197
+	c.Assert(err, checker.IsNil, check.Commentf(out))
198
+	c.Assert(strings.TrimSpace(out), checker.Equals, "hello")
199
+
200
+	err = s.d.Restart()
201
+	c.Assert(err, checker.IsNil)
202
+
203
+	out, err = s.d.Cmd("start", "-a", "test2")
204
+	c.Assert(strings.TrimSpace(out), checker.Equals, "hello")
205
+
206
+	out, err = s.d.Cmd("rm", "test2")
207
+	c.Assert(err, checker.IsNil, check.Commentf(out))
208
+
209
+	out, err = s.d.Cmd("volume", "inspect", "foo")
210
+	c.Assert(err, checker.IsNil, check.Commentf(out))
211
+
212
+	out, err = s.d.Cmd("volume", "rm", "foo")
213
+	c.Assert(err, checker.IsNil, check.Commentf(out))
214
+}
... ...
@@ -3,7 +3,6 @@ package plugins
3 3
 import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6
-	"fmt"
7 6
 	"io"
8 7
 	"io/ioutil"
9 8
 	"net/http"
... ...
@@ -124,7 +123,7 @@ func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool)
124 124
 		if resp.StatusCode != http.StatusOK {
125 125
 			b, err := ioutil.ReadAll(resp.Body)
126 126
 			if err != nil {
127
-				return nil, fmt.Errorf("%s: %s", serviceMethod, err)
127
+				return nil, &statusError{resp.StatusCode, serviceMethod, err.Error()}
128 128
 			}
129 129
 
130 130
 			// Plugins' Response(s) should have an Err field indicating what went
... ...
@@ -136,11 +135,11 @@ func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool)
136 136
 			remoteErr := responseErr{}
137 137
 			if err := json.Unmarshal(b, &remoteErr); err == nil {
138 138
 				if remoteErr.Err != "" {
139
-					return nil, fmt.Errorf("%s: %s", serviceMethod, remoteErr.Err)
139
+					return nil, &statusError{resp.StatusCode, serviceMethod, remoteErr.Err}
140 140
 				}
141 141
 			}
142 142
 			// old way...
143
-			return nil, fmt.Errorf("%s: %s", serviceMethod, string(b))
143
+			return nil, &statusError{resp.StatusCode, serviceMethod, string(b)}
144 144
 		}
145 145
 		return resp.Body, nil
146 146
 	}
147 147
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package plugins
1
+
2
+import (
3
+	"fmt"
4
+	"net/http"
5
+)
6
+
7
+type statusError struct {
8
+	status int
9
+	method string
10
+	err    string
11
+}
12
+
13
+// Error returns a formated string for this error type
14
+func (e *statusError) Error() string {
15
+	return fmt.Sprintf("%s: %v", e.method, e.err)
16
+}
17
+
18
+// IsNotFound indicates if the passed in error is from an http.StatusNotFound from the plugin
19
+func IsNotFound(err error) bool {
20
+	return isStatusError(err, http.StatusNotFound)
21
+}
22
+
23
+func isStatusError(err error, status int) bool {
24
+	if err == nil {
25
+		return false
26
+	}
27
+	e, ok := err.(*statusError)
28
+	if !ok {
29
+		return false
30
+	}
31
+	return e.status == status
32
+}
... ...
@@ -1,6 +1,9 @@
1 1
 package volumedrivers
2 2
 
3
-import "github.com/docker/docker/volume"
3
+import (
4
+	"github.com/docker/docker/pkg/plugins"
5
+	"github.com/docker/docker/volume"
6
+)
4 7
 
5 8
 type volumeDriverAdapter struct {
6 9
 	name  string
... ...
@@ -47,7 +50,11 @@ func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
47 47
 func (a *volumeDriverAdapter) Get(name string) (volume.Volume, error) {
48 48
 	v, err := a.proxy.Get(name)
49 49
 	if err != nil {
50
-		return nil, err
50
+		// TODO: remove this hack. Allows back compat with volume drivers that don't support this call
51
+		if !plugins.IsNotFound(err) {
52
+			return nil, err
53
+		}
54
+		return a.Create(name, nil)
51 55
 	}
52 56
 
53 57
 	return &volumeAdapter{
... ...
@@ -14,23 +14,23 @@ import (
14 14
 func New() *VolumeStore {
15 15
 	return &VolumeStore{
16 16
 		locks: &locker.Locker{},
17
-		names: make(map[string]string),
17
+		names: make(map[string]volume.Volume),
18 18
 		refs:  make(map[string][]string),
19 19
 	}
20 20
 }
21 21
 
22
-func (s *VolumeStore) getNamed(name string) (string, bool) {
22
+func (s *VolumeStore) getNamed(name string) (volume.Volume, bool) {
23 23
 	s.globalLock.Lock()
24
-	driverName, exists := s.names[name]
24
+	v, exists := s.names[name]
25 25
 	s.globalLock.Unlock()
26
-	return driverName, exists
26
+	return v, exists
27 27
 }
28 28
 
29
-func (s *VolumeStore) setNamed(name, driver, ref string) {
29
+func (s *VolumeStore) setNamed(v volume.Volume, ref string) {
30 30
 	s.globalLock.Lock()
31
-	s.names[name] = driver
31
+	s.names[v.Name()] = v
32 32
 	if len(ref) > 0 {
33
-		s.refs[name] = append(s.refs[name], ref)
33
+		s.refs[v.Name()] = append(s.refs[v.Name()], ref)
34 34
 	}
35 35
 	s.globalLock.Unlock()
36 36
 }
... ...
@@ -48,7 +48,7 @@ type VolumeStore struct {
48 48
 	globalLock sync.Mutex
49 49
 	// names stores the volume name -> driver name relationship.
50 50
 	// This is used for making lookups faster so we don't have to probe all drivers
51
-	names map[string]string
51
+	names map[string]volume.Volume
52 52
 	// refs stores the volume name and the list of things referencing it
53 53
 	refs map[string][]string
54 54
 }
... ...
@@ -67,12 +67,12 @@ func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
67 67
 		name := normaliseVolumeName(v.Name())
68 68
 
69 69
 		s.locks.Lock(name)
70
-		driverName, exists := s.getNamed(name)
70
+		storedV, exists := s.getNamed(name)
71 71
 		if !exists {
72
-			s.setNamed(name, v.DriverName(), "")
72
+			s.setNamed(v, "")
73 73
 		}
74
-		if exists && driverName != v.DriverName() {
75
-			logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), driverName, v.DriverName())
74
+		if exists && storedV.DriverName() != v.DriverName() {
75
+			logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), storedV.DriverName(), v.DriverName())
76 76
 			s.locks.Unlock(v.Name())
77 77
 			continue
78 78
 		}
... ...
@@ -95,8 +95,9 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
95 95
 	)
96 96
 
97 97
 	type vols struct {
98
-		vols []volume.Volume
99
-		err  error
98
+		vols       []volume.Volume
99
+		err        error
100
+		driverName string
100 101
 	}
101 102
 	chVols := make(chan vols, len(drivers))
102 103
 
... ...
@@ -104,23 +105,32 @@ func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
104 104
 		go func(d volume.Driver) {
105 105
 			vs, err := d.List()
106 106
 			if err != nil {
107
-				chVols <- vols{err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
107
+				chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
108 108
 				return
109 109
 			}
110 110
 			chVols <- vols{vols: vs}
111 111
 		}(vd)
112 112
 	}
113 113
 
114
+	badDrivers := make(map[string]struct{})
114 115
 	for i := 0; i < len(drivers); i++ {
115 116
 		vs := <-chVols
116 117
 
117 118
 		if vs.err != nil {
118 119
 			warnings = append(warnings, vs.err.Error())
120
+			badDrivers[vs.driverName] = struct{}{}
119 121
 			logrus.Warn(vs.err)
120
-			continue
121 122
 		}
122 123
 		ls = append(ls, vs.vols...)
123 124
 	}
125
+
126
+	if len(badDrivers) > 0 {
127
+		for _, v := range s.names {
128
+			if _, exists := badDrivers[v.DriverName()]; exists {
129
+				ls = append(ls, v)
130
+			}
131
+		}
132
+	}
124 133
 	return ls, warnings, nil
125 134
 }
126 135
 
... ...
@@ -137,7 +147,7 @@ func (s *VolumeStore) CreateWithRef(name, driverName, ref string, opts map[strin
137 137
 		return nil, &OpErr{Err: err, Name: name, Op: "create"}
138 138
 	}
139 139
 
140
-	s.setNamed(name, v.DriverName(), ref)
140
+	s.setNamed(v, ref)
141 141
 	return v, nil
142 142
 }
143 143
 
... ...
@@ -151,7 +161,7 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v
151 151
 	if err != nil {
152 152
 		return nil, &OpErr{Err: err, Name: name, Op: "create"}
153 153
 	}
154
-	s.setNamed(name, v.DriverName(), "")
154
+	s.setNamed(v, "")
155 155
 	return v, nil
156 156
 }
157 157
 
... ...
@@ -169,12 +179,11 @@ func (s *VolumeStore) create(name, driverName string, opts map[string]string) (v
169 169
 		return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"}
170 170
 	}
171 171
 
172
-	vdName, exists := s.getNamed(name)
173
-	if exists {
174
-		if vdName != driverName && driverName != "" && driverName != volume.DefaultDriverName {
172
+	if v, exists := s.getNamed(name); exists {
173
+		if v.DriverName() != driverName && driverName != "" && driverName != volume.DefaultDriverName {
175 174
 			return nil, errNameConflict
176 175
 		}
177
-		driverName = vdName
176
+		return v, nil
178 177
 	}
179 178
 
180 179
 	logrus.Debugf("Registering new volume reference: driver %s, name %s", driverName, name)
... ...
@@ -207,7 +216,7 @@ func (s *VolumeStore) GetWithRef(name, driverName, ref string) (volume.Volume, e
207 207
 		return nil, &OpErr{Err: err, Name: name, Op: "get"}
208 208
 	}
209 209
 
210
-	s.setNamed(name, v.DriverName(), ref)
210
+	s.setNamed(v, ref)
211 211
 	return v, nil
212 212
 }
213 213
 
... ...
@@ -221,6 +230,7 @@ func (s *VolumeStore) Get(name string) (volume.Volume, error) {
221 221
 	if err != nil {
222 222
 		return nil, &OpErr{Err: err, Name: name, Op: "get"}
223 223
 	}
224
+	s.setNamed(v, "")
224 225
 	return v, nil
225 226
 }
226 227
 
... ...
@@ -229,8 +239,8 @@ func (s *VolumeStore) Get(name string) (volume.Volume, error) {
229 229
 // it is expected that callers of this function hold any neccessary locks
230 230
 func (s *VolumeStore) getVolume(name string) (volume.Volume, error) {
231 231
 	logrus.Debugf("Getting volume reference for name: %s", name)
232
-	if vdName, exists := s.names[name]; exists {
233
-		vd, err := volumedrivers.GetDriver(vdName)
232
+	if v, exists := s.names[name]; exists {
233
+		vd, err := volumedrivers.GetDriver(v.DriverName())
234 234
 		if err != nil {
235 235
 			return nil, err
236 236
 		}