github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/environs/context"
    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(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(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  // Releasable is defined on the Provider interface.
    86  func (*rootfsProvider) Releasable() bool {
    87  	return false
    88  }
    89  
    90  // DefaultPools is defined on the Provider interface.
    91  func (*rootfsProvider) DefaultPools() []*storage.Config {
    92  	return nil
    93  }
    94  
    95  type rootfsFilesystemSource struct {
    96  	dirFuncs   dirFuncs
    97  	run        runCommandFunc
    98  	storageDir string
    99  }
   100  
   101  // ensureDir ensures the specified path is a directory, or
   102  // if it does not exist, that a directory can be created there.
   103  func ensureDir(d dirFuncs, path string) error {
   104  	// If path already exists, we check that it is empty.
   105  	// It is up to the storage provisioner to ensure that any
   106  	// shared storage constraints and attachments with the same
   107  	// path are validated etc. So the check here is more a sanity check.
   108  	fi, err := d.lstat(path)
   109  	if err == nil {
   110  		if !fi.IsDir() {
   111  			return errors.Errorf("path %q must be a directory", path)
   112  		}
   113  		return nil
   114  	}
   115  	if !os.IsNotExist(err) {
   116  		return errors.Trace(err)
   117  	}
   118  	if err := d.mkDirAll(path, 0755); err != nil {
   119  		return errors.Annotate(err, "could not create directory")
   120  	}
   121  	return nil
   122  }
   123  
   124  // ensureEmptyDir ensures the specified directory is empty.
   125  func ensureEmptyDir(d dirFuncs, path string) error {
   126  	fileCount, err := d.fileCount(path)
   127  	if err != nil {
   128  		return errors.Annotate(err, "could not read directory")
   129  	}
   130  	if fileCount > 0 {
   131  		return errors.Errorf("%q is not empty", path)
   132  	}
   133  	return nil
   134  }
   135  
   136  var _ storage.FilesystemSource = (*rootfsFilesystemSource)(nil)
   137  
   138  // ValidateFilesystemParams is defined on the FilesystemSource interface.
   139  func (s *rootfsFilesystemSource) ValidateFilesystemParams(params storage.FilesystemParams) error {
   140  	// ValidateFilesystemParams may be called on a machine other than the
   141  	// machine where the filesystem will be mounted, so we cannot check
   142  	// available size until we get to CreateFilesystem.
   143  	return nil
   144  }
   145  
   146  // CreateFilesystems is defined on the FilesystemSource interface.
   147  func (s *rootfsFilesystemSource) CreateFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) {
   148  	results := make([]storage.CreateFilesystemsResult, len(args))
   149  	for i, arg := range args {
   150  		filesystem, err := s.createFilesystem(arg)
   151  		if err != nil {
   152  			results[i].Error = err
   153  			continue
   154  		}
   155  		results[i].Filesystem = filesystem
   156  	}
   157  	return results, nil
   158  }
   159  
   160  func (s *rootfsFilesystemSource) createFilesystem(params storage.FilesystemParams) (*storage.Filesystem, error) {
   161  	if err := s.ValidateFilesystemParams(params); err != nil {
   162  		return nil, errors.Trace(err)
   163  	}
   164  	path := filepath.Join(s.storageDir, params.Tag.Id())
   165  	if err := ensureDir(s.dirFuncs, path); err != nil {
   166  		return nil, errors.Trace(err)
   167  	}
   168  	if err := ensureEmptyDir(s.dirFuncs, path); err != nil {
   169  		return nil, errors.Trace(err)
   170  	}
   171  	sizeInMiB, err := s.dirFuncs.calculateSize(s.storageDir)
   172  	if err != nil {
   173  		os.Remove(path)
   174  		return nil, errors.Trace(err)
   175  	}
   176  	if sizeInMiB < params.Size {
   177  		os.Remove(path)
   178  		return nil, errors.Errorf("filesystem is not big enough (%dM < %dM)", sizeInMiB, params.Size)
   179  	}
   180  	return &storage.Filesystem{
   181  		params.Tag,
   182  		names.VolumeTag{},
   183  		storage.FilesystemInfo{
   184  			FilesystemId: params.Tag.Id(),
   185  			Size:         sizeInMiB,
   186  		},
   187  	}, nil
   188  }
   189  
   190  // DestroyFilesystems is defined on the FilesystemSource interface.
   191  func (s *rootfsFilesystemSource) DestroyFilesystems(ctx context.ProviderCallContext, filesystemIds []string) ([]error, error) {
   192  	// DestroyFilesystems is a no-op; we leave the storage directory
   193  	// in tact for post-mortems and such.
   194  	return make([]error, len(filesystemIds)), nil
   195  }
   196  
   197  // ReleaseFilesystems is defined on the FilesystemSource interface.
   198  func (s *rootfsFilesystemSource) ReleaseFilesystems(ctx context.ProviderCallContext, filesystemIds []string) ([]error, error) {
   199  	return make([]error, len(filesystemIds)), nil
   200  }
   201  
   202  // AttachFilesystems is defined on the FilesystemSource interface.
   203  func (s *rootfsFilesystemSource) AttachFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) {
   204  	results := make([]storage.AttachFilesystemsResult, len(args))
   205  	for i, arg := range args {
   206  		attachment, err := s.attachFilesystem(arg)
   207  		if err != nil {
   208  			results[i].Error = err
   209  			continue
   210  		}
   211  		results[i].FilesystemAttachment = attachment
   212  	}
   213  	return results, nil
   214  }
   215  
   216  func (s *rootfsFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (*storage.FilesystemAttachment, error) {
   217  	mountPoint := arg.Path
   218  	if mountPoint == "" {
   219  		return nil, errNoMountPoint
   220  	}
   221  	// The filesystem is created at <storage-dir>/<storage-id>.
   222  	// If it is different to the attachment path, bind mount.
   223  	if err := s.mount(arg.Filesystem, mountPoint); err != nil {
   224  		return nil, err
   225  	}
   226  	return &storage.FilesystemAttachment{
   227  		arg.Filesystem,
   228  		arg.Machine,
   229  		storage.FilesystemAttachmentInfo{
   230  			Path: mountPoint,
   231  		},
   232  	}, nil
   233  }
   234  
   235  func (s *rootfsFilesystemSource) mount(tag names.FilesystemTag, target string) error {
   236  	fsPath := filepath.Join(s.storageDir, tag.Id())
   237  	if target == fsPath {
   238  		return nil
   239  	}
   240  	logger.Debugf("mounting filesystem %q at %q", fsPath, target)
   241  
   242  	if err := ensureDir(s.dirFuncs, target); err != nil {
   243  		return errors.Trace(err)
   244  	}
   245  
   246  	mounted, err := s.tryBindMount(fsPath, target)
   247  	if err != nil {
   248  		return errors.Trace(err)
   249  	}
   250  	if mounted {
   251  		return nil
   252  	}
   253  	// We couldn't bind-mount over the designated directory;
   254  	// carry on and check if it's on the same filesystem. If
   255  	// it is, and it's empty, then claim it as our own.
   256  
   257  	if err := s.validateSameMountPoints(fsPath, target); err != nil {
   258  		return err
   259  	}
   260  
   261  	// The first time we try to take the existing directory, we'll
   262  	// ensure that it's empty and create a file to "claim" it.
   263  	// Future attachments will simply ensure that the claim file
   264  	// exists.
   265  	targetClaimPath := filepath.Join(fsPath, "juju-target-claimed")
   266  	_, err = s.dirFuncs.lstat(targetClaimPath)
   267  	if err == nil {
   268  		return nil
   269  	} else if !os.IsNotExist(err) {
   270  		return errors.Trace(err)
   271  	}
   272  	if err := ensureEmptyDir(s.dirFuncs, target); err != nil {
   273  		return errors.Trace(err)
   274  	}
   275  	if err := s.dirFuncs.mkDirAll(targetClaimPath, 0755); err != nil {
   276  		return errors.Annotate(err, "writing claim file")
   277  	}
   278  	return nil
   279  }
   280  
   281  func (s *rootfsFilesystemSource) tryBindMount(source, target string) (bool, error) {
   282  	targetSource, err := s.dirFuncs.mountPointSource(target)
   283  	if err != nil {
   284  		return false, errors.Annotate(err, "getting target mount-point source")
   285  	}
   286  	if targetSource == source {
   287  		// Already bind mounted.
   288  		return true, nil
   289  	}
   290  	if err := s.dirFuncs.bindMount(source, target); err != nil {
   291  		logger.Debugf("cannot bind-mount: %v", err)
   292  	} else {
   293  		return true, nil
   294  	}
   295  	return false, nil
   296  }
   297  
   298  func (s *rootfsFilesystemSource) validateSameMountPoints(source, target string) error {
   299  	sourceMountPoint, err := s.dirFuncs.mountPoint(source)
   300  	if err != nil {
   301  		return errors.Trace(err)
   302  	}
   303  	targetMountPoint, err := s.dirFuncs.mountPoint(target)
   304  	if err != nil {
   305  		return errors.Trace(err)
   306  	}
   307  	if sourceMountPoint != targetMountPoint {
   308  		return errors.Errorf(
   309  			"%q (%q) and %q (%q) are on different filesystems",
   310  			source, sourceMountPoint, target, targetMountPoint,
   311  		)
   312  	}
   313  	return nil
   314  }
   315  
   316  // DetachFilesystems is defined on the FilesystemSource interface.
   317  func (s *rootfsFilesystemSource) DetachFilesystems(ctx context.ProviderCallContext, args []storage.FilesystemAttachmentParams) ([]error, error) {
   318  	results := make([]error, len(args))
   319  	for i, arg := range args {
   320  		if err := maybeUnmount(s.run, s.dirFuncs, arg.Path); err != nil {
   321  			results[i] = err
   322  		}
   323  	}
   324  	return results, nil
   325  }