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