github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/storage/provider/rootfs.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provider
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/storage"
    14  )
    15  
    16  const (
    17  	RootfsProviderType = storage.ProviderType("rootfs")
    18  )
    19  
    20  // rootfsProviders create storage sources which provide access to filesystems.
    21  type rootfsProvider struct {
    22  	// run is a function type used for running commands on the local machine.
    23  	run runCommandFunc
    24  }
    25  
    26  var (
    27  	_ storage.Provider = (*rootfsProvider)(nil)
    28  )
    29  
    30  // ValidateConfig is defined on the Provider interface.
    31  func (p *rootfsProvider) ValidateConfig(cfg *storage.Config) error {
    32  	// Rootfs provider has no configuration.
    33  	return nil
    34  }
    35  
    36  // validateFullConfig validates a fully-constructed storage config,
    37  // combining the user-specified config and any internally specified
    38  // config.
    39  func (p *rootfsProvider) validateFullConfig(cfg *storage.Config) error {
    40  	if err := p.ValidateConfig(cfg); err != nil {
    41  		return err
    42  	}
    43  	storageDir, ok := cfg.ValueString(storage.ConfigStorageDir)
    44  	if !ok || storageDir == "" {
    45  		return errors.New("storage directory not specified")
    46  	}
    47  	return nil
    48  }
    49  
    50  // VolumeSource is defined on the Provider interface.
    51  func (p *rootfsProvider) VolumeSource(providerConfig *storage.Config) (storage.VolumeSource, error) {
    52  	return nil, errors.NotSupportedf("volumes")
    53  }
    54  
    55  // FilesystemSource is defined on the Provider interface.
    56  func (p *rootfsProvider) FilesystemSource(sourceConfig *storage.Config) (storage.FilesystemSource, error) {
    57  	if err := p.validateFullConfig(sourceConfig); err != nil {
    58  		return nil, err
    59  	}
    60  	// storageDir is validated by validateFullConfig.
    61  	storageDir, _ := sourceConfig.ValueString(storage.ConfigStorageDir)
    62  	return &rootfsFilesystemSource{
    63  		&osDirFuncs{p.run},
    64  		p.run,
    65  		storageDir,
    66  	}, nil
    67  }
    68  
    69  // Supports is defined on the Provider interface.
    70  func (*rootfsProvider) Supports(k storage.StorageKind) bool {
    71  	return k == storage.StorageKindFilesystem
    72  }
    73  
    74  // Scope is defined on the Provider interface.
    75  func (*rootfsProvider) Scope() storage.Scope {
    76  	return storage.ScopeMachine
    77  }
    78  
    79  // Dynamic is defined on the Provider interface.
    80  func (*rootfsProvider) Dynamic() bool {
    81  	return true
    82  }
    83  
    84  // DefaultPools is defined on the Provider interface.
    85  func (*rootfsProvider) DefaultPools() []*storage.Config {
    86  	return nil
    87  }
    88  
    89  type rootfsFilesystemSource struct {
    90  	dirFuncs   dirFuncs
    91  	run        runCommandFunc
    92  	storageDir string
    93  }
    94  
    95  // ensureDir ensures the specified path is a directory, or
    96  // if it does not exist, that a directory can be created there.
    97  func ensureDir(d dirFuncs, path string) error {
    98  	// If path already exists, we check that it is empty.
    99  	// It is up to the storage provisioner to ensure that any
   100  	// shared storage constraints and attachments with the same
   101  	// path are validated etc. So the check here is more a sanity check.
   102  	fi, err := d.lstat(path)
   103  	if err == nil {
   104  		if !fi.IsDir() {
   105  			return errors.Errorf("path %q must be a directory", path)
   106  		}
   107  		return nil
   108  	}
   109  	if !os.IsNotExist(err) {
   110  		return errors.Trace(err)
   111  	}
   112  	if err := d.mkDirAll(path, 0755); err != nil {
   113  		return errors.Annotate(err, "could not create directory")
   114  	}
   115  	return nil
   116  }
   117  
   118  // ensureEmptyDir ensures the specified directory is empty.
   119  func ensureEmptyDir(d dirFuncs, path string) error {
   120  	fileCount, err := d.fileCount(path)
   121  	if err != nil {
   122  		return errors.Annotate(err, "could not read directory")
   123  	}
   124  	if fileCount > 0 {
   125  		return errors.Errorf("%q is not empty", path)
   126  	}
   127  	return nil
   128  }
   129  
   130  var _ storage.FilesystemSource = (*rootfsFilesystemSource)(nil)
   131  
   132  // ValidateFilesystemParams is defined on the FilesystemSource interface.
   133  func (s *rootfsFilesystemSource) ValidateFilesystemParams(params storage.FilesystemParams) error {
   134  	// ValidateFilesystemParams may be called on a machine other than the
   135  	// machine where the filesystem will be mounted, so we cannot check
   136  	// available size until we get to CreateFilesystem.
   137  	return nil
   138  }
   139  
   140  // CreateFilesystems is defined on the FilesystemSource interface.
   141  func (s *rootfsFilesystemSource) CreateFilesystems(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) {
   142  	results := make([]storage.CreateFilesystemsResult, len(args))
   143  	for i, arg := range args {
   144  		filesystem, err := s.createFilesystem(arg)
   145  		if err != nil {
   146  			results[i].Error = err
   147  			continue
   148  		}
   149  		results[i].Filesystem = filesystem
   150  	}
   151  	return results, nil
   152  }
   153  
   154  func (s *rootfsFilesystemSource) createFilesystem(params storage.FilesystemParams) (*storage.Filesystem, error) {
   155  	if err := s.ValidateFilesystemParams(params); err != nil {
   156  		return nil, errors.Trace(err)
   157  	}
   158  	path := filepath.Join(s.storageDir, params.Tag.Id())
   159  	if err := ensureDir(s.dirFuncs, path); err != nil {
   160  		return nil, errors.Trace(err)
   161  	}
   162  	if err := ensureEmptyDir(s.dirFuncs, path); err != nil {
   163  		return nil, errors.Trace(err)
   164  	}
   165  	sizeInMiB, err := s.dirFuncs.calculateSize(s.storageDir)
   166  	if err != nil {
   167  		os.Remove(path)
   168  		return nil, errors.Trace(err)
   169  	}
   170  	if sizeInMiB < params.Size {
   171  		os.Remove(path)
   172  		return nil, errors.Errorf("filesystem is not big enough (%dM < %dM)", sizeInMiB, params.Size)
   173  	}
   174  	return &storage.Filesystem{
   175  		params.Tag,
   176  		names.VolumeTag{},
   177  		storage.FilesystemInfo{
   178  			FilesystemId: params.Tag.Id(),
   179  			Size:         sizeInMiB,
   180  		},
   181  	}, nil
   182  }
   183  
   184  // DestroyFilesystems is defined on the FilesystemSource interface.
   185  func (s *rootfsFilesystemSource) DestroyFilesystems(filesystemIds []string) ([]error, error) {
   186  	// DestroyFilesystems is a no-op; we leave the storage directory
   187  	// in tact for post-mortems and such.
   188  	return make([]error, len(filesystemIds)), nil
   189  }
   190  
   191  // AttachFilesystems is defined on the FilesystemSource interface.
   192  func (s *rootfsFilesystemSource) AttachFilesystems(args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) {
   193  	results := make([]storage.AttachFilesystemsResult, len(args))
   194  	for i, arg := range args {
   195  		attachment, err := s.attachFilesystem(arg)
   196  		if err != nil {
   197  			results[i].Error = err
   198  			continue
   199  		}
   200  		results[i].FilesystemAttachment = attachment
   201  	}
   202  	return results, nil
   203  }
   204  
   205  func (s *rootfsFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (*storage.FilesystemAttachment, error) {
   206  	mountPoint := arg.Path
   207  	if mountPoint == "" {
   208  		return nil, errNoMountPoint
   209  	}
   210  	// The filesystem is created at <storage-dir>/<storage-id>.
   211  	// If it is different to the attachment path, bind mount.
   212  	if err := s.mount(arg.Filesystem, mountPoint); err != nil {
   213  		return nil, err
   214  	}
   215  	return &storage.FilesystemAttachment{
   216  		arg.Filesystem,
   217  		arg.Machine,
   218  		storage.FilesystemAttachmentInfo{
   219  			Path: mountPoint,
   220  		},
   221  	}, nil
   222  }
   223  
   224  func (s *rootfsFilesystemSource) mount(tag names.FilesystemTag, target string) error {
   225  	fsPath := filepath.Join(s.storageDir, tag.Id())
   226  	if target == fsPath {
   227  		return nil
   228  	}
   229  	logger.Debugf("mounting filesystem %q at %q", fsPath, target)
   230  
   231  	if err := ensureDir(s.dirFuncs, target); err != nil {
   232  		return errors.Trace(err)
   233  	}
   234  
   235  	mounted, err := s.tryBindMount(fsPath, target)
   236  	if err != nil {
   237  		return errors.Trace(err)
   238  	}
   239  	if mounted {
   240  		return nil
   241  	}
   242  	// We couldn't bind-mount over the designated directory;
   243  	// carry on and check if it's on the same filesystem. If
   244  	// it is, and it's empty, then claim it as our own.
   245  
   246  	if err := s.validateSameMountPoints(fsPath, target); err != nil {
   247  		return err
   248  	}
   249  
   250  	// The first time we try to take the existing directory, we'll
   251  	// ensure that it's empty and create a file to "claim" it.
   252  	// Future attachments will simply ensure that the claim file
   253  	// exists.
   254  	targetClaimPath := filepath.Join(fsPath, "juju-target-claimed")
   255  	_, err = s.dirFuncs.lstat(targetClaimPath)
   256  	if err == nil {
   257  		return nil
   258  	} else if !os.IsNotExist(err) {
   259  		return errors.Trace(err)
   260  	}
   261  	if err := ensureEmptyDir(s.dirFuncs, target); err != nil {
   262  		return errors.Trace(err)
   263  	}
   264  	if err := s.dirFuncs.mkDirAll(targetClaimPath, 0755); err != nil {
   265  		return errors.Annotate(err, "writing claim file")
   266  	}
   267  	return nil
   268  }
   269  
   270  func (s *rootfsFilesystemSource) tryBindMount(source, target string) (bool, error) {
   271  	targetSource, err := s.dirFuncs.mountPointSource(target)
   272  	if err != nil {
   273  		return false, errors.Annotate(err, "getting target mount-point source")
   274  	}
   275  	if targetSource == source {
   276  		// Already bind mounted.
   277  		return true, nil
   278  	}
   279  	if err := s.dirFuncs.bindMount(source, target); err != nil {
   280  		logger.Debugf("cannot bind-mount: %v", err)
   281  	} else {
   282  		return true, nil
   283  	}
   284  	return false, nil
   285  }
   286  
   287  func (s *rootfsFilesystemSource) validateSameMountPoints(source, target string) error {
   288  	sourceMountPoint, err := s.dirFuncs.mountPoint(source)
   289  	if err != nil {
   290  		return errors.Trace(err)
   291  	}
   292  	targetMountPoint, err := s.dirFuncs.mountPoint(target)
   293  	if err != nil {
   294  		return errors.Trace(err)
   295  	}
   296  	if sourceMountPoint != targetMountPoint {
   297  		return errors.Errorf(
   298  			"%q (%q) and %q (%q) are on different filesystems",
   299  			source, sourceMountPoint, target, targetMountPoint,
   300  		)
   301  	}
   302  	return nil
   303  }
   304  
   305  // DetachFilesystems is defined on the FilesystemSource interface.
   306  func (s *rootfsFilesystemSource) DetachFilesystems(args []storage.FilesystemAttachmentParams) ([]error, error) {
   307  	results := make([]error, len(args))
   308  	for i, arg := range args {
   309  		if err := maybeUnmount(s.run, s.dirFuncs, arg.Path); err != nil {
   310  			results[i] = err
   311  		}
   312  	}
   313  	return results, nil
   314  }