github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/params"
    13  	"github.com/juju/juju/environs/tags"
    14  	"github.com/juju/juju/state"
    15  	"github.com/juju/juju/storage"
    16  )
    17  
    18  // StorageAccess is an interface for obtaining information about storage
    19  // instances and any associated volume and/or filesystem instances.
    20  type StorageAccess interface {
    21  	// StorageInstance returns the state.StorageInstance corresponding
    22  	// to the specified storage tag.
    23  	StorageInstance(names.StorageTag) (state.StorageInstance, error)
    24  
    25  	// UnitStorageAttachments returns the storage attachments for the
    26  	// specified unit.
    27  	UnitStorageAttachments(names.UnitTag) ([]state.StorageAttachment, error)
    28  }
    29  
    30  // VolumeAccess is an interface for obtaining information about
    31  // block storage instances and related entities.
    32  type VolumeAccess interface {
    33  	// StorageInstanceVolume returns the state.Volume assigned to the
    34  	// storage instance with the specified storage tag.
    35  	StorageInstanceVolume(names.StorageTag) (state.Volume, error)
    36  
    37  	// VolumeAttachment returns the state.VolumeAttachment corresponding
    38  	// to the specified host and volume.
    39  	VolumeAttachment(names.Tag, names.VolumeTag) (state.VolumeAttachment, error)
    40  
    41  	// VolumeAttachmentPlan returns state.VolumeAttachmentPlan corresponding
    42  	// to the specified machine and volume
    43  	VolumeAttachmentPlan(names.Tag, names.VolumeTag) (state.VolumeAttachmentPlan, error)
    44  
    45  	// BlockDevices returns information about block devices published
    46  	// for the specified machine.
    47  	BlockDevices(names.MachineTag) ([]state.BlockDeviceInfo, error)
    48  }
    49  
    50  // FilesystemAccess is an interface for obtaining information about
    51  // filesystem storage instances and related entities.
    52  type FilesystemAccess interface {
    53  	// StorageInstanceFilesystem returns the state.Filesystem assigned
    54  	// to the storage instance with the specified storage tag.
    55  	StorageInstanceFilesystem(names.StorageTag) (state.Filesystem, error)
    56  
    57  	// FilesystemAttachment returns the state.FilesystemAttachment
    58  	// corresponding to the specified host and filesystem.
    59  	FilesystemAttachment(names.Tag, names.FilesystemTag) (state.FilesystemAttachment, error)
    60  }
    61  
    62  // StorageAttachmentInfo is called by the uniter facade to get info needed to
    63  // run storage hooks and also the client facade to display storage info.
    64  
    65  // StorageAttachmentInfo returns the StorageAttachmentInfo for the specified
    66  // StorageAttachment by gathering information from related entities (volumes,
    67  // filesystems).
    68  //
    69  // StorageAttachmentInfo returns an error satisfying errors.IsNotProvisioned
    70  // if the storage attachment is not yet fully provisioned and ready for use
    71  // by a charm.
    72  func StorageAttachmentInfo(
    73  	st StorageAccess,
    74  	stVolume VolumeAccess,
    75  	stFile FilesystemAccess,
    76  	att state.StorageAttachment,
    77  	hostTag names.Tag,
    78  ) (*storage.StorageAttachmentInfo, error) {
    79  	storageInstance, err := st.StorageInstance(att.StorageInstance())
    80  	if err != nil {
    81  		return nil, errors.Annotate(err, "getting storage instance")
    82  	}
    83  	switch storageInstance.Kind() {
    84  	case state.StorageKindBlock:
    85  		if stVolume == nil {
    86  			return nil, errors.NotImplementedf("BlockStorage instance")
    87  		}
    88  		return volumeStorageAttachmentInfo(stVolume, storageInstance, hostTag)
    89  	case state.StorageKindFilesystem:
    90  		if stFile == nil {
    91  			return nil, errors.NotImplementedf("FilesystemStorage instance")
    92  		}
    93  		return filesystemStorageAttachmentInfo(stFile, storageInstance, hostTag)
    94  	}
    95  	return nil, errors.Errorf("invalid storage kind %v", storageInstance.Kind())
    96  }
    97  
    98  func volumeStorageAttachmentInfo(
    99  	st VolumeAccess,
   100  	storageInstance state.StorageInstance,
   101  	hostTag names.Tag,
   102  ) (*storage.StorageAttachmentInfo, error) {
   103  	storageTag := storageInstance.StorageTag()
   104  	volume, err := st.StorageInstanceVolume(storageTag)
   105  	if errors.IsNotFound(err) {
   106  		// If the unit of the storage attachment is not
   107  		// assigned to a machine, there will be no volume
   108  		// yet. Handle this gracefully by saying that the
   109  		// volume is not yet provisioned.
   110  		return nil, errors.NotProvisionedf("volume for storage %q", storageTag.Id())
   111  	} else if err != nil {
   112  		return nil, errors.Annotate(err, "getting volume")
   113  	}
   114  	volumeInfo, err := volume.Info()
   115  	if err != nil {
   116  		return nil, errors.Annotate(err, "getting volume info")
   117  	}
   118  	volumeAttachment, err := st.VolumeAttachment(hostTag, volume.VolumeTag())
   119  	if err != nil {
   120  		return nil, errors.Annotate(err, "getting volume attachment")
   121  	}
   122  	volumeAttachmentInfo, err := volumeAttachment.Info()
   123  	if err != nil {
   124  		return nil, errors.Annotate(err, "getting volume attachment info")
   125  	}
   126  
   127  	blockDeviceInfo := state.BlockDeviceInfo{}
   128  	volumeAttachmentPlan, err := st.VolumeAttachmentPlan(hostTag, volume.VolumeTag())
   129  	if err != nil {
   130  		if !errors.IsNotFound(err) {
   131  			return nil, errors.Annotate(err, "getting attachment plans")
   132  		}
   133  	} else {
   134  		blockDeviceInfo, err = volumeAttachmentPlan.BlockDeviceInfo()
   135  		if err != nil {
   136  			if !errors.IsNotFound(err) {
   137  				return nil, errors.Annotate(err, "getting block device info")
   138  			}
   139  		}
   140  	}
   141  
   142  	// TODO(caas) - we currently only support block devices on machines.
   143  	if hostTag.Kind() != names.MachineTagKind {
   144  		return nil, errors.NotProvisionedf("%v", names.ReadableString(storageTag))
   145  	}
   146  	blockDevices, err := st.BlockDevices(hostTag.(names.MachineTag))
   147  	if err != nil {
   148  		return nil, errors.Annotate(err, "getting block devices")
   149  	}
   150  	blockDevice, ok := MatchingBlockDevice(
   151  		blockDevices,
   152  		volumeInfo,
   153  		volumeAttachmentInfo,
   154  		blockDeviceInfo,
   155  	)
   156  	if !ok {
   157  		// We must not say that a block-kind storage attachment is
   158  		// provisioned until its block device has shown up on the
   159  		// machine, otherwise the charm may attempt to use it and
   160  		// fail.
   161  		return nil, errors.NotProvisionedf("%v", names.ReadableString(storageTag))
   162  	}
   163  	devicePath, err := volumeAttachmentDevicePath(
   164  		volumeInfo,
   165  		volumeAttachmentInfo,
   166  		*blockDevice,
   167  	)
   168  	if err != nil {
   169  		return nil, errors.Trace(err)
   170  	}
   171  	return &storage.StorageAttachmentInfo{
   172  		storage.StorageKindBlock,
   173  		devicePath,
   174  	}, nil
   175  }
   176  
   177  func filesystemStorageAttachmentInfo(
   178  	st FilesystemAccess,
   179  	storageInstance state.StorageInstance,
   180  	hostTag names.Tag,
   181  ) (*storage.StorageAttachmentInfo, error) {
   182  	storageTag := storageInstance.StorageTag()
   183  	filesystem, err := st.StorageInstanceFilesystem(storageTag)
   184  	if errors.IsNotFound(err) {
   185  		// If the unit of the storage attachment is not
   186  		// assigned to a machine, there will be no filesystem
   187  		// yet. Handle this gracefully by saying that the
   188  		// filesystem is not yet provisioned.
   189  		return nil, errors.NotProvisionedf("filesystem for storage %q", storageTag.Id())
   190  	} else if err != nil {
   191  		return nil, errors.Annotate(err, "getting filesystem")
   192  	}
   193  	filesystemAttachment, err := st.FilesystemAttachment(hostTag, filesystem.FilesystemTag())
   194  	if err != nil {
   195  		return nil, errors.Annotate(err, "getting filesystem attachment")
   196  	}
   197  	filesystemAttachmentInfo, err := filesystemAttachment.Info()
   198  	if err != nil {
   199  		return nil, errors.Annotate(err, "getting filesystem attachment info")
   200  	}
   201  	return &storage.StorageAttachmentInfo{
   202  		storage.StorageKindFilesystem,
   203  		filesystemAttachmentInfo.MountPoint,
   204  	}, nil
   205  }
   206  
   207  // volumeAttachmentDevicePath returns the absolute device path for
   208  // a volume attachment. The value is only meaningful in the context
   209  // of the machine that the volume is attached to.
   210  func volumeAttachmentDevicePath(
   211  	volumeInfo state.VolumeInfo,
   212  	volumeAttachmentInfo state.VolumeAttachmentInfo,
   213  	blockDevice state.BlockDeviceInfo,
   214  ) (string, error) {
   215  	if volumeInfo.HardwareId != "" ||
   216  		volumeInfo.WWN != "" ||
   217  		volumeAttachmentInfo.DeviceName != "" ||
   218  		volumeAttachmentInfo.DeviceLink != "" {
   219  		// Prefer the volume attachment's information over what is
   220  		// in the published block device information, but only if the
   221  		// block device information actually has any device links. In
   222  		// some cases, the block device has very little hw info published.
   223  		var deviceLinks []string
   224  		if volumeAttachmentInfo.DeviceLink != "" && len(blockDevice.DeviceLinks) > 0 {
   225  			deviceLinks = []string{volumeAttachmentInfo.DeviceLink}
   226  		}
   227  		var deviceName string
   228  		if blockDevice.DeviceName != "" {
   229  			deviceName = blockDevice.DeviceName
   230  		} else {
   231  			deviceName = volumeAttachmentInfo.DeviceName
   232  		}
   233  		return storage.BlockDevicePath(storage.BlockDevice{
   234  			HardwareId:  volumeInfo.HardwareId,
   235  			WWN:         volumeInfo.WWN,
   236  			DeviceName:  deviceName,
   237  			DeviceLinks: deviceLinks,
   238  		})
   239  	}
   240  	return storage.BlockDevicePath(BlockDeviceFromState(blockDevice))
   241  }
   242  
   243  // Called by agent/provisioner and storageprovisioner.
   244  // agent/provisioner so that params used to create a machine
   245  // are augmented with the volumes to be attached at creation time.
   246  // storageprovisioner to provide resource tags for new volumes.
   247  
   248  // MaybeAssignedStorageInstance calls the provided function to get a
   249  // StorageTag, and returns the corresponding state.StorageInstance if
   250  // it didn't return an errors.IsNotAssigned error, or nil if it did.
   251  func MaybeAssignedStorageInstance(
   252  	getTag func() (names.StorageTag, error),
   253  	getStorageInstance func(names.StorageTag) (state.StorageInstance, error),
   254  ) (state.StorageInstance, error) {
   255  	tag, err := getTag()
   256  	if err == nil {
   257  		return getStorageInstance(tag)
   258  	} else if errors.IsNotAssigned(err) {
   259  		return nil, nil
   260  	}
   261  	return nil, errors.Trace(err)
   262  }
   263  
   264  // StorageTags returns the tags that should be set on a volume or filesystem,
   265  // if the provider supports them.
   266  func StorageTags(
   267  	storageInstance state.StorageInstance,
   268  	modelUUID, controllerUUID string,
   269  	tagger tags.ResourceTagger,
   270  ) (map[string]string, error) {
   271  	storageTags := tags.ResourceTags(
   272  		names.NewModelTag(modelUUID),
   273  		names.NewControllerTag(controllerUUID),
   274  		tagger,
   275  	)
   276  	if storageInstance != nil {
   277  		storageTags[tags.JujuStorageInstance] = storageInstance.Tag().Id()
   278  		if owner, ok := storageInstance.Owner(); ok {
   279  			storageTags[tags.JujuStorageOwner] = owner.Id()
   280  		}
   281  	}
   282  	return storageTags, nil
   283  }
   284  
   285  // These methods are used by ModelManager and Application facades
   286  // when destroying models and applications/units.
   287  
   288  // UnitStorage returns the storage instances attached to the specified unit.
   289  func UnitStorage(st StorageAccess, unit names.UnitTag) ([]state.StorageInstance, error) {
   290  	attachments, err := st.UnitStorageAttachments(unit)
   291  	if err != nil {
   292  		return nil, errors.Trace(err)
   293  	}
   294  	instances := make([]state.StorageInstance, 0, len(attachments))
   295  	for _, attachment := range attachments {
   296  		instance, err := st.StorageInstance(attachment.StorageInstance())
   297  		if errors.IsNotFound(err) {
   298  			continue
   299  		} else if err != nil {
   300  			return nil, errors.Trace(err)
   301  		}
   302  		instances = append(instances, instance)
   303  	}
   304  	return instances, nil
   305  }
   306  
   307  // ClassifyDetachedStorage classifies storage instances into those that will
   308  // be destroyed, and those that will be detached, when their attachment is
   309  // removed. Any storage that is not found will be omitted.
   310  func ClassifyDetachedStorage(
   311  	stVolume VolumeAccess,
   312  	stFile FilesystemAccess,
   313  	storage []state.StorageInstance,
   314  ) (destroyed, detached []params.Entity, _ error) {
   315  	for _, storage := range storage {
   316  		var detachable bool
   317  		switch storage.Kind() {
   318  		case state.StorageKindFilesystem:
   319  			if stFile == nil {
   320  				return nil, nil, errors.NotImplementedf("FilesystemStorage instance")
   321  			}
   322  			f, err := stFile.StorageInstanceFilesystem(storage.StorageTag())
   323  			if errors.IsNotFound(err) {
   324  				continue
   325  			} else if err != nil {
   326  				return nil, nil, err
   327  			}
   328  			detachable = f.Detachable()
   329  		case state.StorageKindBlock:
   330  			if stVolume == nil {
   331  				return nil, nil, errors.NotImplementedf("BlockStorage instance")
   332  			}
   333  			v, err := stVolume.StorageInstanceVolume(storage.StorageTag())
   334  			if errors.IsNotFound(err) {
   335  				continue
   336  			} else if err != nil {
   337  				return nil, nil, err
   338  			}
   339  			detachable = v.Detachable()
   340  		default:
   341  			return nil, nil, errors.NotValidf("storage kind %s", storage.Kind())
   342  		}
   343  		entity := params.Entity{storage.StorageTag().String()}
   344  		if detachable {
   345  			detached = append(detached, entity)
   346  		} else {
   347  			destroyed = append(destroyed, entity)
   348  		}
   349  	}
   350  	return destroyed, detached, nil
   351  }