Browse code

Replace vol plugin integration test w/ unit test

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

Brian Goff authored on 2017/11/16 03:13:22
Showing 7 changed files
1 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-package cmd
2 1
deleted file mode 100644
... ...
@@ -1,23 +0,0 @@
1
-package main
2
-
3
-import (
4
-	"net"
5
-	"net/http"
6
-)
7
-
8
-func main() {
9
-	l, err := net.Listen("unix", "/run/docker/plugins/plugin.sock")
10
-	if err != nil {
11
-		panic(err)
12
-	}
13
-
14
-	mux := http.NewServeMux()
15
-	server := http.Server{
16
-		Addr:    l.Addr().String(),
17
-		Handler: http.NewServeMux(),
18
-	}
19
-	mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
20
-		http.Error(w, "error during create", http.StatusInternalServerError)
21
-	})
22
-	server.Serve(l)
23
-}
24 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-package main
2 1
deleted file mode 100644
... ...
@@ -1,51 +0,0 @@
1
-// +build linux
2
-
3
-package volume
4
-
5
-import (
6
-	"context"
7
-	"testing"
8
-
9
-	"github.com/docker/docker/api/types"
10
-	"github.com/docker/docker/api/types/volume"
11
-	"github.com/docker/docker/integration-cli/daemon"
12
-)
13
-
14
-// TestCreateDerefOnError ensures that if a volume create fails, that the plugin is dereferenced
15
-// Normally 1 volume == 1 reference to a plugin, which prevents a plugin from being removed.
16
-// If the volume create fails, we should make sure to dereference the plugin.
17
-func TestCreateDerefOnError(t *testing.T) {
18
-	t.Parallel()
19
-
20
-	d := daemon.New(t, "", dockerdBinary, daemon.Config{})
21
-	d.Start(t)
22
-	defer d.Stop(t)
23
-
24
-	c, err := d.NewClient()
25
-	if err != nil {
26
-		t.Fatal(err)
27
-	}
28
-
29
-	pName := "testderef"
30
-	createPlugin(t, c, pName, "create-error", asVolumeDriver)
31
-
32
-	if err := c.PluginEnable(context.Background(), pName, types.PluginEnableOptions{Timeout: 30}); err != nil {
33
-		t.Fatal(err)
34
-	}
35
-
36
-	_, err = c.VolumeCreate(context.Background(), volume.VolumesCreateBody{
37
-		Driver: pName,
38
-		Name:   "fake",
39
-	})
40
-	if err == nil {
41
-		t.Fatal("volume create should have failed")
42
-	}
43
-
44
-	if err := c.PluginDisable(context.Background(), pName, types.PluginDisableOptions{}); err != nil {
45
-		t.Fatal(err)
46
-	}
47
-
48
-	if err := c.PluginRemove(context.Background(), pName, types.PluginRemoveOptions{}); err != nil {
49
-		t.Fatal(err)
50
-	}
51
-}
52 1
deleted file mode 100644
... ...
@@ -1,69 +0,0 @@
1
-package volume
2
-
3
-import (
4
-	"context"
5
-	"os"
6
-	"os/exec"
7
-	"path/filepath"
8
-	"testing"
9
-	"time"
10
-
11
-	"github.com/docker/docker/api/types"
12
-	"github.com/docker/docker/integration-cli/fixtures/plugin"
13
-	"github.com/docker/docker/pkg/locker"
14
-	"github.com/pkg/errors"
15
-)
16
-
17
-const dockerdBinary = "dockerd"
18
-
19
-var pluginBuildLock = locker.New()
20
-
21
-func ensurePlugin(t *testing.T, name string) string {
22
-	pluginBuildLock.Lock(name)
23
-	defer pluginBuildLock.Unlock(name)
24
-
25
-	installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name)
26
-	if _, err := os.Stat(installPath); err == nil {
27
-		return installPath
28
-	}
29
-
30
-	goBin, err := exec.LookPath("go")
31
-	if err != nil {
32
-		t.Fatal(err)
33
-	}
34
-
35
-	cmd := exec.Command(goBin, "build", "-o", installPath, "./"+filepath.Join("cmd", name))
36
-	cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
37
-	if out, err := cmd.CombinedOutput(); err != nil {
38
-		t.Fatal(errors.Wrapf(err, "error building basic plugin bin: %s", string(out)))
39
-	}
40
-
41
-	return installPath
42
-}
43
-
44
-func asVolumeDriver(cfg *plugin.Config) {
45
-	cfg.Interface.Types = []types.PluginInterfaceType{
46
-		{Capability: "volumedriver", Prefix: "docker", Version: "1.0"},
47
-	}
48
-}
49
-
50
-func withSockPath(name string) func(*plugin.Config) {
51
-	return func(cfg *plugin.Config) {
52
-		cfg.Interface.Socket = name
53
-	}
54
-}
55
-
56
-func createPlugin(t *testing.T, client plugin.CreateClient, alias, bin string, opts ...plugin.CreateOpt) {
57
-	pluginBin := ensurePlugin(t, bin)
58
-
59
-	opts = append(opts, withSockPath("plugin.sock"))
60
-	opts = append(opts, plugin.WithBinary(pluginBin))
61
-
62
-	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
63
-	err := plugin.Create(ctx, client, alias, opts...)
64
-	cancel()
65
-
66
-	if err != nil {
67
-		t.Fatal(err)
68
-	}
69
-}
... ...
@@ -2,7 +2,9 @@ package store
2 2
 
