package testutils // import "github.com/docker/docker/volume/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 }