github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/volume/volume.go (about)

     1  package volume
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"syscall"
     9  	"time"
    10  
    11  	mounttypes "github.com/docker/docker/api/types/mount"
    12  	"github.com/docker/docker/pkg/idtools"
    13  	"github.com/docker/docker/pkg/stringid"
    14  	"github.com/opencontainers/selinux/go-selinux/label"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // DefaultDriverName is the driver name used for the driver
    19  // implemented in the local package.
    20  const DefaultDriverName = "local"
    21  
    22  // Scopes define if a volume has is cluster-wide (global) or local only.
    23  // Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume
    24  const (
    25  	LocalScope  = "local"
    26  	GlobalScope = "global"
    27  )
    28  
    29  // Driver is for creating and removing volumes.
    30  type Driver interface {
    31  	// Name returns the name of the volume driver.
    32  	Name() string
    33  	// Create makes a new volume with the given name.
    34  	Create(name string, opts map[string]string) (Volume, error)
    35  	// Remove deletes the volume.
    36  	Remove(vol Volume) (err error)
    37  	// List lists all the volumes the driver has
    38  	List() ([]Volume, error)
    39  	// Get retrieves the volume with the requested name
    40  	Get(name string) (Volume, error)
    41  	// Scope returns the scope of the driver (e.g. `global` or `local`).
    42  	// Scope determines how the driver is handled at a cluster level
    43  	Scope() string
    44  }
    45  
    46  // Capability defines a set of capabilities that a driver is able to handle.
    47  type Capability struct {
    48  	// Scope is the scope of the driver, `global` or `local`
    49  	// A `global` scope indicates that the driver manages volumes across the cluster
    50  	// A `local` scope indicates that the driver only manages volumes resources local to the host
    51  	// Scope is declared by the driver
    52  	Scope string
    53  }
    54  
    55  // Volume is a place to store data. It is backed by a specific driver, and can be mounted.
    56  type Volume interface {
    57  	// Name returns the name of the volume
    58  	Name() string
    59  	// DriverName returns the name of the driver which owns this volume.
    60  	DriverName() string
    61  	// Path returns the absolute path to the volume.
    62  	Path() string
    63  	// Mount mounts the volume and returns the absolute path to
    64  	// where it can be consumed.
    65  	Mount(id string) (string, error)
    66  	// Unmount unmounts the volume when it is no longer in use.
    67  	Unmount(id string) error
    68  	// CreatedAt returns Volume Creation time
    69  	CreatedAt() (time.Time, error)
    70  	// Status returns low-level status information about a volume
    71  	Status() map[string]interface{}
    72  }
    73  
    74  // DetailedVolume wraps a Volume with user-defined labels, options, and cluster scope (e.g., `local` or `global`)
    75  type DetailedVolume interface {
    76  	Labels() map[string]string
    77  	Options() map[string]string
    78  	Scope() string
    79  	Volume
    80  }
    81  
    82  // MountPoint is the intersection point between a volume and a container. It
    83  // specifies which volume is to be used and where inside a container it should
    84  // be mounted.
    85  type MountPoint struct {
    86  	// Source is the source path of the mount.
    87  	// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
    88  	Source string
    89  	// Destination is the path relative to the container root (`/`) to the mount point
    90  	// It is where the `Source` is mounted to
    91  	Destination string
    92  	// RW is set to true when the mountpoint should be mounted as read-write
    93  	RW bool
    94  	// Name is the name reference to the underlying data defined by `Source`
    95  	// e.g., the volume name
    96  	Name string
    97  	// Driver is the volume driver used to create the volume (if it is a volume)
    98  	Driver string
    99  	// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
   100  	Type mounttypes.Type `json:",omitempty"`
   101  	// Volume is the volume providing data to this mountpoint.
   102  	// This is nil unless `Type` is set to `TypeVolume`
   103  	Volume Volume `json:"-"`
   104  
   105  	// Mode is the comma separated list of options supplied by the user when creating
   106  	// the bind/volume mount.
   107  	// Note Mode is not used on Windows
   108  	Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
   109  
   110  	// Propagation describes how the mounts are propagated from the host into the
   111  	// mount point, and vice-versa.
   112  	// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
   113  	// Note Propagation is not used on Windows
   114  	Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
   115  
   116  	// Specifies if data should be copied from the container before the first mount
   117  	// Use a pointer here so we can tell if the user set this value explicitly
   118  	// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
   119  	CopyData bool `json:"-"`
   120  	// ID is the opaque ID used to pass to the volume driver.
   121  	// This should be set by calls to `Mount` and unset by calls to `Unmount`
   122  	ID string `json:",omitempty"`
   123  
   124  	// Sepc is a copy of the API request that created this mount.
   125  	Spec mounttypes.Mount
   126  
   127  	// Track usage of this mountpoint
   128  	// Specicially needed for containers which are running and calls to `docker cp`
   129  	// because both these actions require mounting the volumes.
   130  	active int
   131  }
   132  
   133  // Cleanup frees resources used by the mountpoint
   134  func (m *MountPoint) Cleanup() error {
   135  	if m.Volume == nil || m.ID == "" {
   136  		return nil
   137  	}
   138  
   139  	if err := m.Volume.Unmount(m.ID); err != nil {
   140  		return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
   141  	}
   142  
   143  	m.active--
   144  	if m.active == 0 {
   145  		m.ID = ""
   146  	}
   147  	return nil
   148  }
   149  
   150  // Setup sets up a mount point by either mounting the volume if it is
   151  // configured, or creating the source directory if supplied.
   152  // The, optional, checkFun parameter allows doing additional checking
   153  // before creating the source directory on the host.
   154  func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) {
   155  	defer func() {
   156  		if err == nil {
   157  			if label.RelabelNeeded(m.Mode) {
   158  				if err = label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)); err != nil {
   159  					path = ""
   160  					err = errors.Wrapf(err, "error setting label on mount source '%s'", m.Source)
   161  					return
   162  				}
   163  			}
   164  		}
   165  		return
   166  	}()
   167  
   168  	if m.Volume != nil {
   169  		id := m.ID
   170  		if id == "" {
   171  			id = stringid.GenerateNonCryptoID()
   172  		}
   173  		path, err := m.Volume.Mount(id)
   174  		if err != nil {
   175  			return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
   176  		}
   177  
   178  		m.ID = id
   179  		m.active++
   180  		return path, nil
   181  	}
   182  
   183  	if len(m.Source) == 0 {
   184  		return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
   185  	}
   186  
   187  	// system.MkdirAll() produces an error if m.Source exists and is a file (not a directory),
   188  	if m.Type == mounttypes.TypeBind {
   189  		// Before creating the source directory on the host, invoke checkFun if it's not nil. One of
   190  		// the use case is to forbid creating the daemon socket as a directory if the daemon is in
   191  		// the process of shutting down.
   192  		if checkFun != nil {
   193  			if err := checkFun(m); err != nil {
   194  				return "", err
   195  			}
   196  		}
   197  		// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
   198  		// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
   199  		if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
   200  			if perr, ok := err.(*os.PathError); ok {
   201  				if perr.Err != syscall.ENOTDIR {
   202  					return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
   203  				}
   204  			}
   205  		}
   206  	}
   207  	return m.Source, nil
   208  }
   209  
   210  // Path returns the path of a volume in a mount point.
   211  func (m *MountPoint) Path() string {
   212  	if m.Volume != nil {
   213  		return m.Volume.Path()
   214  	}
   215  	return m.Source
   216  }
   217  
   218  // ParseVolumesFrom ensures that the supplied volumes-from is valid.
   219  func ParseVolumesFrom(spec string) (string, string, error) {
   220  	if len(spec) == 0 {
   221  		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
   222  	}
   223  
   224  	specParts := strings.SplitN(spec, ":", 2)
   225  	id := specParts[0]
   226  	mode := "rw"
   227  
   228  	if len(specParts) == 2 {
   229  		mode = specParts[1]
   230  		if !ValidMountMode(mode) {
   231  			return "", "", errInvalidMode(mode)
   232  		}
   233  		// For now don't allow propagation properties while importing
   234  		// volumes from data container. These volumes will inherit
   235  		// the same propagation property as of the original volume
   236  		// in data container. This probably can be relaxed in future.
   237  		if HasPropagation(mode) {
   238  			return "", "", errInvalidMode(mode)
   239  		}
   240  		// Do not allow copy modes on volumes-from
   241  		if _, isSet := getCopyMode(mode); isSet {
   242  			return "", "", errInvalidMode(mode)
   243  		}
   244  	}
   245  	return id, mode, nil
   246  }
   247  
   248  // ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a
   249  // structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to
   250  // validate the spec and create a MountPoint
   251  func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
   252  	arr, err := splitRawSpec(convertSlash(raw))
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	var spec mounttypes.Mount
   258  	var mode string
   259  	switch len(arr) {
   260  	case 1:
   261  		// Just a destination path in the container
   262  		spec.Target = arr[0]
   263  	case 2:
   264  		if ValidMountMode(arr[1]) {
   265  			// Destination + Mode is not a valid volume - volumes
   266  			// cannot include a mode. e.g. /foo:rw
   267  			return nil, errInvalidSpec(raw)
   268  		}
   269  		// Host Source Path or Name + Destination
   270  		spec.Source = arr[0]
   271  		spec.Target = arr[1]
   272  	case 3:
   273  		// HostSourcePath+DestinationPath+Mode
   274  		spec.Source = arr[0]
   275  		spec.Target = arr[1]
   276  		mode = arr[2]
   277  	default:
   278  		return nil, errInvalidSpec(raw)
   279  	}
   280  
   281  	if !ValidMountMode(mode) {
   282  		return nil, errInvalidMode(mode)
   283  	}
   284  
   285  	if filepath.IsAbs(spec.Source) {
   286  		spec.Type = mounttypes.TypeBind
   287  	} else {
   288  		spec.Type = mounttypes.TypeVolume
   289  	}
   290  
   291  	spec.ReadOnly = !ReadWrite(mode)
   292  
   293  	// cannot assume that if a volume driver is passed in that we should set it
   294  	if volumeDriver != "" && spec.Type == mounttypes.TypeVolume {
   295  		spec.VolumeOptions = &mounttypes.VolumeOptions{
   296  			DriverConfig: &mounttypes.Driver{Name: volumeDriver},
   297  		}
   298  	}
   299  
   300  	if copyData, isSet := getCopyMode(mode); isSet {
   301  		if spec.VolumeOptions == nil {
   302  			spec.VolumeOptions = &mounttypes.VolumeOptions{}
   303  		}
   304  		spec.VolumeOptions.NoCopy = !copyData
   305  	}
   306  	if HasPropagation(mode) {
   307  		spec.BindOptions = &mounttypes.BindOptions{
   308  			Propagation: GetPropagation(mode),
   309  		}
   310  	}
   311  
   312  	mp, err := ParseMountSpec(spec, platformRawValidationOpts...)
   313  	if mp != nil {
   314  		mp.Mode = mode
   315  	}
   316  	if err != nil {
   317  		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
   318  	}
   319  	return mp, err
   320  }
   321  
   322  // ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
   323  func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
   324  	if err := validateMountConfig(&cfg, options...); err != nil {
   325  		return nil, err
   326  	}
   327  	mp := &MountPoint{
   328  		RW:          !cfg.ReadOnly,
   329  		Destination: clean(convertSlash(cfg.Target)),
   330  		Type:        cfg.Type,
   331  		Spec:        cfg,
   332  	}
   333  
   334  	switch cfg.Type {
   335  	case mounttypes.TypeVolume:
   336  		if cfg.Source == "" {
   337  			mp.Name = stringid.GenerateNonCryptoID()
   338  		} else {
   339  			mp.Name = cfg.Source
   340  		}
   341  		mp.CopyData = DefaultCopyMode
   342  
   343  		if cfg.VolumeOptions != nil {
   344  			if cfg.VolumeOptions.DriverConfig != nil {
   345  				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
   346  			}
   347  			if cfg.VolumeOptions.NoCopy {
   348  				mp.CopyData = false
   349  			}
   350  		}
   351  	case mounttypes.TypeBind:
   352  		mp.Source = clean(convertSlash(cfg.Source))
   353  		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
   354  			mp.Propagation = cfg.BindOptions.Propagation
   355  		} else {
   356  			// If user did not specify a propagation mode, get
   357  			// default propagation mode.
   358  			mp.Propagation = DefaultPropagationMode
   359  		}
   360  	case mounttypes.TypeTmpfs:
   361  		// NOP
   362  	}
   363  	return mp, nil
   364  }
   365  
   366  func errInvalidMode(mode string) error {
   367  	return fmt.Errorf("invalid mode: %v", mode)
   368  }
   369  
   370  func errInvalidSpec(spec string) error {
   371  	return fmt.Errorf("invalid volume specification: '%s'", spec)
   372  }