Files
docker-cli/components/engine/volume/testutils/testutils.go
Brian Goff 90450b2044 Ensure plugin returns correctly scoped paths
Before this change, volume management was relying on the fact that
everything the plugin mounts is visible on the host within the plugin's
rootfs. In practice this caused some issues with mount leaks, so we
changed the behavior such that mounts are not visible on the plugin's
rootfs, but available outside of it, which breaks volume management.

To fix the issue, allow the plugin to scope the path correctly rather
than assuming that everything is visible in `p.Rootfs`.
In practice this is just scoping the `PropagatedMount` paths to the
correct host path.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Upstream-commit: 0e5eaf8ee32662182147f5f62c1bfebef66f5c47
Component: engine
2018-02-07 15:48:27 -05:00

226 lines
6.2 KiB
Go

package testutils
import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"time"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/volume"
)
// NoopVolume is a volume that doesn't perform any operation
type NoopVolume struct{}
// Name is the name of the volume
func (NoopVolume) Name() string { return "noop" }
// DriverName is the name of the driver
func (NoopVolume) DriverName() string { return "noop" }
// Path is the filesystem path to the volume
func (NoopVolume) Path() string { return "noop" }
// Mount mounts the volume in the container
func (NoopVolume) Mount(_ string) (string, error) { return "noop", nil }
// Unmount unmounts the volume from the container
func (NoopVolume) Unmount(_ string) error { return nil }
// Status provides low-level details about the volume
func (NoopVolume) Status() map[string]interface{} { return nil }
// CreatedAt provides the time the volume (directory) was created at
func (NoopVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
// FakeVolume is a fake volume with a random name
type FakeVolume struct {
name string
driverName string
}
// NewFakeVolume creates a new fake volume for testing
func NewFakeVolume(name string, driverName string) volume.Volume {
return FakeVolume{name: name, driverName: driverName}
}
// Name is the name of the volume
func (f FakeVolume) Name() string { return f.name }
// DriverName is the name of the driver
func (f FakeVolume) DriverName() string { return f.driverName }
// Path is the filesystem path to the volume
func (FakeVolume) Path() string { return "fake" }
// Mount mounts the volume in the container
func (FakeVolume) Mount(_ string) (string, error) { return "fake", nil }
// Unmount unmounts the volume from the container
func (FakeVolume) Unmount(_ string) error { return nil }
// Status provides low-level details about the volume
func (FakeVolume) Status() map[string]interface{} { return nil }
// CreatedAt provides the time the volume (directory) was created at
func (FakeVolume) CreatedAt() (time.Time, error) { return time.Now(), nil }
// FakeDriver is a driver that generates fake volumes
type FakeDriver struct {
name string
vols map[string]volume.Volume
}
// NewFakeDriver creates a new FakeDriver with the specified name
func NewFakeDriver(name string) volume.Driver {
return &FakeDriver{
name: name,
vols: make(map[string]volume.Volume),
}
}
// Name is the name of the driver
func (d *FakeDriver) Name() string { return d.name }
// Create initializes a fake volume.
// It returns an error if the options include an "error" key with a message
func (d *FakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) {
if opts != nil && opts["error"] != "" {
return nil, fmt.Errorf(opts["error"])
}
v := NewFakeVolume(name, d.name)
d.vols[name] = v
return v, nil
}
// Remove deletes a volume.
func (d *FakeDriver) Remove(v volume.Volume) error {
if _, exists := d.vols[v.Name()]; !exists {
return fmt.Errorf("no such volume")
}
delete(d.vols, v.Name())
return nil
}
// List lists the volumes
func (d *FakeDriver) List() ([]volume.Volume, error) {
var vols []volume.Volume
for _, v := range d.vols {
vols = append(vols, v)
}
return vols, nil
}
// Get gets the volume
func (d *FakeDriver) Get(name string) (volume.Volume, error) {
if v, exists := d.vols[name]; exists {
return v, nil
}
return nil, fmt.Errorf("no such volume")
}
// Scope returns the local scope
func (*FakeDriver) Scope() string {
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) ScopedPath(s string) string {
return s
}
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
}