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 }