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 }