1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
// Package storagecommon provides common storage-related services
5
// for API server facades.
9
"github.com/juju/errors"
10
"gopkg.in/juju/names.v2"
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"
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)
25
// StorageInstanceFilesystem returns the state.Filesystem assigned
26
// to the storage instance with the specified storage tag.
27
StorageInstanceFilesystem(names.StorageTag) (state.Filesystem, error)
29
// StorageInstanceVolume returns the state.Volume assigned to the
30
// storage instance with the specified storage tag.
31
StorageInstanceVolume(names.StorageTag) (state.Volume, error)
33
// FilesystemAttachment returns the state.FilesystemAttachment
34
// corresponding to the identified machine and filesystem.
35
FilesystemAttachment(names.MachineTag, names.FilesystemTag) (state.FilesystemAttachment, error)
37
// VolumeAttachment returns the state.VolumeAttachment corresponding
38
// to the identified machine and volume.
39
VolumeAttachment(names.MachineTag, names.VolumeTag) (state.VolumeAttachment, error)
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
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
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
53
// WatchBlockDevices watches for changes to block devices associated
54
// with the specified machine.
55
WatchBlockDevices(names.MachineTag) state.NotifyWatcher
57
// BlockDevices returns information about block devices published
58
// for the specified machine.
59
BlockDevices(names.MachineTag) ([]state.BlockDeviceInfo, error)
62
// StorageAttachmentInfo returns the StorageAttachmentInfo for the specified
63
// StorageAttachment by gathering information from related entities (volumes,
66
// StorageAttachmentInfo returns an error satisfying errors.IsNotProvisioned
67
// if the storage attachment is not yet fully provisioned and ready for use
69
func StorageAttachmentInfo(
71
att state.StorageAttachment,
72
machineTag names.MachineTag,
73
) (*storage.StorageAttachmentInfo, error) {
74
storageInstance, err := st.StorageInstance(att.StorageInstance())
76
return nil, errors.Annotate(err, "getting storage instance")
78
switch storageInstance.Kind() {
79
case state.StorageKindBlock:
80
return volumeStorageAttachmentInfo(st, storageInstance, machineTag)
81
case state.StorageKindFilesystem:
82
return filesystemStorageAttachmentInfo(st, storageInstance, machineTag)
84
return nil, errors.Errorf("invalid storage kind %v", storageInstance.Kind())
87
func volumeStorageAttachmentInfo(
89
storageInstance state.StorageInstance,
90
machineTag names.MachineTag,
91
) (*storage.StorageAttachmentInfo, error) {
92
storageTag := storageInstance.StorageTag()
93
volume, err := st.StorageInstanceVolume(storageTag)
95
return nil, errors.Annotate(err, "getting volume")
97
volumeInfo, err := volume.Info()
99
return nil, errors.Annotate(err, "getting volume info")
101
volumeAttachment, err := st.VolumeAttachment(machineTag, volume.VolumeTag())
103
return nil, errors.Annotate(err, "getting volume attachment")
105
volumeAttachmentInfo, err := volumeAttachment.Info()
107
return nil, errors.Annotate(err, "getting volume attachment info")
109
blockDevices, err := st.BlockDevices(machineTag)
111
return nil, errors.Annotate(err, "getting block devices")
113
blockDevice, ok := MatchingBlockDevice(
116
volumeAttachmentInfo,
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
123
return nil, errors.NotProvisionedf("%v", names.ReadableString(storageTag))
125
devicePath, err := volumeAttachmentDevicePath(
127
volumeAttachmentInfo,
131
return nil, errors.Trace(err)
133
return &storage.StorageAttachmentInfo{
134
storage.StorageKindBlock,
139
func filesystemStorageAttachmentInfo(
141
storageInstance state.StorageInstance,
142
machineTag names.MachineTag,
143
) (*storage.StorageAttachmentInfo, error) {
144
storageTag := storageInstance.StorageTag()
145
filesystem, err := st.StorageInstanceFilesystem(storageTag)
147
return nil, errors.Annotate(err, "getting filesystem")
149
filesystemAttachment, err := st.FilesystemAttachment(machineTag, filesystem.FilesystemTag())
151
return nil, errors.Annotate(err, "getting filesystem attachment")
153
filesystemAttachmentInfo, err := filesystemAttachment.Info()
155
return nil, errors.Annotate(err, "getting filesystem attachment info")
157
return &storage.StorageAttachmentInfo{
158
storage.StorageKindFilesystem,
159
filesystemAttachmentInfo.MountPoint,
163
// WatchStorageAttachment returns a state.NotifyWatcher that reacts to changes
164
// to the VolumeAttachmentInfo or FilesystemAttachmentInfo corresponding to the
166
func WatchStorageAttachment(
168
storageTag names.StorageTag,
169
machineTag names.MachineTag,
170
unitTag names.UnitTag,
171
) (state.NotifyWatcher, error) {
172
storageInstance, err := st.StorageInstance(storageTag)
174
return nil, errors.Annotate(err, "getting storage instance")
176
var watchers []state.NotifyWatcher
177
switch storageInstance.Kind() {
178
case state.StorageKindBlock:
179
volume, err := st.StorageInstanceVolume(storageTag)
181
return nil, errors.Annotate(err, "getting storage volume")
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),
197
case state.StorageKindFilesystem:
198
filesystem, err := st.StorageInstanceFilesystem(storageTag)
200
return nil, errors.Annotate(err, "getting storage filesystem")
202
watchers = []state.NotifyWatcher{
203
st.WatchFilesystemAttachment(machineTag, filesystem.FilesystemTag()),
206
return nil, errors.Errorf("invalid storage kind %v", storageInstance.Kind())
208
watchers = append(watchers, st.WatchStorageAttachment(storageTag, unitTag))
209
return common.NewMultiNotifyWatcher(watchers...), nil
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,
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}
227
return storage.BlockDevicePath(storage.BlockDevice{
228
HardwareId: volumeInfo.HardwareId,
229
DeviceName: volumeAttachmentInfo.DeviceName,
230
DeviceLinks: deviceLinks,
233
return storage.BlockDevicePath(BlockDeviceFromState(blockDevice))
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) {
245
return getStorageInstance(tag)
246
} else if errors.IsNotAssigned(err) {
249
return nil, errors.Trace(err)
252
// storageTags returns the tags that should be set on a volume or filesystem,
253
// if the provider supports them.
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.NewModelTag(controllerUUID),
264
if storageInstance != nil {
265
storageTags[tags.JujuStorageInstance] = storageInstance.Tag().Id()
266
storageTags[tags.JujuStorageOwner] = storageInstance.Owner().Id()
268
return storageTags, nil