2c6c07752c
Volumes are accounted when a container is created. If the creation fails we should remove the reference from the counter. Do not log ErrVolumeInUse as an error, having other volume references is not an error. Signed-off-by: David Calavera <david.calavera@gmail.com>
253 lines
6.4 KiB
Go
253 lines
6.4 KiB
Go
package daemon
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/api/types"
|
|
"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
|
|
// specifies which volume is to be used and where inside a container it should
|
|
// be mounted.
|
|
type mountPoint struct {
|
|
Name string
|
|
Destination string
|
|
Driver string
|
|
RW bool
|
|
Volume volume.Volume `json:"-"`
|
|
Source string
|
|
Mode string `json:"Relabel"` // Originally field was `Relabel`"
|
|
}
|
|
|
|
// Setup sets up a mount point by either mounting the volume if it is
|
|
// configured, or creating the source directory if supplied.
|
|
func (m *mountPoint) Setup() (string, error) {
|
|
if m.Volume != nil {
|
|
return m.Volume.Mount()
|
|
}
|
|
|
|
if len(m.Source) > 0 {
|
|
if _, err := os.Stat(m.Source); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return "", err
|
|
}
|
|
if err := system.MkdirAll(m.Source, 0755); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
return m.Source, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
|
}
|
|
|
|
// hasResource checks whether the given absolute path for a container is in
|
|
// this mount point. If the relative path starts with `../` then the resource
|
|
// is outside of this mount point, but we can't simply check for this prefix
|
|
// because it misses `..` which is also outside of the mount, so check both.
|
|
func (m *mountPoint) hasResource(absolutePath string) bool {
|
|
relPath, err := filepath.Rel(m.Destination, absolutePath)
|
|
|
|
return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
|
|
}
|
|
|
|
// Path returns the path of a volume in a mount point.
|
|
func (m *mountPoint) Path() string {
|
|
if m.Volume != nil {
|
|
return m.Volume.Path()
|
|
}
|
|
|
|
return m.Source
|
|
}
|
|
|
|
// copyExistingContents copies from the source to the destination and
|
|
// ensures the ownership is appropriately set.
|
|
func copyExistingContents(source, destination string) error {
|
|
volList, err := ioutil.ReadDir(source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(volList) > 0 {
|
|
srcList, err := ioutil.ReadDir(destination)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(srcList) == 0 {
|
|
// If the source volume is empty copy files from the root into the volume
|
|
if err := chrootarchive.CopyWithTar(source, destination); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
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++
|
|
return
|
|
}
|
|
|
|
// 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--
|
|
return
|
|
}
|
|
|
|
// Count returns the usage count of the passed in volume
|
|
func (s *volumeStore) Count(v volume.Volume) int {
|
|
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 {
|
|
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{
|
|
Name: v.Name(),
|
|
Driver: v.DriverName(),
|
|
Mountpoint: v.Path(),
|
|
}
|
|
}
|