github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/storage/provider/managedfs.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  	"path"
     8  	"path/filepath"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names"
    12  
    13  	"github.com/juju/juju/storage"
    14  )
    15  
    16  const (
    17  	// defaultFilesystemType is the default filesystem type
    18  	// to create for volume-backed managed filesystems.
    19  	defaultFilesystemType = "ext4"
    20  )
    21  
    22  // managedFilesystemSource is an implementation of storage.FilesystemSource
    23  // that manages filesystems on volumes attached to the host machine.
    24  //
    25  // managedFilesystemSource is expected to be called from a single goroutine.
    26  type managedFilesystemSource struct {
    27  	run                runCommandFunc
    28  	dirFuncs           dirFuncs
    29  	volumeBlockDevices map[names.VolumeTag]storage.BlockDevice
    30  	filesystems        map[names.FilesystemTag]storage.Filesystem
    31  }
    32  
    33  // NewManagedFilesystemSource returns a storage.FilesystemSource that manages
    34  // filesystems on block devices on the host machine.
    35  //
    36  // The parameters are maps that the caller will update with information about
    37  // block devices and filesystems created by the source. The caller must not
    38  // update the maps during calls to the source's methods.
    39  func NewManagedFilesystemSource(
    40  	volumeBlockDevices map[names.VolumeTag]storage.BlockDevice,
    41  	filesystems map[names.FilesystemTag]storage.Filesystem,
    42  ) storage.FilesystemSource {
    43  	return &managedFilesystemSource{
    44  		logAndExec,
    45  		&osDirFuncs{logAndExec},
    46  		volumeBlockDevices, filesystems,
    47  	}
    48  }
    49  
    50  // ValidateFilesystemParams is defined on storage.FilesystemSource.
    51  func (s *managedFilesystemSource) ValidateFilesystemParams(arg storage.FilesystemParams) error {
    52  	if _, err := s.backingVolumeBlockDevice(arg.Volume); err != nil {
    53  		return errors.Trace(err)
    54  	}
    55  	return nil
    56  }
    57  
    58  func (s *managedFilesystemSource) backingVolumeBlockDevice(v names.VolumeTag) (storage.BlockDevice, error) {
    59  	blockDevice, ok := s.volumeBlockDevices[v]
    60  	if !ok {
    61  		return storage.BlockDevice{}, errors.Errorf(
    62  			"backing-volume %s is not yet attached", v.Id(),
    63  		)
    64  	}
    65  	return blockDevice, nil
    66  }
    67  
    68  // CreateFilesystems is defined on storage.FilesystemSource.
    69  func (s *managedFilesystemSource) CreateFilesystems(args []storage.FilesystemParams) ([]storage.Filesystem, error) {
    70  	filesystems := make([]storage.Filesystem, len(args))
    71  	for i, arg := range args {
    72  		filesystem, err := s.createFilesystem(arg)
    73  		if err != nil {
    74  			return nil, errors.Annotatef(err, "creating filesystem %s", arg.Tag.Id())
    75  		}
    76  		filesystems[i] = filesystem
    77  	}
    78  	return filesystems, nil
    79  }
    80  
    81  func (s *managedFilesystemSource) createFilesystem(arg storage.FilesystemParams) (storage.Filesystem, error) {
    82  	blockDevice, err := s.backingVolumeBlockDevice(arg.Volume)
    83  	if err != nil {
    84  		return storage.Filesystem{}, errors.Trace(err)
    85  	}
    86  	devicePath := s.devicePath(blockDevice)
    87  	if err := createFilesystem(s.run, devicePath); err != nil {
    88  		return storage.Filesystem{}, errors.Trace(err)
    89  	}
    90  	return storage.Filesystem{
    91  		arg.Tag,
    92  		arg.Volume,
    93  		storage.FilesystemInfo{
    94  			arg.Tag.String(),
    95  			blockDevice.Size,
    96  		},
    97  	}, nil
    98  }
    99  
   100  // DestroyFilesystems is defined on storage.FilesystemSource.
   101  func (s *managedFilesystemSource) DestroyFilesystems(filesystemIds []string) []error {
   102  	// DestroyFilesystems is a no-op; there is nothing to destroy,
   103  	// since the filesystem is just data on a volume. The volume
   104  	// is destroyed separately.
   105  	return make([]error, len(filesystemIds))
   106  }
   107  
   108  func (s *managedFilesystemSource) devicePath(dev storage.BlockDevice) string {
   109  	if dev.DeviceName != "" {
   110  		return path.Join("/dev", dev.DeviceName)
   111  	}
   112  	return path.Join("/dev/disk/by-id", dev.HardwareId)
   113  }
   114  
   115  // AttachFilesystems is defined on storage.FilesystemSource.
   116  func (s *managedFilesystemSource) AttachFilesystems(args []storage.FilesystemAttachmentParams) ([]storage.FilesystemAttachment, error) {
   117  	attachments := make([]storage.FilesystemAttachment, len(args))
   118  	for i, arg := range args {
   119  		attachment, err := s.attachFilesystem(arg)
   120  		if err != nil {
   121  			return nil, errors.Annotatef(err, "attaching filesystem %s", arg.Filesystem.Id())
   122  		}
   123  		attachments[i] = attachment
   124  	}
   125  	return attachments, nil
   126  }
   127  
   128  func (s *managedFilesystemSource) attachFilesystem(arg storage.FilesystemAttachmentParams) (storage.FilesystemAttachment, error) {
   129  	filesystem, ok := s.filesystems[arg.Filesystem]
   130  	if !ok {
   131  		return storage.FilesystemAttachment{}, errors.Errorf("filesystem %v is not yet provisioned", arg.Filesystem.Id())
   132  	}
   133  	blockDevice, err := s.backingVolumeBlockDevice(filesystem.Volume)
   134  	if err != nil {
   135  		return storage.FilesystemAttachment{}, errors.Trace(err)
   136  	}
   137  	devicePath := s.devicePath(blockDevice)
   138  	if err := mountFilesystem(s.run, s.dirFuncs, devicePath, arg.Path, arg.ReadOnly); err != nil {
   139  		return storage.FilesystemAttachment{}, errors.Trace(err)
   140  	}
   141  	return storage.FilesystemAttachment{
   142  		arg.Filesystem,
   143  		arg.Machine,
   144  		storage.FilesystemAttachmentInfo{
   145  			arg.Path,
   146  			arg.ReadOnly,
   147  		},
   148  	}, nil
   149  }
   150  
   151  // DetachFilesystems is defined on storage.FilesystemSource.
   152  func (s *managedFilesystemSource) DetachFilesystems(args []storage.FilesystemAttachmentParams) error {
   153  	for _, arg := range args {
   154  		if err := maybeUnmount(s.run, s.dirFuncs, arg.Path); err != nil {
   155  			return errors.Annotatef(err, "detaching filesystem %s", arg.Filesystem.Id())
   156  		}
   157  	}
   158  	return nil
   159  }
   160  
   161  func createFilesystem(run runCommandFunc, devicePath string) error {
   162  	logger.Debugf("attempting to create filesystem on %q", devicePath)
   163  	mkfscmd := "mkfs." + defaultFilesystemType
   164  	_, err := run(mkfscmd, devicePath)
   165  	if err != nil {
   166  		return errors.Annotatef(err, "%s failed (%q)", mkfscmd)
   167  	}
   168  	logger.Infof("created filesystem on %q", devicePath)
   169  	return nil
   170  }
   171  
   172  func mountFilesystem(run runCommandFunc, dirFuncs dirFuncs, devicePath, mountPoint string, readOnly bool) error {
   173  	logger.Debugf("attempting to mount filesystem on %q at %q", devicePath, mountPoint)
   174  	if err := dirFuncs.mkDirAll(mountPoint, 0755); err != nil {
   175  		return errors.Annotate(err, "creating mount point")
   176  	}
   177  	mounted, mountSource, err := isMounted(dirFuncs, mountPoint)
   178  	if err != nil {
   179  		return errors.Trace(err)
   180  	}
   181  	if mounted {
   182  		logger.Debugf("filesystem on %q already mounted at %q", mountSource, mountPoint)
   183  		return nil
   184  	}
   185  	var args []string
   186  	if readOnly {
   187  		args = append(args, "-o", "ro")
   188  	}
   189  	args = append(args, devicePath, mountPoint)
   190  	if _, err := run("mount", args...); err != nil {
   191  		return errors.Annotate(err, "mount failed")
   192  	}
   193  	logger.Infof("mounted filesystem on %q at %q", devicePath, mountPoint)
   194  	return nil
   195  }
   196  
   197  func maybeUnmount(run runCommandFunc, dirFuncs dirFuncs, mountPoint string) error {
   198  	mounted, _, err := isMounted(dirFuncs, mountPoint)
   199  	if err != nil {
   200  		return errors.Trace(err)
   201  	}
   202  	if !mounted {
   203  		return nil
   204  	}
   205  	logger.Debugf("attempting to unmount filesystem at %q", mountPoint)
   206  	if _, err := run("umount", mountPoint); err != nil {
   207  		return errors.Annotate(err, "umount failed")
   208  	}
   209  	logger.Infof("unmounted filesystem at %q", mountPoint)
   210  	return nil
   211  }
   212  
   213  func isMounted(dirFuncs dirFuncs, mountPoint string) (bool, string, error) {
   214  	mountPointParent := filepath.Dir(mountPoint)
   215  	parentSource, err := dirFuncs.mountPointSource(mountPointParent)
   216  	if err != nil {
   217  		return false, "", errors.Trace(err)
   218  	}
   219  	source, err := dirFuncs.mountPointSource(mountPoint)
   220  	if err != nil {
   221  		return false, "", errors.Trace(err)
   222  	}
   223  	if source != parentSource {
   224  		// Already mounted.
   225  		return true, source, nil
   226  	}
   227  	return false, "", nil
   228  }