3 3
 import (
4 4
 	"errors"
5
+	"fmt"
5 6
 	"io/ioutil"
7
+	"net"
6 8
 	"os"
7 9
 	"strings"
8 10
 	"testing"
... ...
@@ -266,3 +268,55 @@ func TestCreateKeepOptsLabelsWhenExistsRemotely(t *testing.T) {
266 266
 		t.Fatalf("got unexpected type: %T", v)
267 267
 	}
268 268
 }
269
+
270
+func TestDefererencePluginOnCreateError(t *testing.T) {
271
+	var (
272
+		l   net.Listener
273
+		err error
274
+	)
275
+
276
+	for i := 32768; l == nil && i < 40000; i++ {
277
+		l, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", i))
278
+	}
279
+	if l == nil {
280
+		t.Fatalf("could not create listener: %v", err)
281
+	}
282
+	defer l.Close()
283
+
284
+	d := volumetestutils.NewFakeDriver("TestDefererencePluginOnCreateError")
285
+	p, err := volumetestutils.MakeFakePlugin(d, l)
286
+	if err != nil {
287
+		t.Fatal(err)
288
+	}
289
+
290
+	pg := volumetestutils.NewFakePluginGetter(p)
291
+	volumedrivers.RegisterPluginGetter(pg)
292
+
293
+	dir, err := ioutil.TempDir("", "test-plugin-deref-err")
294
+	if err != nil {
295
+		t.Fatal(err)
296
+	}
297
+	defer os.RemoveAll(dir)
298
+
299
+	s, err := New(dir)
300
+	if err != nil {
301
+		t.Fatal(err)
302
+	}
303
+
304
+	// create a good volume so we have a plugin reference
305
+	_, err = s.Create("fake1", d.Name(), nil, nil)
306
+	if err != nil {
307
+		t.Fatal(err)
308
+	}
309
+
310
+	// Now create another one expecting an error
311
+	_, err = s.Create("fake2", d.Name(), map[string]string{"error": "some error"}, nil)
312
+	if err == nil || !strings.Contains(err.Error(), "some error") {
313
+		t.Fatalf("expected an error on create: %v", err)
314
+	}
315
+
316
+	// There should be only 1 plugin reference
317
+	if refs := volumetestutils.FakeRefs(p); refs != 1 {
318
+		t.Fatalf("expected 1 plugin reference, got: %d", refs)
319
+	}
320
+}
... ...
@@ -1,9 +1,15 @@
1 1
 package testutils
2 2
 
3 3
 import (
4
+	"encoding/json"
5
+	"errors"
4 6
 	"fmt"
7
+	"net"
8
+	"net/http"
5 9
 	"time"
6 10
 
11
+	"github.com/docker/docker/pkg/plugingetter"
12
+	"github.com/docker/docker/pkg/plugins"
7 13
 	"github.com/docker/docker/volume"
8 14
 )
