Browse code

Move volume ref counting store to a package.

- Add unit tests to make sure the functionality is correct.
- Add FilterByDriver to allow filtering volumes by driver, for future
`volume ls` filtering and whatnot.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2015/09/19 08:58:05
Showing 11 changed files
... ...
@@ -27,6 +27,7 @@ import (
27 27
 	"github.com/docker/docker/runconfig"
28 28
 	"github.com/docker/docker/utils"
29 29
 	"github.com/docker/docker/volume"
30
+	"github.com/docker/docker/volume/store"
30 31
 	"github.com/docker/libnetwork"
31 32
 	"github.com/docker/libnetwork/netlabel"
32 33
 	"github.com/docker/libnetwork/options"
... ...
@@ -1225,7 +1226,7 @@ func (container *Container) removeMountPoints(rm bool) error {
1225 1225
 			// not an error, but an implementation detail.
1226 1226
 			// This prevents docker from logging "ERROR: Volume in use"
1227 1227
 			// where there is another container using the volume.
1228
-			if err != nil && err != ErrVolumeInUse {
1228
+			if err != nil && err != store.ErrVolumeInUse {
1229 1229
 				rmErrors = append(rmErrors, err.Error())
1230 1230
 			}
1231 1231
 		}
... ...
@@ -49,6 +49,7 @@ import (
49 49
 	"github.com/docker/docker/trust"
50 50
 	volumedrivers "github.com/docker/docker/volume/drivers"
51 51
 	"github.com/docker/docker/volume/local"
52
+	"github.com/docker/docker/volume/store"
52 53
 	"github.com/docker/libnetwork"
53 54
 	"github.com/opencontainers/runc/libcontainer/netlink"
54 55
 )
... ...
@@ -114,7 +115,7 @@ type Daemon struct {
114 114
 	RegistryService  *registry.Service
115 115
 	EventsService    *events.Events
116 116
 	netController    libnetwork.NetworkController
117
-	volumes          *volumeStore
117
+	volumes          *store.VolumeStore
118 118
 	root             string
119 119
 	shutdown         bool
120 120
 }
... ...
@@ -1121,11 +1122,15 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
1121 1121
 	return verifyPlatformContainerSettings(daemon, hostConfig, config)
1122 1122
 }
1123 1123
 
1124
-func configureVolumes(config *Config) (*volumeStore, error) {
1124
+func configureVolumes(config *Config) (*store.VolumeStore, error) {
1125 1125
 	volumesDriver, err := local.New(config.Root)
1126 1126
 	if err != nil {
1127 1127
 		return nil, err
1128 1128
 	}
1129
+
1129 1130
 	volumedrivers.Register(volumesDriver, volumesDriver.Name())
1130
-	return newVolumeStore(volumesDriver.List()), nil
1131
+	s := store.New()
1132
+	s.AddAll(volumesDriver.List())
1133
+
1134
+	return s, nil
1131 1135
 }
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"github.com/docker/docker/volume"
16 16
 	volumedrivers "github.com/docker/docker/volume/drivers"
17 17
 	"github.com/docker/docker/volume/local"
18
+	"github.com/docker/docker/volume/store"
18 19
 )
19 20
 
20 21
 //
... ...
@@ -505,7 +506,7 @@ func initDaemonForVolumesTest(tmp string) (*Daemon, error) {
505 505
 	daemon := &Daemon{
506 506
 		repository: tmp,
507 507
 		root:       tmp,
508
-		volumes:    newVolumeStore([]volume.Volume{}),
508
+		volumes:    store.New(),
509 509
 	}
510 510
 
511 511
 	volumesDriver, err := local.New(tmp)
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"path"
7 7
 
8 8
 	"github.com/Sirupsen/logrus"
9
+	"github.com/docker/docker/volume/store"
9 10
 )
10 11
 
11 12
 // ContainerRmConfig is a holder for passing in runtime config.
... ...
@@ -152,7 +153,7 @@ func (daemon *Daemon) VolumeRm(name string) error {
152 152
 		return err
153 153
 	}
154 154
 	if err := daemon.volumes.Remove(v); err != nil {
155
-		if err == ErrVolumeInUse {
155
+		if err == store.ErrVolumeInUse {
156 156
 			return fmt.Errorf("Conflict: %v", err)
157 157
 		}
158 158
 		return fmt.Errorf("Error while removing volume %s: %v", name, err)
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"os"
8 8
 	"path/filepath"
9 9
 	"strings"
10
-	"sync"
11 10
 
12 11
 	"github.com/Sirupsen/logrus"
13 12
 	"github.com/docker/docker/api/types"
... ...
@@ -15,17 +14,12 @@ import (
15 15
 	"github.com/docker/docker/pkg/chrootarchive"
16 16
 	"github.com/docker/docker/pkg/system"
17 17
 	"github.com/docker/docker/volume"
18
-	"github.com/docker/docker/volume/drivers"
19 18
 )
20 19
 
21 20
 var (
22 21
 	// ErrVolumeReadonly is used to signal an error when trying to copy data into
23 22
 	// a volume mount that is not writable.
24 23
 	ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
25
-	// ErrVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container
26
-	ErrVolumeInUse = errors.New("volume is in use")
27
-	// ErrNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
28
-	ErrNoSuchVolume = errors.New("no such volume")
29 24
 )
30 25
 
31 26
 // mountPoint is the intersection point between a volume and a container. It
... ...
@@ -105,148 +99,6 @@ func copyExistingContents(source, destination string) error {
105 105
 	return copyOwnership(source, destination)
106 106
 }
107 107
 
108
-func newVolumeStore(vols []volume.Volume) *volumeStore {
109
-	store := &volumeStore{
110
-		vols: make(map[string]*volumeCounter),
111
-	}
112
-	for _, v := range vols {
113
-		store.vols[v.Name()] = &volumeCounter{v, 0}
114
-	}
115
-	return store
116
-}
117
-
118
-// volumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
119
-type volumeStore struct {
120
-	vols map[string]*volumeCounter
121
-	mu   sync.Mutex
122
-}
123
-
124
-type volumeCounter struct {
125
-	volume.Volume
126
-	count int
127
-}
128
-
129
-func getVolumeDriver(name string) (volume.Driver, error) {
130
-	if name == "" {
131
-		name = volume.DefaultDriverName
132
-	}
133
-	return volumedrivers.Lookup(name)
134
-}
135
-
136
-// Create tries to find an existing volume with the given name or create a new one from the passed in driver
137
-func (s *volumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
138
-	s.mu.Lock()
139
-	if vc, exists := s.vols[name]; exists {
140
-		v := vc.Volume
141
-		s.mu.Unlock()
142
-		return v, nil
143
-	}
144
-	s.mu.Unlock()
145
-	logrus.Debugf("Registering new volume reference: driver %s, name %s", driverName, name)
146
-
147
-	vd, err := getVolumeDriver(driverName)
148
-	if err != nil {
149
-		return nil, err
150
-	}
151
-
152
-	v, err := vd.Create(name, opts)
153
-	if err != nil {
154
-		return nil, err
155
-	}
156
-
157
-	s.mu.Lock()
158
-	s.vols[v.Name()] = &volumeCounter{v, 0}
159
-	s.mu.Unlock()
160
-
161
-	return v, nil
162
-}
163
-
164
-// Get looks if a volume with the given name exists and returns it if so
165
-func (s *volumeStore) Get(name string) (volume.Volume, error) {
166
-	s.mu.Lock()
167
-	defer s.mu.Unlock()
168
-	vc, exists := s.vols[name]
169
-	if !exists {
170
-		return nil, ErrNoSuchVolume
171
-	}
172
-	return vc.Volume, nil
173
-}
174
-
175
-// Remove removes the requested volume. A volume is not removed if the usage count is > 0
176
-func (s *volumeStore) Remove(v volume.Volume) error {
177
-	s.mu.Lock()
178
-	defer s.mu.Unlock()
179
-	name := v.Name()
180
-	logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
181
-	vc, exists := s.vols[name]
182
-	if !exists {
183
-		return ErrNoSuchVolume
184
-	}
185
-
186
-	if vc.count != 0 {
187
-		return ErrVolumeInUse
188
-	}
189
-
190
-	vd, err := getVolumeDriver(vc.DriverName())
191
-	if err != nil {
192
-		return err
193
-	}
194
-	if err := vd.Remove(vc.Volume); err != nil {
195
-		return err
196
-	}
197
-	delete(s.vols, name)
198
-	return nil
199
-}
200
-
201
-// Increment increments the usage count of the passed in volume by 1
202
-func (s *volumeStore) Increment(v volume.Volume) {
203
-	s.mu.Lock()
204
-	defer s.mu.Unlock()
205
-	logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
206
-
207
-	vc, exists := s.vols[v.Name()]
208
-	if !exists {
209
-		s.vols[v.Name()] = &volumeCounter{v, 1}
210
-		return
211
-	}
212
-	vc.count++
213
-}
214
-
215
-// Decrement decrements the usage count of the passed in volume by 1
216
-func (s *volumeStore) Decrement(v volume.Volume) {
217
-	s.mu.Lock()
218
-	defer s.mu.Unlock()
219
-	logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
220
-
221
-	vc, exists := s.vols[v.Name()]
222
-	if !exists {
223
-		return
224
-	}
225
-	vc.count--
226
-}
227
-
228
-// Count returns the usage count of the passed in volume
229
-func (s *volumeStore) Count(v volume.Volume) int {
230
-	s.mu.Lock()
231
-	defer s.mu.Unlock()
232
-	vc, exists := s.vols[v.Name()]
233
-	if !exists {
234
-		return 0
235
-	}
236
-	return vc.count
237
-}
238
-
239
-// List returns all the available volumes
240
-func (s *volumeStore) List() []volume.Volume {
241
-	s.mu.Lock()
242
-	defer s.mu.Unlock()
243
-	var ls []volume.Volume
244
-	for _, vc := range s.vols {
245
-		ls = append(ls, vc.Volume)
246
-	}
247
-	return ls
248
-}
249
-
250 108
 // volumeToAPIType converts a volume.Volume to the type used by the remote API
251 109
 func volumeToAPIType(v volume.Volume) *types.Volume {
252 110
 	return &types.Volume{
... ...
@@ -2,34 +2,7 @@
2 2
 
3 3
 package daemon
4 4
 
5
-import (
6
-	"testing"
7
-
8
-	"github.com/docker/docker/volume"
9
-	"github.com/docker/docker/volume/drivers"
10
-)
11
-
12
-type fakeDriver struct{}
13
-
14
-func (fakeDriver) Name() string                                                      { return "fake" }
15
-func (fakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) { return nil, nil }
16
-func (fakeDriver) Remove(v volume.Volume) error                                      { return nil }
17
-
18
-func TestGetVolumeDriver(t *testing.T) {
19
-	_, err := getVolumeDriver("missing")
20
-	if err == nil {
21
-		t.Fatal("Expected error, was nil")
22
-	}
23
-
24
-	volumedrivers.Register(fakeDriver{}, "fake")
25
-	d, err := getVolumeDriver("fake")
26
-	if err != nil {
27
-		t.Fatal(err)
28
-	}
29
-	if d.Name() != "fake" {
30
-		t.Fatalf("Expected fake driver, got %s\n", d.Name())
31
-	}
32
-}
5
+import "testing"
33 6
 
34 7
 func TestParseBindMount(t *testing.T) {
35 8
 	cases := []struct {
... ...
@@ -97,3 +97,12 @@ func Lookup(name string) (volume.Driver, error) {
97 97
 	drivers.extensions[name] = d
98 98
 	return d, nil
99 99
 }
100
+
101
+// GetDriver returns a volume driver by it's name.
102
+// If the driver is empty, it looks for the local driver.
103
+func GetDriver(name string) (volume.Driver, error) {
104
+	if name == "" {
105
+		name = volume.DefaultDriverName
106
+	}
107
+	return Lookup(name)
108
+}
100 109
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package volumedrivers
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/volume/testutils"
6
+)
7
+
8
+func TestGetDriver(t *testing.T) {
9
+	_, err := GetDriver("missing")
10
+	if err == nil {
11
+		t.Fatal("Expected error, was nil")
12
+	}
13
+
14
+	Register(volumetestutils.FakeDriver{}, "fake")
15
+	d, err := GetDriver("fake")
16
+	if err != nil {
17
+		t.Fatal(err)
18
+	}
19
+	if d.Name() != "fake" {
20
+		t.Fatalf("Expected fake driver, got %s\n", d.Name())
21
+	}
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,189 @@
0
+package store
1
+
2
+import (
3
+	"errors"
4
+	"sync"
5
+
6
+	"github.com/Sirupsen/logrus"
7
+	"github.com/docker/docker/volume"
8
+	"github.com/docker/docker/volume/drivers"
9
+)
10
+
11
+var (
12
+	// ErrVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container
13
+	ErrVolumeInUse = errors.New("volume is in use")
14
+	// ErrNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
15
+	ErrNoSuchVolume = errors.New("no such volume")
16
+)
17
+
18
+// New initializes a VolumeStore to keep
19
+// reference counting of volumes in the system.
20
+func New() *VolumeStore {
21
+	return &VolumeStore{
22
+		vols: make(map[string]*volumeCounter),
23
+	}
24
+}
25
+
26
+// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
27
+type VolumeStore struct {
28
+	vols map[string]*volumeCounter
29
+	mu   sync.Mutex
30
+}
31
+
32
+// volumeCounter keeps track of references to a volume
33
+type volumeCounter struct {
34
+	volume.Volume
35
+	count uint
36
+}
37
+
38
+// AddAll adds a list of volumes to the store
39
+func (s *VolumeStore) AddAll(vols []volume.Volume) {
40
+	for _, v := range vols {
41
+		s.vols[v.Name()] = &volumeCounter{v, 0}
42
+	}
43
+}
44
+
45
+// Create tries to find an existing volume with the given name or create a new one from the passed in driver
46
+func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
47
+	s.mu.Lock()
48
+	if vc, exists := s.vols[name]; exists {
49
+		v := vc.Volume
50
+		s.mu.Unlock()
51
+		return v, nil
52
+	}
53
+	s.mu.Unlock()
54
+	logrus.Debugf("Registering new volume reference: driver %s, name %s", driverName, name)
55
+
56
+	vd, err := volumedrivers.GetDriver(driverName)
57
+	if err != nil {
58
+		return nil, err
59
+	}
60
+
61
+	v, err := vd.Create(name, opts)
62
+	if err != nil {
63
+		return nil, err
64
+	}
65
+
66
+	s.mu.Lock()
67
+	s.vols[v.Name()] = &volumeCounter{v, 0}
68
+	s.mu.Unlock()
69
+
70
+	return v, nil
71
+}
72
+
73
+// Get looks if a volume with the given name exists and returns it if so
74
+func (s *VolumeStore) Get(name string) (volume.Volume, error) {
75
+	s.mu.Lock()
76
+	defer s.mu.Unlock()
77
+	vc, exists := s.vols[name]
78
+	if !exists {
79
+		return nil, ErrNoSuchVolume
80
+	}
81
+	return vc.Volume, nil
82
+}
83
+
84
+// Remove removes the requested volume. A volume is not removed if the usage count is > 0
85
+func (s *VolumeStore) Remove(v volume.Volume) error {
86
+	s.mu.Lock()
87
+	defer s.mu.Unlock()
88
+	name := v.Name()
89
+	logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
90
+	vc, exists := s.vols[name]
91
+	if !exists {
92
+		return ErrNoSuchVolume
93
+	}
94
+
95
+	if vc.count > 0 {
96
+		return ErrVolumeInUse
97
+	}
98
+
99
+	vd, err := volumedrivers.GetDriver(vc.DriverName())
100
+	if err != nil {
101
+		return err
102
+	}
103
+	if err := vd.Remove(vc.Volume); err != nil {
104
+		return err
105
+	}
106
+	delete(s.vols, name)
107
+	return nil
108
+}
109
+
110
+// Increment increments the usage count of the passed in volume by 1
111
+func (s *VolumeStore) Increment(v volume.Volume) {
112
+	s.mu.Lock()
113
+	defer s.mu.Unlock()
114
+	logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
115
+
116
+	vc, exists := s.vols[v.Name()]
117
+	if !exists {
118
+		s.vols[v.Name()] = &volumeCounter{v, 1}
119
+		return
120
+	}
121
+	vc.count++
122
+}
123
+
124
+// Decrement decrements the usage count of the passed in volume by 1
125
+func (s *VolumeStore) Decrement(v volume.Volume) {
126
+	s.mu.Lock()
127
+	defer s.mu.Unlock()
128
+	logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
129
+
130
+	vc, exists := s.vols[v.Name()]
131
+	if !exists {
132
+		return
133
+	}
134
+	if vc.count == 0 {
135
+		return
136
+	}
137
+	vc.count--
138
+}
139
+
140
+// Count returns the usage count of the passed in volume
141
+func (s *VolumeStore) Count(v volume.Volume) uint {
142
+	s.mu.Lock()
143
+	defer s.mu.Unlock()
144
+	vc, exists := s.vols[v.Name()]
145
+	if !exists {
146
+		return 0
147
+	}
148
+	return vc.count
149
+}
150
+
151
+// List returns all the available volumes
152
+func (s *VolumeStore) List() []volume.Volume {
153
+	s.mu.Lock()
154
+	defer s.mu.Unlock()
155
+	var ls []volume.Volume
156
+	for _, vc := range s.vols {
157
+		ls = append(ls, vc.Volume)
158
+	}
159
+	return ls
160
+}
161
+
162
+// FilterByDriver returns the available volumes filtered by driver name
163
+func (s *VolumeStore) FilterByDriver(name string) []volume.Volume {
164
+	return s.filter(byDriver(name))
165
+}
166
+
167
+// filterFunc defines a function to allow filter volumes in the store
168
+type filterFunc func(vol volume.Volume) bool
169
+
170
+// byDriver generates a filterFunc to filter volumes by their driver name
171
+func byDriver(name string) filterFunc {
172
+	return func(vol volume.Volume) bool {
173
+		return vol.DriverName() == name
174
+	}
175
+}
176
+
177
+// filter returns the available volumes filtered by a filterFunc function
178
+func (s *VolumeStore) filter(f filterFunc) []volume.Volume {
179
+	s.mu.Lock()
180
+	defer s.mu.Unlock()
181
+	var ls []volume.Volume
182
+	for _, vc := range s.vols {
183
+		if f(vc.Volume) {
184
+			ls = append(ls, vc.Volume)
185
+		}
186
+	}
187
+	return ls
188
+}
0 189
new file mode 100644
... ...
@@ -0,0 +1,152 @@
0
+package store
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/volume"
6
+	"github.com/docker/docker/volume/drivers"
7
+	vt "github.com/docker/docker/volume/testutils"
8
+)
9
+
10
+func TestList(t *testing.T) {
11
+	volumedrivers.Register(vt.FakeDriver{}, "fake")
12
+	s := New()
13
+	s.AddAll([]volume.Volume{vt.NewFakeVolume("fake1"), vt.NewFakeVolume("fake2")})
14
+	l := s.List()
15
+	if len(l) != 2 {
16
+		t.Fatalf("Expected 2 volumes in the store, got %v: %v", len(l), l)
17
+	}
18
+}
19
+
20
+func TestGet(t *testing.T) {
21
+	volumedrivers.Register(vt.FakeDriver{}, "fake")
22
+	s := New()
23
+	s.AddAll([]volume.Volume{vt.NewFakeVolume("fake1"), vt.NewFakeVolume("fake2")})
24
+	v, err := s.Get("fake1")
25
+	if err != nil {
26
+		t.Fatal(err)
27
+	}
28
+	if v.Name() != "fake1" {
29
+		t.Fatalf("Expected fake1 volume, got %v", v)
30
+	}
31
+
32
+	if _, err := s.Get("fake4"); err != ErrNoSuchVolume {
33
+		t.Fatalf("Expected ErrNoSuchVolume error, got %v", err)
34
+	}
35
+}
36
+
37
+func TestCreate(t *testing.T) {
38
+	volumedrivers.Register(vt.FakeDriver{}, "fake")
39
+	s := New()
40
+	v, err := s.Create("fake1", "fake", nil)
41
+	if err != nil {
42
+		t.Fatal(err)
43
+	}
44
+	if v.Name() != "fake1" {
45
+		t.Fatalf("Expected fake1 volume, got %v", v)
46
+	}
47
+	if l := s.List(); len(l) != 1 {
48
+		t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
49
+	}
50
+
51
+	if _, err := s.Create("none", "none", nil); err == nil {
52
+		t.Fatalf("Expected unknown driver error, got nil")
53
+	}
54
+
55
+	_, err = s.Create("fakeError", "fake", map[string]string{"error": "create error"})
56
+	if err == nil || err.Error() != "create error" {
57
+		t.Fatalf("Expected create error, got %v", err)
58
+	}
59
+}
60
+
61
+func TestRemove(t *testing.T) {
62
+	volumedrivers.Register(vt.FakeDriver{}, "fake")
63
+	s := New()
64
+	if err := s.Remove(vt.NoopVolume{}); err != ErrNoSuchVolume {
65
+		t.Fatalf("Expected ErrNoSuchVolume error, got %v", err)
66
+	}
67
+	v, err := s.Create("fake1", "fake", nil)
68
+	if err != nil {
69
+		t.Fatal(err)
70
+	}
71
+	s.Increment(v)
72
+	if err := s.Remove(v); err != ErrVolumeInUse {
73
+		t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
74
+	}
75
+	s.Decrement(v)
76
+	if err := s.Remove(v); err != nil {
77
+		t.Fatal(err)
78
+	}
79
+	if l := s.List(); len(l) != 0 {
80
+		t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
81
+	}
82
+}
83
+
84
+func TestIncrement(t *testing.T) {
85
+	s := New()
86
+	v := vt.NewFakeVolume("fake1")
87
+	s.Increment(v)
88
+	if l := s.List(); len(l) != 1 {
89
+		t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
90
+	}
91
+	if c := s.Count(v); c != 1 {
92
+		t.Fatalf("Expected 1 counter, got %v", c)
93
+	}
94
+
95
+	s.Increment(v)
96
+	if l := s.List(); len(l) != 1 {
97
+		t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
98
+	}
99
+	if c := s.Count(v); c != 2 {
100
+		t.Fatalf("Expected 2 counter, got %v", c)
101
+	}
102
+
103
+	v2 := vt.NewFakeVolume("fake2")
104
+	s.Increment(v2)
105
+	if l := s.List(); len(l) != 2 {
106
+		t.Fatalf("Expected 2 volume, got %v, %v", len(l), l)
107
+	}
108
+}
109
+
110
+func TestDecrement(t *testing.T) {
111
+	s := New()
112
+	v := vt.NoopVolume{}
113
+	s.Decrement(v)
114
+	if c := s.Count(v); c != 0 {
115
+		t.Fatalf("Expected 0 volumes, got %v", c)
116
+	}
117
+
118
+	s.Increment(v)
119
+	s.Increment(v)
120
+	s.Decrement(v)
121
+	if c := s.Count(v); c != 1 {
122
+		t.Fatalf("Expected 1 volume, got %v", c)
123
+	}
124
+
125
+	s.Decrement(v)
126
+	if c := s.Count(v); c != 0 {
127
+		t.Fatalf("Expected 0 volumes, got %v", c)
128
+	}
129
+
130
+	// Test counter cannot be negative.
131
+	s.Decrement(v)
132
+	if c := s.Count(v); c != 0 {
133
+		t.Fatalf("Expected 0 volumes, got %v", c)
134
+	}
135
+}
136
+
137
+func TestFilterByDriver(t *testing.T) {
138
+	s := New()
139
+
140
+	s.Increment(vt.NewFakeVolume("fake1"))
141
+	s.Increment(vt.NewFakeVolume("fake2"))
142
+	s.Increment(vt.NoopVolume{})
143
+
144
+	if l := s.FilterByDriver("fake"); len(l) != 2 {
145
+		t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
146
+	}
147
+
148
+	if l := s.FilterByDriver("noop"); len(l) != 1 {
149
+		t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
150
+	}
151
+}
0 152
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+package volumetestutils
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/docker/docker/volume"
6
+)
7
+
8
+// NoopVolume is a volume that doesn't perform any operation
9
+type NoopVolume struct{}
10
+
11
+// Name is the name of the volume
12
+func (NoopVolume) Name() string { return "noop" }
13
+
14
+// DriverName is the name of the driver
15
+func (NoopVolume) DriverName() string { return "noop" }
16
+
17
+// Path is the filesystem path to the volume
18
+func (NoopVolume) Path() string { return "noop" }
19
+
20
+// Mount mounts the volume in the container
21
+func (NoopVolume) Mount() (string, error) { return "noop", nil }
22
+
23
+// Unmount unmounts the volume from the container
24
+func (NoopVolume) Unmount() error { return nil }
25
+
26
+// FakeVolume is a fake volume with a random name
27
+type FakeVolume struct {
28
+	name string
29
+}
30
+
31
+// NewFakeVolume creates a new fake volume for testing
32
+func NewFakeVolume(name string) volume.Volume {
33
+	return FakeVolume{name: name}
34
+}
35
+
36
+// Name is the name of the volume
37
+func (f FakeVolume) Name() string { return f.name }
38
+
39
+// DriverName is the name of the driver
40
+func (FakeVolume) DriverName() string { return "fake" }
41
+
42
+// Path is the filesystem path to the volume
43
+func (FakeVolume) Path() string { return "fake" }
44
+
45
+// Mount mounts the volume in the container
46
+func (FakeVolume) Mount() (string, error) { return "fake", nil }
47
+
48
+// Unmount unmounts the volume from the container
49
+func (FakeVolume) Unmount() error { return nil }
50
+
51
+// FakeDriver is a driver that generates fake volumes
52
+type FakeDriver struct{}
53
+
54
+// Name is the name of the driver
55
+func (FakeDriver) Name() string { return "fake" }
56
+
57
+// Create initializes a fake volume.
58
+// It returns an error if the options include an "error" key with a message
59
+func (FakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) {
60
+	if opts != nil && opts["error"] != "" {
61
+		return nil, fmt.Errorf(opts["error"])
62
+	}
63
+	return NewFakeVolume(name), nil
64
+}
65
+
66
+// Remove deletes a volume.
67
+func (FakeDriver) Remove(v volume.Volume) error { return nil }