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> Upstream-commit: 72bb56618b522fc3cece7cfd706c56296824673d Component: engine
This commit is contained in:
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/store"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/netlabel"
|
||||
"github.com/docker/libnetwork/options"
|
||||
@@ -1225,7 +1226,7 @@ func (container *Container) removeMountPoints(rm bool) error {
|
||||
// not an error, but an implementation detail.
|
||||
// This prevents docker from logging "ERROR: Volume in use"
|
||||
// where there is another container using the volume.
|
||||
if err != nil && err != ErrVolumeInUse {
|
||||
if err != nil && err != store.ErrVolumeInUse {
|
||||
rmErrors = append(rmErrors, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import (
|
||||
"github.com/docker/docker/trust"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/local"
|
||||
"github.com/docker/docker/volume/store"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/opencontainers/runc/libcontainer/netlink"
|
||||
)
|
||||
@@ -114,7 +115,7 @@ type Daemon struct {
|
||||
RegistryService *registry.Service
|
||||
EventsService *events.Events
|
||||
netController libnetwork.NetworkController
|
||||
volumes *volumeStore
|
||||
volumes *store.VolumeStore
|
||||
root string
|
||||
shutdown bool
|
||||
}
|
||||
@@ -1121,11 +1122,15 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
|
||||
return verifyPlatformContainerSettings(daemon, hostConfig, config)
|
||||
}
|
||||
|
||||
func configureVolumes(config *Config) (*volumeStore, error) {
|
||||
func configureVolumes(config *Config) (*store.VolumeStore, error) {
|
||||
volumesDriver, err := local.New(config.Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumedrivers.Register(volumesDriver, volumesDriver.Name())
|
||||
return newVolumeStore(volumesDriver.List()), nil
|
||||
s := store.New()
|
||||
s.AddAll(volumesDriver.List())
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/docker/docker/volume"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/local"
|
||||
"github.com/docker/docker/volume/store"
|
||||
)
|
||||
|
||||
//
|
||||
@@ -505,7 +506,7 @@ func initDaemonForVolumesTest(tmp string) (*Daemon, error) {
|
||||
daemon := &Daemon{
|
||||
repository: tmp,
|
||||
root: tmp,
|
||||
volumes: newVolumeStore([]volume.Volume{}),
|
||||
volumes: store.New(),
|
||||
}
|
||||
|
||||
volumesDriver, err := local.New(tmp)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/volume/store"
|
||||
)
|
||||
|
||||
// ContainerRmConfig is a holder for passing in runtime config.
|
||||
@@ -152,7 +153,7 @@ func (daemon *Daemon) VolumeRm(name string) error {
|
||||
return err
|
||||
}
|
||||
if err := daemon.volumes.Remove(v); err != nil {
|
||||
if err == ErrVolumeInUse {
|
||||
if err == store.ErrVolumeInUse {
|
||||
return fmt.Errorf("Conflict: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Error while removing volume %s: %v", name, err)
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -15,17 +14,12 @@ import (
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrVolumeReadonly is used to signal an error when trying to copy data into
|
||||
// a volume mount that is not writable.
|
||||
ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
|
||||
// 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")
|
||||
)
|
||||
|
||||
// mountPoint is the intersection point between a volume and a container. It
|
||||
@@ -105,148 +99,6 @@ func copyExistingContents(source, destination string) error {
|
||||
return copyOwnership(source, destination)
|
||||
}
|
||||
|
||||
func newVolumeStore(vols []volume.Volume) *volumeStore {
|
||||
store := &volumeStore{
|
||||
vols: make(map[string]*volumeCounter),
|
||||
}
|
||||
for _, v := range vols {
|
||||
store.vols[v.Name()] = &volumeCounter{v, 0}
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type volumeCounter struct {
|
||||
volume.Volume
|
||||
count int
|
||||
}
|
||||
|
||||
func getVolumeDriver(name string) (volume.Driver, error) {
|
||||
if name == "" {
|
||||
name = volume.DefaultDriverName
|
||||
}
|
||||
return volumedrivers.Lookup(name)
|
||||
}
|
||||
|
||||
// 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 := getVolumeDriver(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 := getVolumeDriver(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
|
||||
}
|
||||
vc.count--
|
||||
}
|
||||
|
||||
// Count returns the usage count of the passed in volume
|
||||
func (s *volumeStore) Count(v volume.Volume) int {
|
||||
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
|
||||
}
|
||||
|
||||
// volumeToAPIType converts a volume.Volume to the type used by the remote API
|
||||
func volumeToAPIType(v volume.Volume) *types.Volume {
|
||||
return &types.Volume{
|
||||
|
||||
@@ -2,34 +2,7 @@
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
)
|
||||
|
||||
type fakeDriver struct{}
|
||||
|
||||
func (fakeDriver) Name() string { return "fake" }
|
||||
func (fakeDriver) Create(name string, opts map[string]string) (volume.Volume, error) { return nil, nil }
|
||||
func (fakeDriver) Remove(v volume.Volume) error { return nil }
|
||||
|
||||
func TestGetVolumeDriver(t *testing.T) {
|
||||
_, err := getVolumeDriver("missing")
|
||||
if err == nil {
|
||||
t.Fatal("Expected error, was nil")
|
||||
}
|
||||
|
||||
volumedrivers.Register(fakeDriver{}, "fake")
|
||||
d, err := getVolumeDriver("fake")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.Name() != "fake" {
|
||||
t.Fatalf("Expected fake driver, got %s\n", d.Name())
|
||||
}
|
||||
}
|
||||
import "testing"
|
||||
|
||||
func TestParseBindMount(t *testing.T) {
|
||||
cases := []struct {
|
||||
|
||||
Reference in New Issue
Block a user