9 15
 
... ...
@@ -121,3 +127,99 @@ func (d *FakeDriver) Get(name string) (volume.Volume, error) {
121 121
 func (*FakeDriver) Scope() string {
122 122
 	return "local"
123 123
 }
124
+
125
+type fakePlugin struct {
126
+	client *plugins.Client
127
+	name   string
128
+	refs   int
129
+}
130
+
131
+// MakeFakePlugin creates a fake plugin from the passed in driver
132
+// Note: currently only "Create" is implemented because that's all that's needed
133
+// so far. If you need it to test something else, add it here, but probably you
134
+// shouldn't need to use this except for very specific cases with v2 plugin handling.
135
+func MakeFakePlugin(d volume.Driver, l net.Listener) (plugingetter.CompatPlugin, error) {
136
+	c, err := plugins.NewClient(l.Addr().Network()+"://"+l.Addr().String(), nil)
137
+	if err != nil {
138
+		return nil, err
139
+	}
140
+	mux := http.NewServeMux()
141
+
142
+	mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
143
+		createReq := struct {
144
+			Name string
145
+			Opts map[string]string
146
+		}{}
147
+		if err := json.NewDecoder(r.Body).Decode(&createReq); err != nil {
148
+			fmt.Fprintf(w, `{"Err": "%s"}`, err.Error())
149
+			return
150
+		}
151
+		_, err := d.Create(createReq.Name, createReq.Opts)
152
+		if err != nil {
153
+			fmt.Fprintf(w, `{"Err": "%s"}`, err.Error())
154
+			return
155
+		}
156
+		w.Write([]byte("{}"))
157
+	})
158
+
159
+	go http.Serve(l, mux)
160
+	return &fakePlugin{client: c, name: d.Name()}, nil
161
+}
162
+
163
+func (p *fakePlugin) Client() *plugins.Client {
164
+	return p.client
165
+}
166
+
167
+func (p *fakePlugin) Name() string {
168
+	return p.name
169
+}
170
+
171
+func (p *fakePlugin) IsV1() bool {
172
+	return false
173
+}
174
+
175
+func (p *fakePlugin) BasePath() string {
176
+	return ""
177
+}
178
+
179
+type fakePluginGetter struct {
180
+	plugins map[string]plugingetter.CompatPlugin
181
+}
182
+
183
+// NewFakePluginGetter returns a plugin getter for fake plugins
184
+func NewFakePluginGetter(pls ...plugingetter.CompatPlugin) plugingetter.PluginGetter {
185
+	idx := make(map[string]plugingetter.CompatPlugin, len(pls))
186
+	for _, p := range pls {
187
+		idx[p.Name()] = p
188
+	}
189
+	return &fakePluginGetter{plugins: idx}
190
+}
191
+
192
+// This ignores the second argument since we only care about volume drivers here,
193
+// there shouldn't be any other kind of plugin in here
194
+func (g *fakePluginGetter) Get(name, _ string, mode int) (plugingetter.CompatPlugin, error) {
195
+	p, ok := g.plugins[name]
196
+	if !ok {
197
+		return nil, errors.New("not found")
198
+	}
199
+	p.(*fakePlugin).refs += mode
200
+	return p, nil
201
+}
202
+
203
+func (g *fakePluginGetter) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
204
+	panic("GetAllByCap shouldn't be called")
205
+}
206
+
207
+func (g *fakePluginGetter) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin {
208
+	panic("GetAllManagedPluginsByCap should not be called")
209
+}
210
+
211
+func (g *fakePluginGetter) Handle(capability string, callback func(string, *plugins.Client)) {
212
+	panic("Handle should not be called")
213
+}
214
+
215
+// FakeRefs checks ref count on a fake plugin.
216
+func FakeRefs(p plugingetter.CompatPlugin) int {
217
+	// this should panic if something other than a `*fakePlugin` is passed in
218
+	return p.(*fakePlugin).refs
219
+}