Merge pull request #14242 from cpuguy83/add_volume_api
Add volume api Upstream-commit: fa13f7cde81d1d92c17740efb05823e7f9cc5468 Component: engine
This commit is contained in:
@@ -1179,7 +1179,7 @@ func (container *Container) isDestinationMounted(destination string) bool {
|
||||
func (container *Container) prepareMountPoints() error {
|
||||
for _, config := range container.MountPoints {
|
||||
if len(config.Driver) > 0 {
|
||||
v, err := createVolume(config.Name, config.Driver)
|
||||
v, err := container.daemon.createVolume(config.Name, config.Driver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1189,13 +1189,22 @@ func (container *Container) prepareMountPoints() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) removeMountPoints() error {
|
||||
func (container *Container) removeMountPoints(rm bool) error {
|
||||
var rmErrors []string
|
||||
for _, m := range container.MountPoints {
|
||||
if m.Volume != nil {
|
||||
if err := removeVolume(m.Volume); err != nil {
|
||||
return err
|
||||
if m.Volume == nil {
|
||||
continue
|
||||
}
|
||||
container.daemon.volumes.Decrement(m.Volume)
|
||||
if rm {
|
||||
if err := container.daemon.volumes.Remove(m.Volume); err != nil {
|
||||
rmErrors = append(rmErrors, fmt.Sprintf("%v\n", err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(rmErrors) > 0 {
|
||||
return fmt.Errorf("Error removing volumes:\n%v", rmErrors)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -169,6 +169,6 @@ func (container *Container) prepareMountPoints() error {
|
||||
}
|
||||
|
||||
// removeMountPoints is a no-op on Windows.
|
||||
func (container *Container) removeMountPoints() error {
|
||||
func (container *Container) removeMountPoints(_ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/graph/tags"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
@@ -124,3 +126,17 @@ func (daemon *Daemon) GenerateSecurityOpt(ipcMode runconfig.IpcMode, pidMode run
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// VolumeCreate creates a volume with the specified name, driver, and opts
|
||||
// This is called directly from the remote API
|
||||
func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]string) (*types.Volume, error) {
|
||||
if name == "" {
|
||||
name = stringid.GenerateNonCryptoID()
|
||||
}
|
||||
|
||||
v, err := daemon.volumes.Create(name, driverName, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumeToAPIType(v), nil
|
||||
}
|
||||
|
||||
@@ -54,10 +54,11 @@ func createContainerPlatformSpecificSettings(container *Container, config *runco
|
||||
}
|
||||
}
|
||||
|
||||
v, err := createVolume(name, volumeDriver)
|
||||
v, err := container.daemon.createVolume(name, volumeDriver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := label.Relabel(v.Path(), container.MountLabel, "z"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ type Daemon struct {
|
||||
RegistryService *registry.Service
|
||||
EventsService *events.Events
|
||||
netController libnetwork.NetworkController
|
||||
volumes *volumeStore
|
||||
root string
|
||||
shutdown bool
|
||||
}
|
||||
@@ -653,7 +654,8 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
|
||||
}
|
||||
|
||||
// Configure the volumes driver
|
||||
if err := configureVolumes(config); err != nil {
|
||||
volStore, err := configureVolumes(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -740,6 +742,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
|
||||
d.defaultLogConfig = config.LogConfig
|
||||
d.RegistryService = registryService
|
||||
d.EventsService = eventsService
|
||||
d.volumes = volStore
|
||||
d.root = config.Root
|
||||
go d.execCommandGC()
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/local"
|
||||
)
|
||||
|
||||
@@ -486,12 +486,12 @@ func TestRemoveLocalVolumesFollowingSymlinks(t *testing.T) {
|
||||
}
|
||||
|
||||
m := c.MountPoints["/vol1"]
|
||||
v, err := createVolume(m.Name, m.Driver)
|
||||
_, err = daemon.VolumeCreate(m.Name, m.Driver, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := removeVolume(v); err != nil {
|
||||
if err := daemon.VolumeRm(m.Name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -505,6 +505,7 @@ func initDaemonForVolumesTest(tmp string) (*Daemon, error) {
|
||||
daemon := &Daemon{
|
||||
repository: tmp,
|
||||
root: tmp,
|
||||
volumes: newVolumeStore([]volume.Volume{}),
|
||||
}
|
||||
|
||||
volumesDriver, err := local.New(tmp)
|
||||
|
||||
@@ -256,13 +256,13 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
|
||||
return migrateIfAufs(driver, root)
|
||||
}
|
||||
|
||||
func configureVolumes(config *Config) error {
|
||||
func configureVolumes(config *Config) (*volumeStore, error) {
|
||||
volumesDriver, err := local.New(config.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
volumedrivers.Register(volumesDriver, volumesDriver.Name())
|
||||
return nil
|
||||
return newVolumeStore(volumesDriver.List()), nil
|
||||
}
|
||||
|
||||
func configureSysInit(config *Config) (string, error) {
|
||||
|
||||
@@ -74,9 +74,9 @@ func migrateIfDownlevel(driver graphdriver.Driver, root string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureVolumes(config *Config) error {
|
||||
func configureVolumes(config *Config) (*volumeStore, error) {
|
||||
// Windows does not support volumes at this time
|
||||
return nil
|
||||
return &volumeStore{}, nil
|
||||
}
|
||||
|
||||
func configureSysInit(config *Config) (string, error) {
|
||||
|
||||
@@ -50,9 +50,7 @@ func (daemon *Daemon) ContainerRm(name string, config *ContainerRmConfig) error
|
||||
return fmt.Errorf("Cannot destroy container %s: %v", name, err)
|
||||
}
|
||||
|
||||
if config.RemoveVolume {
|
||||
container.removeMountPoints()
|
||||
}
|
||||
container.removeMountPoints(config.RemoveVolume)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -137,6 +135,19 @@ func (daemon *Daemon) rm(container *Container, forceRemove bool) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) DeleteVolumes(c *Container) error {
|
||||
return c.removeMountPoints()
|
||||
// VolumeRm removes the volume with the given name.
|
||||
// If the volume is referenced by a container it is not removed
|
||||
// This is called directly from the remote API
|
||||
func (daemon *Daemon) VolumeRm(name string) error {
|
||||
v, err := daemon.volumes.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := daemon.volumes.Remove(v); err != nil {
|
||||
if err == ErrVolumeInUse {
|
||||
return fmt.Errorf("Conflict: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -97,3 +97,11 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) {
|
||||
}
|
||||
return eConfig, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
|
||||
v, err := daemon.volumes.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumeToAPIType(v), nil
|
||||
}
|
||||
|
||||
@@ -214,3 +214,32 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container,
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
|
||||
var volumesOut []*types.Volume
|
||||
volFilters, err := filters.FromParam(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filterUsed := false
|
||||
if i, ok := volFilters["dangling"]; ok {
|
||||
if len(i) > 1 {
|
||||
return nil, fmt.Errorf("Conflict: cannot use more than 1 value for `dangling` filter")
|
||||
}
|
||||
|
||||
filterValue := i[0]
|
||||
if strings.ToLower(filterValue) == "true" || filterValue == "1" {
|
||||
filterUsed = true
|
||||
}
|
||||
}
|
||||
|
||||
volumes := daemon.volumes.List()
|
||||
for _, v := range volumes {
|
||||
if filterUsed && daemon.volumes.Count(v) == 0 {
|
||||
continue
|
||||
}
|
||||
volumesOut = append(volumesOut, volumeToAPIType(v))
|
||||
}
|
||||
return volumesOut, nil
|
||||
}
|
||||
|
||||
@@ -7,15 +7,24 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// ErrVolumeReadonly is used to signal an error when trying to copy data into
|
||||
// a volume mount that is not writable.
|
||||
var ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
|
||||
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
|
||||
@@ -92,3 +101,144 @@ 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()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if vc, exists := s.vols[name]; exists {
|
||||
return vc.Volume, nil
|
||||
}
|
||||
|
||||
vd, err := getVolumeDriver(driverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := vd.Create(name, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.vols[v.Name()] = &volumeCounter{v, 0}
|
||||
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()
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
|
||||
type fakeDriver struct{}
|
||||
|
||||
func (fakeDriver) Name() string { return "fake" }
|
||||
func (fakeDriver) Create(name string) (volume.Volume, error) { return nil, nil }
|
||||
func (fakeDriver) Remove(v volume.Volume) error { return nil }
|
||||
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")
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/volume"
|
||||
"github.com/docker/docker/volume/drivers"
|
||||
volumedrivers "github.com/docker/docker/volume/drivers"
|
||||
"github.com/docker/docker/volume/local"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
)
|
||||
@@ -138,7 +138,7 @@ func (m mounts) parts(i int) int {
|
||||
// It preserves the volume json configuration generated pre Docker 1.7 to be able to
|
||||
// downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility.
|
||||
func migrateVolume(id, vfs string) error {
|
||||
l, err := getVolumeDriver(volume.DefaultDriverName)
|
||||
l, err := volumedrivers.Lookup(volume.DefaultDriverName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
|
||||
// Volumes created with a Docker version >= 1.7. We verify integrity in case of data created
|
||||
// with Docker 1.7 RC versions that put the information in
|
||||
// DOCKER_ROOT/volumes/VOLUME_ID rather than DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
|
||||
l, err := getVolumeDriver(volume.DefaultDriverName)
|
||||
l, err := volumedrivers.Lookup(volume.DefaultDriverName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -311,7 +311,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
||||
}
|
||||
|
||||
if len(cp.Source) == 0 {
|
||||
v, err := createVolume(cp.Name, cp.Driver)
|
||||
v, err := daemon.createVolume(cp.Name, cp.Driver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -336,7 +336,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
||||
|
||||
if len(bind.Name) > 0 && len(bind.Driver) > 0 {
|
||||
// create the volume
|
||||
v, err := createVolume(bind.Name, bind.Driver)
|
||||
v, err := daemon.createVolume(bind.Name, bind.Driver, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -362,6 +362,11 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
||||
if m.BackwardsCompatible() {
|
||||
bcVolumes[m.Destination] = m.Path()
|
||||
bcVolumesRW[m.Destination] = m.RW
|
||||
|
||||
// This mountpoint is replacing an existing one, so the count needs to be decremented
|
||||
if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
|
||||
daemon.volumes.Decrement(mp.Volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,29 +380,13 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
|
||||
}
|
||||
|
||||
// createVolume creates a volume.
|
||||
func createVolume(name, driverName string) (volume.Volume, error) {
|
||||
vd, err := getVolumeDriver(driverName)
|
||||
func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
|
||||
v, err := daemon.volumes.Create(name, driverName, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return vd.Create(name)
|
||||
}
|
||||
|
||||
// removeVolume removes a volume.
|
||||
func removeVolume(v volume.Volume) error {
|
||||
vd, err := getVolumeDriver(v.DriverName())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return vd.Remove(v)
|
||||
}
|
||||
|
||||
// getVolumeDriver returns the volume driver for the supplied name.
|
||||
func getVolumeDriver(name string) (volume.Driver, error) {
|
||||
if name == "" {
|
||||
name = volume.DefaultDriverName
|
||||
}
|
||||
return volumedrivers.Lookup(name)
|
||||
daemon.volumes.Increment(v)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// parseVolumeSource parses the origin sources that's mounted into the container.
|
||||
|
||||
Reference in New Issue
Block a user