Merge pull request #35512 from cpuguy83/replace_plugun_integration_with_unit
Replace vol plugin integration test w/ unit test
This commit is contained in:
commit
ab90bc2961
7 changed files with 156 additions and 145 deletions
|
@ -1 +0,0 @@
|
||||||
package cmd
|
|
|
@ -1,23 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
l, err := net.Listen("unix", "/run/docker/plugins/plugin.sock")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
server := http.Server{
|
|
||||||
Addr: l.Addr().String(),
|
|
||||||
Handler: http.NewServeMux(),
|
|
||||||
}
|
|
||||||
mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Error(w, "error during create", http.StatusInternalServerError)
|
|
||||||
})
|
|
||||||
server.Serve(l)
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package main
|
|
|
@ -1,51 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
"github.com/docker/docker/integration-cli/daemon"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestCreateDerefOnError ensures that if a volume create fails, that the plugin is dereferenced
|
|
||||||
// Normally 1 volume == 1 reference to a plugin, which prevents a plugin from being removed.
|
|
||||||
// If the volume create fails, we should make sure to dereference the plugin.
|
|
||||||
func TestCreateDerefOnError(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
d := daemon.New(t, "", dockerdBinary, daemon.Config{})
|
|
||||||
d.Start(t)
|
|
||||||
defer d.Stop(t)
|
|
||||||
|
|
||||||
c, err := d.NewClient()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pName := "testderef"
|
|
||||||
createPlugin(t, c, pName, "create-error", asVolumeDriver)
|
|
||||||
|
|
||||||
if err := c.PluginEnable(context.Background(), pName, types.PluginEnableOptions{Timeout: 30}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.VolumeCreate(context.Background(), volume.VolumesCreateBody{
|
|
||||||
Driver: pName,
|
|
||||||
Name: "fake",
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("volume create should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.PluginDisable(context.Background(), pName, types.PluginDisableOptions{}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.PluginRemove(context.Background(), pName, types.PluginRemoveOptions{}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/integration-cli/fixtures/plugin"
|
|
||||||
"github.com/docker/docker/pkg/locker"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const dockerdBinary = "dockerd"
|
|
||||||
|
|
||||||
var pluginBuildLock = locker.New()
|
|
||||||
|
|
||||||
func ensurePlugin(t *testing.T, name string) string {
|
|
||||||
pluginBuildLock.Lock(name)
|
|
||||||
defer pluginBuildLock.Unlock(name)
|
|
||||||
|
|
||||||
installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name)
|
|
||||||
if _, err := os.Stat(installPath); err == nil {
|
|
||||||
return installPath
|
|
||||||
}
|
|
||||||
|
|
||||||
goBin, err := exec.LookPath("go")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(goBin, "build", "-o", installPath, "./"+filepath.Join("cmd", name))
|
|
||||||
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
t.Fatal(errors.Wrapf(err, "error building basic plugin bin: %s", string(out)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return installPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func asVolumeDriver(cfg *plugin.Config) {
|
|
||||||
cfg.Interface.Types = []types.PluginInterfaceType{
|
|
||||||
{Capability: "volumedriver", Prefix: "docker", Version: "1.0"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withSockPath(name string) func(*plugin.Config) {
|
|
||||||
return func(cfg *plugin.Config) {
|
|
||||||
cfg.Interface.Socket = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPlugin(t *testing.T, client plugin.CreateClient, alias, bin string, opts ...plugin.CreateOpt) {
|
|
||||||
pluginBin := ensurePlugin(t, bin)
|
|
||||||
|
|
||||||
opts = append(opts, withSockPath("plugin.sock"))
|
|
||||||
opts = append(opts, plugin.WithBinary(pluginBin))
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
||||||
err := plugin.Create(ctx, client, alias, opts...)
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,9 @@ package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -266,3 +268,55 @@ func TestCreateKeepOptsLabelsWhenExistsRemotely(t *testing.T) {
|
||||||
t.Fatalf("got unexpected type: %T", v)
|
t.Fatalf("got unexpected type: %T", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefererencePluginOnCreateError(t *testing.T) {
|
||||||
|
var (
|
||||||
|
l net.Listener
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 32768; l == nil && i < 40000; i++ {
|
||||||
|
l, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", i))
|
||||||
|
}
|
||||||
|
if l == nil {
|
||||||
|
t.Fatalf("could not create listener: %v", err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
d := volumetestutils.NewFakeDriver("TestDefererencePluginOnCreateError")
|
||||||
|
p, err := volumetestutils.MakeFakePlugin(d, l)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pg := volumetestutils.NewFakePluginGetter(p)
|
||||||
|
volumedrivers.RegisterPluginGetter(pg)
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "test-plugin-deref-err")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
s, err := New(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a good volume so we have a plugin reference
|
||||||
|
_, err = s.Create("fake1", d.Name(), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now create another one expecting an error
|
||||||
|
_, err = s.Create("fake2", d.Name(), map[string]string{"error": "some error"}, nil)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "some error") {
|
||||||
|
t.Fatalf("expected an error on create: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// There should be only 1 plugin reference
|
||||||
|
if refs := volumetestutils.FakeRefs(p); refs != 1 {
|
||||||
|
t.Fatalf("expected 1 plugin reference, got: %d", refs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
package testutils
|
package testutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/plugingetter"
|
||||||
|
"github.com/docker/docker/pkg/plugins"
|
||||||
"github.com/docker/docker/volume"
|
"github.com/docker/docker/volume"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -121,3 +127,99 @@ func (d *FakeDriver) Get(name string) (volume.Volume, error) {
|
||||||
func (*FakeDriver) Scope() string {
|
func (*FakeDriver) Scope() string {
|
||||||
return "local"
|
return "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakePlugin struct {
|
||||||
|
client *plugins.Client
|
||||||
|
name string
|
||||||
|
refs int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeFakePlugin creates a fake plugin from the passed in driver
|
||||||
|
// Note: currently only "Create" is implemented because that's all that's needed
|
||||||
|
// so far. If you need it to test something else, add it here, but probably you
|
||||||
|
// shouldn't need to use this except for very specific cases with v2 plugin handling.
|
||||||
|
func MakeFakePlugin(d volume.Driver, l net.Listener) (plugingetter.CompatPlugin, error) {
|
||||||
|
c, err := plugins.NewClient(l.Addr().Network()+"://"+l.Addr().String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
createReq := struct {
|
||||||
|
Name string
|
||||||
|
Opts map[string]string
|
||||||
|
}{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&createReq); err != nil {
|
||||||
|
fmt.Fprintf(w, `{"Err": "%s"}`, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := d.Create(createReq.Name, createReq.Opts)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, `{"Err": "%s"}`, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write([]byte("{}"))
|
||||||
|
})
|
||||||
|
|
||||||
|
go http.Serve(l, mux)
|
||||||
|
return &fakePlugin{client: c, name: d.Name()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fakePlugin) Client() *plugins.Client {
|
||||||
|
return p.client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fakePlugin) Name() string {
|
||||||
|
return p.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fakePlugin) IsV1() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fakePlugin) BasePath() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakePluginGetter struct {
|
||||||
|
plugins map[string]plugingetter.CompatPlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFakePluginGetter returns a plugin getter for fake plugins
|
||||||
|
func NewFakePluginGetter(pls ...plugingetter.CompatPlugin) plugingetter.PluginGetter {
|
||||||
|
idx := make(map[string]plugingetter.CompatPlugin, len(pls))
|
||||||
|
for _, p := range pls {
|
||||||
|
idx[p.Name()] = p
|
||||||
|
}
|
||||||
|
return &fakePluginGetter{plugins: idx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This ignores the second argument since we only care about volume drivers here,
|
||||||
|
// there shouldn't be any other kind of plugin in here
|
||||||
|
func (g *fakePluginGetter) Get(name, _ string, mode int) (plugingetter.CompatPlugin, error) {
|
||||||
|
p, ok := g.plugins[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
p.(*fakePlugin).refs += mode
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *fakePluginGetter) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
|
||||||
|
panic("GetAllByCap shouldn't be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *fakePluginGetter) GetAllManagedPluginsByCap(capability string) []plugingetter.CompatPlugin {
|
||||||
|
panic("GetAllManagedPluginsByCap should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *fakePluginGetter) Handle(capability string, callback func(string, *plugins.Client)) {
|
||||||
|
panic("Handle should not be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FakeRefs checks ref count on a fake plugin.
|
||||||
|
func FakeRefs(p plugingetter.CompatPlugin) int {
|
||||||
|
// this should panic if something other than a `*fakePlugin` is passed in
|
||||||
|
return p.(*fakePlugin).refs
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue