github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/apiserver/common/storage.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"path"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names"
    11  
    12  	"github.com/juju/juju/environs/config"
    13  	"github.com/juju/juju/environs/tags"
    14  	"github.com/juju/juju/state"
    15  	"github.com/juju/juju/storage"
    16  )
    17  
    18  // StorageInterface is an interface for obtaining information about storage
    19  // instances and related entities.
    20  type StorageInterface interface {
    21  	// StorageInstance returns the state.StorageInstance corresponding
    22  	// to the specified storage tag.
    23  	StorageInstance(names.StorageTag) (state.StorageInstance, error)
    24  
    25  	// StorageInstanceFilesystem returns the state.Filesystem assigned
    26  	// to the storage instance with the specified storage tag.
    27  	StorageInstanceFilesystem(names.StorageTag) (state.Filesystem, error)
    28  
    29  	// StorageInstanceVolume returns the state.Volume assigned to the
    30  	// storage instance with the specified storage tag.
    31  	StorageInstanceVolume(names.StorageTag) (state.Volume, error)
    32  
    33  	// FilesystemAttachment returns the state.FilesystemAttachment
    34  	// corresponding to the identified machine and filesystem.
    35  	FilesystemAttachment(names.MachineTag, names.FilesystemTag) (state.FilesystemAttachment, error)
    36  
    37  	// VolumeAttachment returns the state.VolumeAttachment corresponding
    38  	// to the identified machine and volume.
    39  	VolumeAttachment(names.MachineTag, names.VolumeTag) (state.VolumeAttachment, error)
    40  
    41  	// WatchStorageAttachment watches for changes to the storage attachment
    42  	// corresponding to the identfified unit and storage instance.
    43  	WatchStorageAttachment(names.StorageTag, names.UnitTag) state.NotifyWatcher
    44  
    45  	// WatchFilesystemAttachment watches for changes to the filesystem
    46  	// attachment corresponding to the identfified machien and filesystem.
    47  	WatchFilesystemAttachment(names.MachineTag, names.FilesystemTag) state.NotifyWatcher
    48  
    49  	// WatchVolumeAttachment watches for changes to the volume attachment
    50  	// corresponding to the identfified machien and volume.
    51  	WatchVolumeAttachment(names.MachineTag, names.VolumeTag) state.NotifyWatcher
    52  }
    53  
    54  // StorageAttachmentInfo returns the StorageAttachmentInfo for the specified
    55  // StorageAttachment by gathering information from related entities (volumes,
    56  // filesystems).
    57  func StorageAttachmentInfo(
    58  	st StorageInterface,
    59  	att state.StorageAttachment,
    60  	machineTag names.MachineTag,
    61  ) (*storage.StorageAttachmentInfo, error) {
    62  	storageInstance, err := st.StorageInstance(att.StorageInstance())
    63  	if err != nil {
    64  		return nil, errors.Annotate(err, "getting storage instance")
    65  	}
    66  	switch storageInstance.Kind() {
    67  	case state.StorageKindBlock:
    68  		return volumeStorageAttachmentInfo(st, storageInstance, machineTag)
    69  	case state.StorageKindFilesystem:
    70  		return filesystemStorageAttachmentInfo(st, storageInstance, machineTag)
    71  	}
    72  	return nil, errors.Errorf("invalid storage kind %v", storageInstance.Kind())
    73  }
    74  
    75  func volumeStorageAttachmentInfo(
    76  	st StorageInterface,
    77  	storageInstance state.StorageInstance,
    78  	machineTag names.MachineTag,
    79  ) (*storage.StorageAttachmentInfo, error) {
    80  	storageTag := storageInstance.StorageTag()
    81  	volume, err := st.StorageInstanceVolume(storageTag)
    82  	if err != nil {
    83  		return nil, errors.Annotate(err, "getting volume")
    84  	}
    85  	volumeInfo, err := volume.Info()
    86  	if err != nil {
    87  		return nil, errors.Annotate(err, "getting volume info")
    88  	}
    89  	volumeAttachment, err := st.VolumeAttachment(machineTag, volume.VolumeTag())
    90  	if err != nil {
    91  		return nil, errors.Annotate(err, "getting volume attachment")
    92  	}
    93  	volumeAttachmentInfo, err := volumeAttachment.Info()
    94  	if err != nil {
    95  		return nil, errors.Annotate(err, "getting volume attachment info")
    96  	}
    97  	devicePath, err := volumeAttachmentDevicePath(
    98  		volumeInfo,
    99  		volumeAttachmentInfo,
   100  	)
   101  	if err != nil {
   102  		return nil, errors.Trace(err)
   103  	}
   104  	return &storage.StorageAttachmentInfo{
   105  		storage.StorageKindBlock,
   106  		devicePath,
   107  	}, nil
   108  }
   109  
   110  func filesystemStorageAttachmentInfo(
   111  	st StorageInterface,
   112  	storageInstance state.StorageInstance,
   113  	machineTag names.MachineTag,
   114  ) (*storage.StorageAttachmentInfo, error) {
   115  	storageTag := storageInstance.StorageTag()
   116  	filesystem, err := st.StorageInstanceFilesystem(storageTag)
   117  	if err != nil {
   118  		return nil, errors.Annotate(err, "getting filesystem")
   119  	}
   120  	filesystemAttachment, err := st.FilesystemAttachment(machineTag, filesystem.FilesystemTag())
   121  	if err != nil {
   122  		return nil, errors.Annotate(err, "getting filesystem attachment")
   123  	}
   124  	filesystemAttachmentInfo, err := filesystemAttachment.Info()
   125  	if err != nil {
   126  		return nil, errors.Annotate(err, "getting filesystem attachment info")
   127  	}
   128  	return &storage.StorageAttachmentInfo{
   129  		storage.StorageKindFilesystem,
   130  		filesystemAttachmentInfo.MountPoint,
   131  	}, nil
   132  }
   133  
   134  // WatchStorageAttachment returns a state.NotifyWatcher that reacts to changes
   135  // to the VolumeAttachmentInfo or FilesystemAttachmentInfo corresponding to the tags
   136  // specified.
   137  func WatchStorageAttachment(
   138  	st StorageInterface,
   139  	storageTag names.StorageTag,
   140  	machineTag names.MachineTag,
   141  	unitTag names.UnitTag,
   142  ) (state.NotifyWatcher, error) {
   143  	storageInstance, err := st.StorageInstance(storageTag)
   144  	if err != nil {
   145  		return nil, errors.Annotate(err, "getting storage instance")
   146  	}
   147  	var w state.NotifyWatcher
   148  	switch storageInstance.Kind() {
   149  	case state.StorageKindBlock:
   150  		volume, err := st.StorageInstanceVolume(storageTag)
   151  		if err != nil {
   152  			return nil, errors.Annotate(err, "getting storage volume")
   153  		}
   154  		w = st.WatchVolumeAttachment(machineTag, volume.VolumeTag())
   155  	case state.StorageKindFilesystem:
   156  		filesystem, err := st.StorageInstanceFilesystem(storageTag)
   157  		if err != nil {
   158  			return nil, errors.Annotate(err, "getting storage filesystem")
   159  		}
   160  		w = st.WatchFilesystemAttachment(machineTag, filesystem.FilesystemTag())
   161  	default:
   162  		return nil, errors.Errorf("invalid storage kind %v", storageInstance.Kind())
   163  	}
   164  	w2 := st.WatchStorageAttachment(storageTag, unitTag)
   165  	return newMultiNotifyWatcher(w, w2), nil
   166  }
   167  
   168  var errNoDevicePath = errors.New("cannot determine device path: no serial or persistent device name")
   169  
   170  // volumeAttachmentDevicePath returns the absolute device path for
   171  // a volume attachment. The value is only meaningful in the context
   172  // of the machine that the volume is attached to.
   173  func volumeAttachmentDevicePath(
   174  	volumeInfo state.VolumeInfo,
   175  	volumeAttachmentInfo state.VolumeAttachmentInfo,
   176  ) (string, error) {
   177  	if volumeInfo.HardwareId != "" {
   178  		return path.Join("/dev/disk/by-id", volumeInfo.HardwareId), nil
   179  	} else if volumeAttachmentInfo.DeviceName != "" {
   180  		return path.Join("/dev", volumeAttachmentInfo.DeviceName), nil
   181  	}
   182  	return "", errNoDevicePath
   183  }
   184  
   185  // MaybeAssignedStorageInstance calls the provided function to get a
   186  // StorageTag, and returns the corresponding state.StorageInstance if
   187  // it didn't return an errors.IsNotAssigned error, or nil if it did.
   188  func MaybeAssignedStorageInstance(
   189  	getTag func() (names.StorageTag, error),
   190  	getStorageInstance func(names.StorageTag) (state.StorageInstance, error),
   191  ) (state.StorageInstance, error) {
   192  	tag, err := getTag()
   193  	if err == nil {
   194  		return getStorageInstance(tag)
   195  	} else if errors.IsNotAssigned(err) {
   196  		return nil, nil
   197  	}
   198  	return nil, errors.Trace(err)
   199  }
   200  
   201  // storageTags returns the tags that should be set on a volume or filesystem,
   202  // if the provider supports them.
   203  func storageTags(
   204  	storageInstance state.StorageInstance,
   205  	cfg *config.Config,
   206  ) (map[string]string, error) {
   207  	uuid, _ := cfg.UUID()
   208  	storageTags := tags.ResourceTags(names.NewEnvironTag(uuid), cfg)
   209  	if storageInstance != nil {
   210  		storageTags[tags.JujuStorageInstance] = storageInstance.Tag().Id()
   211  		storageTags[tags.JujuStorageOwner] = storageInstance.Owner().Id()
   212  	}
   213  	return storageTags, nil
   214  }