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