mirror of
https://github.com/clearlinux/docker.git
synced 2026-09-06 21:51:33 +00:00
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>
This commit is contained in:
@@ -97,3 +97,12 @@ func Lookup(name string) (volume.Driver, error) {
|
||||
drivers.extensions[name] = d
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// GetDriver returns a volume driver by it's name.
|
||||
// If the driver is empty, it looks for the local driver.
|
||||
func GetDriver(name string) (volume.Driver, error) {
|
||||
if name == "" {
|
||||
name = volume.DefaultDriverName
|
||||
}
|
||||
return Lookup(name)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package volumedrivers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/volume/testutils"
|
||||
)
|
||||
|
||||
func TestGetDriver(t *testing.T) {
|
||||
_, err := GetDriver("missing")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
Register(volumetestutils.FakeDriver{}, "fake")
|
||||
d, err := GetDriver("fake")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.Name() != "fake" {
|
||||
t.Fatalf("Expected fake driver, got %s\n", d.Name())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container
|
||||
ErrVolumeInUse = errors.New("volume is in use")
|
||||
// ErrNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
|
||||
ErrNoSuchVolume = errors.New("no such volume")
|
||||
)
|
||||
|
||||
// New initializes a VolumeStore to keep
|
||||
// reference counting of volumes in the system.
|
||||
func New() *VolumeStore {
|
||||
return &VolumeStore{
|
||||
vols: make(map[string]*volumeCounter),
|
||||
}
|
||||
}
|
||||
|
||||
// VolumeStore is a struct that stores the list of volumes available and keeps track of their usage counts
|
||||
type VolumeStore struct {
|
||||
vols map[string]*volumeCounter
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// volumeCounter keeps track of references to a volume
|
||||
type volumeCounter struct {
|
||||
volume.Volume
|
||||
count uint
|
||||
}
|
||||
|
||||
// AddAll adds a list of volumes to the store
|
||||
func (s *VolumeStore) AddAll(vols []volume.Volume) {
|
||||
for _, v := range vols {
|
||||
s.vols[v.Name()] = &volumeCounter{v, 0}
|
||||
}
|
||||
}
|
||||
|
||||
// Create tries to find an existing volume with the given name or create a new one from the passed in driver
|
||||
func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
||||
s.mu.Lock()
|
||||
if vc, exists := s.vols[name]; exists {
|
||||
v := vc.Volume
|
||||
s.mu.Unlock()
|
||||
return v, nil
|
||||
}
|
||||
s.mu.Unlock()
|
||||
logrus.Debugf("Registering new volume reference: driver %s, name %s", driverName, name)
|
||||
|
||||
vd, err := volumedrivers.GetDriver(driverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := vd.Create(name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.vols[v.Name()] = &volumeCounter{v, 0}
|
||||
s.mu.Unlock()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Get looks if a volume with the given name exists and returns it if so
|
||||
func (s *VolumeStore) Get(name string) (volume.Volume, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
vc, exists := s.vols[name]
|
||||
if !exists {
|
||||
return nil, ErrNoSuchVolume
|
||||
}
|
||||
return vc.Volume, nil
|
||||
}
|
||||
|
||||
// Remove removes the requested volume. A volume is not removed if the usage count is > 0
|
||||
func (s *VolumeStore) Remove(v volume.Volume) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
name := v.Name()
|
||||
logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
|
||||
vc, exists := s.vols[name]
|
||||
if !exists {
|
||||
return ErrNoSuchVolume
|
||||
}
|
||||
|
||||
if vc.count > 0 {
|
||||
return ErrVolumeInUse
|
||||
}
|
||||
|
||||
vd, err := volumedrivers.GetDriver(vc.DriverName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := vd.Remove(vc.Volume); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(s.vols, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Increment increments the usage count of the passed in volume by 1
|
||||
func (s *VolumeStore) Increment(v volume.Volume) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
|
||||
|
||||
vc, exists := s.vols[v.Name()]
|
||||
if !exists {
|
||||
s.vols[v.Name()] = &volumeCounter{v, 1}
|
||||
return
|
||||
}
|
||||
vc.count++
|
||||
}
|
||||
|
||||
// Decrement decrements the usage count of the passed in volume by 1
|
||||
func (s *VolumeStore) Decrement(v volume.Volume) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
|
||||
|
||||
vc, exists := s.vols[v.Name()]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
if vc.count == 0 {
|
||||
return
|
||||
}
|
||||
vc.count--
|
||||
}
|
||||
|
||||
// Count returns the usage count of the passed in volume
|
||||
func (s *VolumeStore) Count(v volume.Volume) uint {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
vc, exists := s.vols[v.Name()]
|
||||
if !exists {
|
||||
return 0
|
||||
}
|
||||
return vc.count
|
||||
}
|
||||
|
||||
// List returns all the available volumes
|
||||
func (s *VolumeStore) List() []volume.Volume {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var ls []volume.Volume
|
||||
for _, vc := range s.vols {
|
||||
ls = append(ls, vc.Volume)
|
||||
}
|
||||
return ls
|
||||
}
|
||||
|
||||
// FilterByDriver returns the available volumes filtered by driver name
|
||||
func (s *VolumeStore) FilterByDriver(name string) []volume.Volume {
|
||||
return s.filter(byDriver(name))
|
||||
}
|
||||
|
||||
// filterFunc defines a function to allow filter volumes in the store
|
||||
type filterFunc func(vol volume.Volume) bool
|
||||
|
||||
// byDriver generates a filterFunc to filter volumes by their driver name
|
||||
func byDriver(name string) filterFunc {
|
||||
return func(vol volume.Volume) bool {
|
||||
return vol.DriverName() == name
|
||||
}
|
||||
}
|
||||
|
||||
// filter returns the available volumes filtered by a filterFunc function
|
||||
func (s *VolumeStore) filter(f filterFunc) []volume.Volume {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var ls []volume.Volume
|
||||
for _, vc := range s.vols {
|
||||
if f(vc.Volume) {
|
||||
ls = append(ls, vc.Volume)
|
||||
}
|
||||
}
|
||||
return ls
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
vt "github.com/docker/docker/volume/testutils"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
volumedrivers.Register(vt.FakeDriver{}, "fake")
|
||||
s := New()
|
||||
s.AddAll([]volume.Volume{vt.NewFakeVolume("fake1"), vt.NewFakeVolume("fake2")})
|
||||
l := s.List()
|
||||
if len(l) != 2 {
|
||||
t.Fatalf("Expected 2 volumes in the store, got %v: %v", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
volumedrivers.Register(vt.FakeDriver{}, "fake")
|
||||
s := New()
|
||||
s.AddAll([]volume.Volume{vt.NewFakeVolume("fake1"), vt.NewFakeVolume("fake2")})
|
||||
v, err := s.Get("fake1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Name() != "fake1" {
|
||||
t.Fatalf("Expected fake1 volume, got %v", v)
|
||||
}
|
||||
|
||||
if _, err := s.Get("fake4"); err != ErrNoSuchVolume {
|
||||
t.Fatalf("Expected ErrNoSuchVolume error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
volumedrivers.Register(vt.FakeDriver{}, "fake")
|
||||
s := New()
|
||||
v, err := s.Create("fake1", "fake", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Name() != "fake1" {
|
||||
t.Fatalf("Expected fake1 volume, got %v", v)
|
||||
}
|
||||
if l := s.List(); len(l) != 1 {
|
||||
t.Fatalf("Expected 1 volume in the store, got %v: %v", len(l), l)
|
||||
}
|
||||
|
||||
if _, err := s.Create("none", "none", nil); err == nil {
|
||||
t.Fatalf("Expected unknown driver error, got nil")
|
||||
}
|
||||
|
||||
_, err = s.Create("fakeError", "fake", map[string]string{"error": "create error"})
|
||||
if err == nil || err.Error() != "create error" {
|
||||
t.Fatalf("Expected create error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
volumedrivers.Register(vt.FakeDriver{}, "fake")
|
||||
s := New()
|
||||
if err := s.Remove(vt.NoopVolume{}); err != ErrNoSuchVolume {
|
||||
t.Fatalf("Expected ErrNoSuchVolume error, got %v", err)
|
||||
}
|
||||
v, err := s.Create("fake1", "fake", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.Increment(v)
|
||||
if err := s.Remove(v); err != ErrVolumeInUse {
|
||||
t.Fatalf("Expected ErrVolumeInUse error, got %v", err)
|
||||
}
|
||||
s.Decrement(v)
|
||||
if err := s.Remove(v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l := s.List(); len(l) != 0 {
|
||||
t.Fatalf("Expected 0 volumes in the store, got %v, %v", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrement(t *testing.T) {
|
||||
s := New()
|
||||
v := vt.NewFakeVolume("fake1")
|
||||
s.Increment(v)
|
||||
if l := s.List(); len(l) != 1 {
|
||||
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
||||
}
|
||||
if c := s.Count(v); c != 1 {
|
||||
t.Fatalf("Expected 1 counter, got %v", c)
|
||||
}
|
||||
|
||||
s.Increment(v)
|
||||
if l := s.List(); len(l) != 1 {
|
||||
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
||||
}
|
||||
if c := s.Count(v); c != 2 {
|
||||
t.Fatalf("Expected 2 counter, got %v", c)
|
||||
}
|
||||
|
||||
v2 := vt.NewFakeVolume("fake2")
|
||||
s.Increment(v2)
|
||||
if l := s.List(); len(l) != 2 {
|
||||
t.Fatalf("Expected 2 volume, got %v, %v", len(l), l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrement(t *testing.T) {
|
||||
s := New()
|
||||
v := vt.NoopVolume{}
|
||||
s.Decrement(v)
|
||||
if c := s.Count(v); c != 0 {
|
||||
t.Fatalf("Expected 0 volumes, got %v", c)
|
||||
}
|
||||
|
||||
s.Increment(v)
|
||||
s.Increment(v)
|
||||
s.Decrement(v)
|
||||
if c := s.Count(v); c != 1 {
|
||||
t.Fatalf("Expected 1 volume, got %v", c)
|
||||
}
|
||||
|
||||
s.Decrement(v)
|
||||
if c := s.Count(v); c != 0 {
|
||||
t.Fatalf("Expected 0 volumes, got %v", c)
|
||||
}
|
||||
|
||||
// Test counter cannot be negative.
|
||||
s.Decrement(v)
|
||||
if c := s.Count(v); c != 0 {
|
||||
t.Fatalf("Expected 0 volumes, got %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterByDriver(t *testing.T) {
|
||||
s := New()
|
||||
|
||||
s.Increment(vt.NewFakeVolume("fake1"))
|
||||
s.Increment(vt.NewFakeVolume("fake2"))
|
||||
s.Increment(vt.NoopVolume{})
|
||||
|
||||
if l := s.FilterByDriver("fake"); len(l) != 2 {
|
||||
t.Fatalf("Expected 2 volumes, got %v, %v", len(l), l)
|
||||
}
|
||||
|
||||
if l := s.FilterByDriver("noop"); len(l) != 1 {
|
||||
t.Fatalf("Expected 1 volume, got %v, %v", len(l), l)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package volumetestutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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, error) { return "noop", nil }
|
||||
|
||||
// Unmount unmounts the volume from the container
|
||||
func (NoopVolume) Unmount() error { return nil }
|
||||
|
||||
// FakeVolume is a fake volume with a random name
|
||||
type FakeVolume struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// NewFakeVolume creates a new fake volume for testing
|
||||
func NewFakeVolume(name string) volume.Volume {
|
||||
return FakeVolume{name: name}
|
||||
}
|
||||
|
||||
// Name is the name of the volume
|
||||
func (f FakeVolume) Name() string { return f.name }
|
||||
|
||||
// DriverName is the name of the driver
|
||||
func (FakeVolume) DriverName() string { return "fake" }
|
||||
|
||||
// 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, error) { return "fake", nil }
|
||||
|
||||
// Unmount unmounts the volume from the container
|
||||
func (FakeVolume) Unmount() error { return nil }
|
||||
|
||||
// FakeDriver is a driver that generates fake volumes
|
||||
type FakeDriver struct{}
|
||||
|
||||
// Name is the name of the driver
|
||||
func (FakeDriver) Name() string { return "fake" }
|
||||
|
||||
// Create initializes a fake volume.
|
||||
// It returns an error if the options include an "error" key with a message
|
||||
func (FakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) {
|
||||
if opts != nil && opts["error"] != "" {
|
||||
return nil, fmt.Errorf(opts["error"])
|
||||
}
|
||||
return NewFakeVolume(name), nil
|
||||
}
|
||||
|
||||
// Remove deletes a volume.
|
||||
func (FakeDriver) Remove(v volume.Volume) error { return nil }
|
||||
Reference in New Issue
Block a user