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