github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/storage/provider/loop.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  	"fmt"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names"
    15  
    16  	"github.com/juju/juju/environs/config"
    17  	"github.com/juju/juju/storage"
    18  )
    19  
    20  const (
    21  	// Loop provider types.
    22  	LoopProviderType     = storage.ProviderType("loop")
    23  	HostLoopProviderType = storage.ProviderType("hostloop")
    24  )
    25  
    26  // loopProviders create volume sources which use loop devices.
    27  type loopProvider struct {
    28  	// run is a function used for running commands on the local machine.
    29  	run runCommandFunc
    30  }
    31  
    32  var _ storage.Provider = (*loopProvider)(nil)
    33  
    34  // ValidateConfig is defined on the Provider interface.
    35  func (*loopProvider) ValidateConfig(*storage.Config) error {
    36  	// Loop provider has no configuration.
    37  	return nil
    38  }
    39  
    40  // validateFullConfig validates a fully-constructed storage config,
    41  // combining the user-specified config and any internally specified
    42  // config.
    43  func (lp *loopProvider) validateFullConfig(cfg *storage.Config) error {
    44  	if err := lp.ValidateConfig(cfg); err != nil {
    45  		return err
    46  	}
    47  	storageDir, ok := cfg.ValueString(storage.ConfigStorageDir)
    48  	if !ok || storageDir == "" {
    49  		return errors.New("storage directory not specified")
    50  	}
    51  	return nil
    52  }
    53  
    54  // VolumeSource is defined on the Provider interface.
    55  func (lp *loopProvider) VolumeSource(
    56  	environConfig *config.Config,
    57  	sourceConfig *storage.Config,
    58  ) (storage.VolumeSource, error) {
    59  	if err := lp.validateFullConfig(sourceConfig); err != nil {
    60  		return nil, err
    61  	}
    62  	// storageDir is validated by validateFullConfig.
    63  	storageDir, _ := sourceConfig.ValueString(storage.ConfigStorageDir)
    64  	return &loopVolumeSource{
    65  		&osDirFuncs{lp.run},
    66  		lp.run,
    67  		storageDir,
    68  	}, nil
    69  }
    70  
    71  // FilesystemSource is defined on the Provider interface.
    72  func (lp *loopProvider) FilesystemSource(
    73  	environConfig *config.Config,
    74  	providerConfig *storage.Config,
    75  ) (storage.FilesystemSource, error) {
    76  	return nil, errors.NotSupportedf("filesystems")
    77  }
    78  
    79  // Supports is defined on the Provider interface.
    80  func (*loopProvider) Supports(k storage.StorageKind) bool {
    81  	return k == storage.StorageKindBlock
    82  }
    83  
    84  // Scope is defined on the Provider interface.
    85  func (*loopProvider) Scope() storage.Scope {
    86  	return storage.ScopeMachine
    87  }
    88  
    89  // Dynamic is defined on the Provider interface.
    90  func (*loopProvider) Dynamic() bool {
    91  	return true
    92  }
    93  
    94  // loopVolumeSource provides common functionality to handle
    95  // loop devices for rootfs and host loop volume sources.
    96  type loopVolumeSource struct {
    97  	dirFuncs   dirFuncs
    98  	run        runCommandFunc
    99  	storageDir string
   100  }
   101  
   102  var _ storage.VolumeSource = (*loopVolumeSource)(nil)
   103  
   104  // CreateVolumes is defined on the VolumeSource interface.
   105  func (lvs *loopVolumeSource) CreateVolumes(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
   106  	results := make([]storage.CreateVolumesResult, len(args))
   107  	for i, arg := range args {
   108  		volume, err := lvs.createVolume(arg)
   109  		if err != nil {
   110  			results[i].Error = errors.Annotate(err, "creating volume")
   111  		}
   112  		results[i].Volume = &volume
   113  	}
   114  	return results, nil
   115  }
   116  
   117  func (lvs *loopVolumeSource) createVolume(params storage.VolumeParams) (storage.Volume, error) {
   118  	volumeId := params.Tag.String()
   119  	loopFilePath := lvs.volumeFilePath(params.Tag)
   120  	if err := ensureDir(lvs.dirFuncs, filepath.Dir(loopFilePath)); err != nil {
   121  		return storage.Volume{}, errors.Trace(err)
   122  	}
   123  	if err := createBlockFile(lvs.run, loopFilePath, params.Size); err != nil {
   124  		return storage.Volume{}, errors.Annotate(err, "could not create block file")
   125  	}
   126  	return storage.Volume{
   127  		params.Tag,
   128  		storage.VolumeInfo{
   129  			VolumeId: volumeId,
   130  			Size:     params.Size,
   131  		},
   132  	}, nil
   133  }
   134  
   135  func (lvs *loopVolumeSource) volumeFilePath(tag names.VolumeTag) string {
   136  	return filepath.Join(lvs.storageDir, tag.String())
   137  }
   138  
   139  // ListVolumes is defined on the VolumeSource interface.
   140  func (lvs *loopVolumeSource) ListVolumes() ([]string, error) {
   141  	// TODO(axw) implement this when we need it.
   142  	return nil, errors.NotImplementedf("ListVolumes")
   143  }
   144  
   145  // DescribeVolumes is defined on the VolumeSource interface.
   146  func (lvs *loopVolumeSource) DescribeVolumes(volumeIds []string) ([]storage.DescribeVolumesResult, error) {
   147  	// TODO(axw) implement this when we need it.
   148  	return nil, errors.NotImplementedf("DescribeVolumes")
   149  }
   150  
   151  // DestroyVolumes is defined on the VolumeSource interface.
   152  func (lvs *loopVolumeSource) DestroyVolumes(volumeIds []string) ([]error, error) {
   153  	results := make([]error, len(volumeIds))
   154  	for i, volumeId := range volumeIds {
   155  		if err := lvs.destroyVolume(volumeId); err != nil {
   156  			results[i] = errors.Annotatef(err, "destroying %q", volumeId)
   157  		}
   158  	}
   159  	return results, nil
   160  }
   161  
   162  func (lvs *loopVolumeSource) destroyVolume(volumeId string) error {
   163  	tag, err := names.ParseVolumeTag(volumeId)
   164  	if err != nil {
   165  		return errors.Errorf("invalid loop volume ID %q", volumeId)
   166  	}
   167  	loopFilePath := lvs.volumeFilePath(tag)
   168  	err = os.Remove(loopFilePath)
   169  	if err != nil && !os.IsNotExist(err) {
   170  		return errors.Annotate(err, "removing loop backing file")
   171  	}
   172  	return nil
   173  }
   174  
   175  // ValidateVolumeParams is defined on the VolumeSource interface.
   176  func (lvs *loopVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   177  	// ValdiateVolumeParams may be called on a machine other than the
   178  	// machine where the loop device will be created, so we cannot check
   179  	// available size until we get to CreateVolumes.
   180  	return nil
   181  }
   182  
   183  // AttachVolumes is defined on the VolumeSource interface.
   184  func (lvs *loopVolumeSource) AttachVolumes(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   185  	results := make([]storage.AttachVolumesResult, len(args))
   186  	for i, arg := range args {
   187  		attachment, err := lvs.attachVolume(arg)
   188  		if err != nil {
   189  			results[i].Error = errors.Annotatef(err, "attaching volume %v", arg.Volume.Id())
   190  			continue
   191  		}
   192  		results[i].VolumeAttachment = attachment
   193  	}
   194  	return results, nil
   195  }
   196  
   197  func (lvs *loopVolumeSource) attachVolume(arg storage.VolumeAttachmentParams) (*storage.VolumeAttachment, error) {
   198  	loopFilePath := lvs.volumeFilePath(arg.Volume)
   199  	deviceName, err := attachLoopDevice(lvs.run, loopFilePath, arg.ReadOnly)
   200  	if err != nil {
   201  		os.Remove(loopFilePath)
   202  		return nil, errors.Annotate(err, "attaching loop device")
   203  	}
   204  	return &storage.VolumeAttachment{
   205  		arg.Volume,
   206  		arg.Machine,
   207  		storage.VolumeAttachmentInfo{
   208  			DeviceName: deviceName,
   209  			ReadOnly:   arg.ReadOnly,
   210  		},
   211  	}, nil
   212  }
   213  
   214  // DetachVolumes is defined on the VolumeSource interface.
   215  func (lvs *loopVolumeSource) DetachVolumes(args []storage.VolumeAttachmentParams) ([]error, error) {
   216  	results := make([]error, len(args))
   217  	for i, arg := range args {
   218  		if err := lvs.detachVolume(arg.Volume); err != nil {
   219  			results[i] = errors.Annotatef(err, "detaching volume %s", arg.Volume.Id())
   220  		}
   221  	}
   222  	return results, nil
   223  }
   224  
   225  func (lvs *loopVolumeSource) detachVolume(tag names.VolumeTag) error {
   226  	loopFilePath := lvs.volumeFilePath(tag)
   227  	deviceNames, err := associatedLoopDevices(lvs.run, loopFilePath)
   228  	if err != nil {
   229  		return errors.Annotate(err, "locating loop device")
   230  	}
   231  	if len(deviceNames) > 1 {
   232  		logger.Warningf("expected 1 loop device, got %d", len(deviceNames))
   233  	}
   234  	for _, deviceName := range deviceNames {
   235  		if err := detachLoopDevice(lvs.run, deviceName); err != nil {
   236  			return errors.Trace(err)
   237  		}
   238  	}
   239  	return nil
   240  }
   241  
   242  // createBlockFile creates a file at the specified path, with the
   243  // given size in mebibytes.
   244  func createBlockFile(run runCommandFunc, filePath string, sizeInMiB uint64) error {
   245  	// fallocate will reserve the space without actually writing to it.
   246  	_, err := run("fallocate", "-l", fmt.Sprintf("%dMiB", sizeInMiB), filePath)
   247  	if err != nil {
   248  		return errors.Annotatef(err, "allocating loop backing file %q", filePath)
   249  	}
   250  	return nil
   251  }
   252  
   253  // attachLoopDevice attaches a loop device to the file with the
   254  // specified path, and returns the loop device's name (e.g. "loop0").
   255  // losetup will create additional loop devices as necessary.
   256  func attachLoopDevice(run runCommandFunc, filePath string, readOnly bool) (loopDeviceName string, _ error) {
   257  	devices, err := associatedLoopDevices(run, filePath)
   258  	if err != nil {
   259  		return "", err
   260  	}
   261  	if len(devices) > 0 {
   262  		// Already attached.
   263  		logger.Debugf("%s already attached to %s", filePath, devices)
   264  		return devices[0], nil
   265  	}
   266  	// -f automatically finds the first available loop-device.
   267  	// -r sets up a read-only loop-device.
   268  	// --show returns the loop device chosen on stdout.
   269  	args := []string{"-f", "--show"}
   270  	if readOnly {
   271  		args = append(args, "-r")
   272  	}
   273  	args = append(args, filePath)
   274  	stdout, err := run("losetup", args...)
   275  	if err != nil {
   276  		return "", errors.Annotatef(err, "attaching loop device to %q", filePath)
   277  	}
   278  	stdout = strings.TrimSpace(stdout)
   279  	loopDeviceName = stdout[len("/dev/"):]
   280  	return loopDeviceName, nil
   281  }
   282  
   283  // detachLoopDevice detaches the loop device with the specified name.
   284  func detachLoopDevice(run runCommandFunc, deviceName string) error {
   285  	_, err := run("losetup", "-d", path.Join("/dev", deviceName))
   286  	if err != nil {
   287  		return errors.Annotatef(err, "detaching loop device %q", deviceName)
   288  	}
   289  	return err
   290  }
   291  
   292  // associatedLoopDevices returns the device names of the loop devices
   293  // associated with the specified file path.
   294  func associatedLoopDevices(run runCommandFunc, filePath string) ([]string, error) {
   295  	stdout, err := run("losetup", "-j", filePath)
   296  	if err != nil {
   297  		return nil, errors.Trace(err)
   298  	}
   299  	stdout = strings.TrimSpace(stdout)
   300  	if stdout == "" {
   301  		return nil, nil
   302  	}
   303  	// The output will be zero or more lines with the format:
   304  	//    "/dev/loop0: [0021]:7504142 (/tmp/test.dat)"
   305  	lines := strings.Split(stdout, "\n")
   306  	deviceNames := make([]string, len(lines))
   307  	for i, line := range lines {
   308  		pos := strings.IndexRune(line, ':')
   309  		if pos == -1 {
   310  			return nil, errors.Errorf("unexpected output %q", line)
   311  		}
   312  		deviceName := line[:pos][len("/dev/"):]
   313  		deviceNames[i] = deviceName
   314  	}
   315  	return deviceNames, nil
   316  }