summaryrefslogtreecommitdiff
path: root/volume/testutils/testutils.go
blob: 624f76089c4fe1b9c002a6a90562d494328c6bd7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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
	createdAt  time.Time
}

// NewFakeVolume creates a new fake volume for testing
func NewFakeVolume(name string, driverName string) volume.Volume {
	return FakeVolume{name: name, driverName: driverName, createdAt: time.Now()}
}

// 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 map[string]interface{}{"datakey": "datavalue"}
}

// CreatedAt provides the time the volume (directory) was created at
func (f FakeVolume) CreatedAt() (time.Time, error) {
	return f.createdAt, 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) // #nosec G114 -- Ignoring for test-code: G114: Use of net/http serve function that has no support for setting timeouts (gosec)
	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
}