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