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