github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/common/storagecommon/storage.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package storagecommon provides common storage-related services
     5  // for API server facades.
     6  package storagecommon
     7  
     8  import (
     9  	"github.com/juju/errors"
    10  	"gopkg.in/juju/names.v2"
    11  
    12  	"github.com/juju/juju/apiserver/common"
    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 machine 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 machine and volume.
    51  	WatchVolumeAttachment(names.MachineTag, names.VolumeTag) state.NotifyWatcher
    52  
    53  	// WatchBlockDevices watches for changes to block devices associated
    54  	// with the specified machine.
    55  	WatchBlockDevices(names.MachineTag) state.NotifyWatcher
    56  
    57  	// BlockDevices returns information about block devices published
    58  	// for the specified machine.
    59  	BlockDevices(names.MachineTag) ([]state.BlockDeviceInfo, error)
    60  }
    61  
    62  // StorageAttachmentInfo returns the StorageAttachmentInfo for the specified
    63  // StorageAttachment by gathering information from related entities (volumes,
    64  // filesystems).
    65  //
    66  // StorageAttachmentInfo returns an error satisfying errors.IsNotProvisioned
    67  // if the storage attachment is not yet fully provisioned and ready for use
    68  // by a charm.
    69  func StorageAttachmentInfo(
    70  	st StorageInterface,
    71  	att state.StorageAttachment,
    72  	machineTag names.MachineTag,
    73  ) (*storage.StorageAttachmentInfo, error) {
    74  	storageInstance, err := st.StorageInstance(att.StorageInstance())
    75  	if err != nil {
    76  		return nil, errors.Annotate(err, "getting storage instance")
    77  	}
    78  	switch storageInstance.Kind() {
    79  	case state.StorageKindBlock:
    80  		return volumeStorageAttachmentInfo(st, storageInstance, machineTag)
    81  	case state.StorageKindFilesystem:
    82  		return filesystemStorageAttachmentInfo(st, storageInstance, machineTag)
    83  	}
    84  	return nil, errors.Errorf("invalid storage kind %v", storageInstance.Kind())
    85  }
    86  
    87  func volumeStorageAttachmentInfo(
    88  	st StorageInterface,
    89  	storageInstance state.StorageInstance,
    90  	machineTag names.MachineTag,
    91  ) (*storage.StorageAttachmentInfo, error) {
    92  	storageTag := storageInstance.StorageTag()
    93  	volume, err := st.StorageInstanceVolume(storageTag)
    94  	if err != nil {
    95  		return nil, errors.Annotate(err, "getting volume")
    96  	}
    97  	volumeInfo, err := volume.Info()
    98  	if err != nil {
    99  		return nil, errors.Annotate(err, "getting volume info")
   100  	}
   101  	volumeAttachment, err := st.VolumeAttachment(machineTag, volume.VolumeTag())
   102  	if err != nil {
   103  		return nil, errors.Annotate(err, "getting volume attachment")
   104  	}
   105  	volumeAttachmentInfo, err := volumeAttachment.Info()
   106  	if err != nil {
   107  		return nil, errors.Annotate(err, "getting volume attachment info")
   108  	}
   109  	blockDevices, err := st.BlockDevices(machineTag)
   110  	if err != nil {
   111  		return nil, errors.Annotate(err, "getting block devices")
   112  	}
   113  	blockDevice, ok := MatchingBlockDevice(
   114  		blockDevices,
   115  		volumeInfo,
   116  		volumeAttachmentInfo,
   117  	)
   118  	if !ok {
   119  		// We must not say that a block-kind storage attachment is
   120  		// provisioned until its block device has shown up on the
   121  		// machine, otherwise the charm may attempt to use it and
   122  		// fail.
   123  		return nil, errors.NotProvisionedf("%v", names.ReadableString(storageTag))
   124  	}
   125  	devicePath, err := volumeAttachmentDevicePath(
   126  		volumeInfo,
   127  		volumeAttachmentInfo,
   128  		*blockDevice,
   129  	)
   130  	if err != nil {
   131  		return nil, errors.Trace(err)
   132  	}
   133  	return &storage.StorageAttachmentInfo{
   134  		storage.StorageKindBlock,
   135  		devicePath,
   136  	}, nil
   137  }
   138  
   139  func filesystemStorageAttachmentInfo(
   140  	st StorageInterface,
   141  	storageInstance state.StorageInstance,
   142  	machineTag names.MachineTag,
   143  ) (*storage.StorageAttachmentInfo, error) {
   144  	storageTag := storageInstance.StorageTag()
   145  	filesystem, err := st.StorageInstanceFilesystem(storageTag)
   146  	if err != nil {
   147  		return nil, errors.Annotate(err, "getting filesystem")
   148  	}
   149  	filesystemAttachment, err := st.FilesystemAttachment(machineTag, filesystem.FilesystemTag())
   150  	if err != nil {
   151  		return nil, errors.Annotate(err, "getting filesystem attachment")
   152  	}
   153  	filesystemAttachmentInfo, err := filesystemAttachment.Info()
   154  	if err != nil {
   155  		return nil, errors.Annotate(err, "getting filesystem attachment info")
   156  	}
   157  	return &storage.StorageAttachmentInfo{
   158  		storage.StorageKindFilesystem,
   159  		filesystemAttachmentInfo.MountPoint,
   160  	}, nil
   161  }
   162  
   163  // WatchStorageAttachment returns a state.NotifyWatcher that reacts to changes
   164  // to the VolumeAttachmentInfo or FilesystemAttachmentInfo corresponding to the
   165  // tags specified.
   166  func WatchStorageAttachment(
   167  	st StorageInterface,
   168  	storageTag names.StorageTag,
   169  	machineTag names.MachineTag,
   170  	unitTag names.UnitTag,
   171  ) (state.NotifyWatcher, error) {
   172  	storageInstance, err := st.StorageInstance(storageTag)
   173  	if err != nil {
   174  		return nil, errors.Annotate(err, "getting storage instance")
   175  	}
   176  	var watchers []state.NotifyWatcher
   177  	switch storageInstance.Kind() {
   178  	case state.StorageKindBlock:
   179  		volume, err := st.StorageInstanceVolume(storageTag)
   180  		if err != nil {
   181  			return nil, errors.Annotate(err, "getting storage volume")
   182  		}
   183  		// We need to watch both the volume attachment, and the
   184  		// machine's block devices. A volume attachment's block
   185  		// device could change (most likely, become present).
   186  		watchers = []state.NotifyWatcher{
   187  			st.WatchVolumeAttachment(machineTag, volume.VolumeTag()),
   188  			// TODO(axw) 2015-09-30 #1501203
   189  			// We should filter the events to only those relevant
   190  			// to the volume attachment. This means we would need
   191  			// to either start th block device watcher after we
   192  			// have provisioned the volume attachment (cleaner?),
   193  			// or have the filter ignore changes until the volume
   194  			// attachment is provisioned.
   195  			st.WatchBlockDevices(machineTag),
   196  		}
   197  	case state.StorageKindFilesystem:
   198  		filesystem, err := st.StorageInstanceFilesystem(storageTag)
   199  		if err != nil {
   200  			return nil, errors.Annotate(err, "getting storage filesystem")
   201  		}
   202  		watchers = []state.NotifyWatcher{
   203  			st.WatchFilesystemAttachment(machineTag, filesystem.FilesystemTag()),
   204  		}
   205  	default:
   206  		return nil, errors.Errorf("invalid storage kind %v", storageInstance.Kind())
   207  	}
   208  	watchers = append(watchers, st.WatchStorageAttachment(storageTag, unitTag))
   209  	return common.NewMultiNotifyWatcher(watchers...), nil
   210  }
   211  
   212  // volumeAttachmentDevicePath returns the absolute device path for
   213  // a volume attachment. The value is only meaningful in the context
   214  // of the machine that the volume is attached to.
   215  func volumeAttachmentDevicePath(
   216  	volumeInfo state.VolumeInfo,
   217  	volumeAttachmentInfo state.VolumeAttachmentInfo,
   218  	blockDevice state.BlockDeviceInfo,
   219  ) (string, error) {
   220  	if volumeInfo.HardwareId != "" || volumeAttachmentInfo.DeviceName != "" || volumeAttachmentInfo.DeviceLink != "" {
   221  		// Prefer the volume attachment's information over what is
   222  		// in the published block device information.
   223  		var deviceLinks []string
   224  		if volumeAttachmentInfo.DeviceLink != "" {
   225  			deviceLinks = []string{volumeAttachmentInfo.DeviceLink}
   226  		}
   227  		return storage.BlockDevicePath(storage.BlockDevice{
   228  			HardwareId:  volumeInfo.HardwareId,
   229  			DeviceName:  volumeAttachmentInfo.DeviceName,
   230  			DeviceLinks: deviceLinks,
   231  		})
   232  	}
   233  	return storage.BlockDevicePath(BlockDeviceFromState(blockDevice))
   234  }
   235  
   236  // MaybeAssignedStorageInstance calls the provided function to get a
   237  // StorageTag, and returns the corresponding state.StorageInstance if
   238  // it didn't return an errors.IsNotAssigned error, or nil if it did.
   239  func MaybeAssignedStorageInstance(
   240  	getTag func() (names.StorageTag, error),
   241  	getStorageInstance func(names.StorageTag) (state.StorageInstance, error),
   242  ) (state.StorageInstance, error) {
   243  	tag, err := getTag()
   244  	if err == nil {
   245  		return getStorageInstance(tag)
   246  	} else if errors.IsNotAssigned(err) {
   247  		return nil, nil
   248  	}
   249  	return nil, errors.Trace(err)
   250  }
   251  
   252  // storageTags returns the tags that should be set on a volume or filesystem,
   253  // if the provider supports them.
   254  func storageTags(
   255  	storageInstance state.StorageInstance,
   256  	modelUUID, controllerUUID string,
   257  	tagger tags.ResourceTagger,
   258  ) (map[string]string, error) {
   259  	storageTags := tags.ResourceTags(
   260  		names.NewModelTag(modelUUID),
   261  		names.NewControllerTag(controllerUUID),
   262  		tagger,
   263  	)
   264  	if storageInstance != nil {
   265  		storageTags[tags.JujuStorageInstance] = storageInstance.Tag().Id()
   266  		storageTags[tags.JujuStorageOwner] = storageInstance.Owner().Id()
   267  	}
   268  	return storageTags, nil
   269  }