github.com/gophercloud/gophercloud@v1.11.0/internal/acceptance/openstack/blockstorage/extensions/extensions.go (about) 1 // Package extensions contains common functions for creating block storage 2 // resources that are extensions of the block storage API. See the `*_test.go` 3 // files for example usages. 4 package extensions 5 6 import ( 7 "fmt" 8 "strings" 9 "testing" 10 11 "github.com/gophercloud/gophercloud" 12 "github.com/gophercloud/gophercloud/internal/acceptance/tools" 13 "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/backups" 14 "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" 15 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" 16 v3 "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" 17 "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes" 18 "github.com/gophercloud/gophercloud/openstack/compute/v2/images" 19 "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" 20 th "github.com/gophercloud/gophercloud/testhelper" 21 ) 22 23 // CreateUploadImage will upload volume it as volume-baked image. An name of new image or err will be 24 // returned 25 func CreateUploadImage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (volumeactions.VolumeImage, error) { 26 if testing.Short() { 27 t.Skip("Skipping test that requires volume-backed image uploading in short mode.") 28 } 29 30 imageName := tools.RandomString("ACPTTEST", 16) 31 uploadImageOpts := volumeactions.UploadImageOpts{ 32 ImageName: imageName, 33 Force: true, 34 } 35 36 volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts).Extract() 37 if err != nil { 38 return volumeImage, err 39 } 40 41 t.Logf("Uploading volume %s as volume-backed image %s", volume.ID, imageName) 42 43 if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { 44 return volumeImage, err 45 } 46 47 t.Logf("Uploaded volume %s as volume-backed image %s", volume.ID, imageName) 48 49 return volumeImage, nil 50 51 } 52 53 // DeleteUploadedImage deletes uploaded image. An error will be returned 54 // if the deletion request failed. 55 func DeleteUploadedImage(t *testing.T, client *gophercloud.ServiceClient, imageID string) error { 56 if testing.Short() { 57 t.Skip("Skipping test that requires volume-backed image removing in short mode.") 58 } 59 60 t.Logf("Removing image %s", imageID) 61 62 err := images.Delete(client, imageID).ExtractErr() 63 if err != nil { 64 return err 65 } 66 67 return nil 68 } 69 70 // CreateVolumeAttach will attach a volume to an instance. An error will be 71 // returned if the attachment failed. 72 func CreateVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, server *servers.Server) error { 73 if testing.Short() { 74 t.Skip("Skipping test that requires volume attachment in short mode.") 75 } 76 77 attachOpts := volumeactions.AttachOpts{ 78 MountPoint: "/mnt", 79 Mode: "rw", 80 InstanceUUID: server.ID, 81 } 82 83 t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID) 84 85 if err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr(); err != nil { 86 return err 87 } 88 89 if err := volumes.WaitForStatus(client, volume.ID, "in-use", 60); err != nil { 90 return err 91 } 92 93 t.Logf("Attached volume %s to server %s", volume.ID, server.ID) 94 95 return nil 96 } 97 98 // CreateVolumeReserve creates a volume reservation. An error will be returned 99 // if the reservation failed. 100 func CreateVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 101 if testing.Short() { 102 t.Skip("Skipping test that requires volume reservation in short mode.") 103 } 104 105 t.Logf("Attempting to reserve volume %s", volume.ID) 106 107 if err := volumeactions.Reserve(client, volume.ID).ExtractErr(); err != nil { 108 return err 109 } 110 111 t.Logf("Reserved volume %s", volume.ID) 112 113 return nil 114 } 115 116 // DeleteVolumeAttach will detach a volume from an instance. A fatal error will 117 // occur if the snapshot failed to be deleted. This works best when used as a 118 // deferred function. 119 func DeleteVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { 120 t.Logf("Attepting to detach volume volume: %s", volume.ID) 121 122 detachOpts := volumeactions.DetachOpts{ 123 AttachmentID: volume.Attachments[0].AttachmentID, 124 } 125 126 if err := volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr(); err != nil { 127 t.Fatalf("Unable to detach volume %s: %v", volume.ID, err) 128 } 129 130 if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { 131 t.Fatalf("Volume %s failed to become unavailable in 60 seconds: %v", volume.ID, err) 132 } 133 134 t.Logf("Detached volume: %s", volume.ID) 135 } 136 137 // DeleteVolumeReserve deletes a volume reservation. A fatal error will occur 138 // if the deletion request failed. This works best when used as a deferred 139 // function. 140 func DeleteVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { 141 if testing.Short() { 142 t.Skip("Skipping test that requires volume reservation in short mode.") 143 } 144 145 t.Logf("Attempting to unreserve volume %s", volume.ID) 146 147 if err := volumeactions.Unreserve(client, volume.ID).ExtractErr(); err != nil { 148 t.Fatalf("Unable to unreserve volume %s: %v", volume.ID, err) 149 } 150 151 t.Logf("Unreserved volume %s", volume.ID) 152 } 153 154 // ExtendVolumeSize will extend the size of a volume. 155 func ExtendVolumeSize(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 156 t.Logf("Attempting to extend the size of volume %s", volume.ID) 157 158 extendOpts := volumeactions.ExtendSizeOpts{ 159 NewSize: 2, 160 } 161 162 err := volumeactions.ExtendSize(client, volume.ID, extendOpts).ExtractErr() 163 if err != nil { 164 return err 165 } 166 167 if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { 168 return err 169 } 170 171 return nil 172 } 173 174 // SetImageMetadata will apply the metadata to a volume. 175 func SetImageMetadata(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 176 t.Logf("Attempting to apply image metadata to volume %s", volume.ID) 177 178 imageMetadataOpts := volumeactions.ImageMetadataOpts{ 179 Metadata: map[string]string{ 180 "image_name": "testimage", 181 }, 182 } 183 184 err := volumeactions.SetImageMetadata(client, volume.ID, imageMetadataOpts).ExtractErr() 185 if err != nil { 186 return err 187 } 188 189 return nil 190 } 191 192 // CreateBackup will create a backup based on a volume. An error will be 193 // will be returned if the backup could not be created. 194 func CreateBackup(t *testing.T, client *gophercloud.ServiceClient, volumeID string) (*backups.Backup, error) { 195 t.Logf("Attempting to create a backup of volume %s", volumeID) 196 197 backupName := tools.RandomString("ACPTTEST", 16) 198 createOpts := backups.CreateOpts{ 199 VolumeID: volumeID, 200 Name: backupName, 201 } 202 203 backup, err := backups.Create(client, createOpts).Extract() 204 if err != nil { 205 return nil, err 206 } 207 208 err = WaitForBackupStatus(client, backup.ID, "available") 209 if err != nil { 210 return nil, err 211 } 212 213 backup, err = backups.Get(client, backup.ID).Extract() 214 if err != nil { 215 return nil, err 216 } 217 218 t.Logf("Successfully created backup %s", backup.ID) 219 tools.PrintResource(t, backup) 220 221 th.AssertEquals(t, backup.Name, backupName) 222 223 return backup, nil 224 } 225 226 // DeleteBackup will delete a backup. A fatal error will occur if the backup 227 // could not be deleted. This works best when used as a deferred function. 228 func DeleteBackup(t *testing.T, client *gophercloud.ServiceClient, backupID string) { 229 if err := backups.Delete(client, backupID).ExtractErr(); err != nil { 230 t.Fatalf("Unable to delete backup %s: %s", backupID, err) 231 } 232 233 t.Logf("Deleted backup %s", backupID) 234 } 235 236 // WaitForBackupStatus will continually poll a backup, checking for a particular 237 // status. It will do this for the amount of seconds defined. 238 func WaitForBackupStatus(client *gophercloud.ServiceClient, id, status string) error { 239 return tools.WaitFor(func() (bool, error) { 240 current, err := backups.Get(client, id).Extract() 241 if err != nil { 242 return false, err 243 } 244 245 if current.Status == status { 246 return true, nil 247 } 248 249 return false, nil 250 }) 251 } 252 253 // SetBootable will set a bootable status to a volume. 254 func SetBootable(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 255 t.Logf("Attempting to apply bootable status to volume %s", volume.ID) 256 257 bootableOpts := volumeactions.BootableOpts{ 258 Bootable: true, 259 } 260 261 err := volumeactions.SetBootable(client, volume.ID, bootableOpts).ExtractErr() 262 if err != nil { 263 return err 264 } 265 266 vol, err := v3.Get(client, volume.ID).Extract() 267 if err != nil { 268 return err 269 } 270 271 if strings.ToLower(vol.Bootable) != "true" { 272 return fmt.Errorf("Volume bootable status is %q, expected 'true'", vol.Bootable) 273 } 274 275 bootableOpts = volumeactions.BootableOpts{ 276 Bootable: false, 277 } 278 279 err = volumeactions.SetBootable(client, volume.ID, bootableOpts).ExtractErr() 280 if err != nil { 281 return err 282 } 283 284 vol, err = v3.Get(client, volume.ID).Extract() 285 if err != nil { 286 return err 287 } 288 289 if strings.ToLower(vol.Bootable) == "true" { 290 return fmt.Errorf("Volume bootable status is %q, expected 'false'", vol.Bootable) 291 } 292 293 return nil 294 } 295 296 // ChangeVolumeType will extend the size of a volume. 297 func ChangeVolumeType(t *testing.T, client *gophercloud.ServiceClient, volume *v3.Volume, vt *volumetypes.VolumeType) error { 298 t.Logf("Attempting to change the type of volume %s from %s to %s", volume.ID, volume.VolumeType, vt.Name) 299 300 changeOpts := volumeactions.ChangeTypeOpts{ 301 NewType: vt.Name, 302 MigrationPolicy: volumeactions.MigrationPolicyOnDemand, 303 } 304 305 err := volumeactions.ChangeType(client, volume.ID, changeOpts).ExtractErr() 306 if err != nil { 307 return err 308 } 309 310 if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { 311 return err 312 } 313 314 return nil 315 } 316 317 // ResetVolumeStatus will reset the status of a volume. 318 func ResetVolumeStatus(t *testing.T, client *gophercloud.ServiceClient, volume *v3.Volume, status string) error { 319 t.Logf("Attempting to reset the status of volume %s from %s to %s", volume.ID, volume.Status, status) 320 321 resetOpts := volumeactions.ResetStatusOpts{ 322 Status: status, 323 } 324 err := volumeactions.ResetStatus(client, volume.ID, resetOpts).ExtractErr() 325 if err != nil { 326 return err 327 } 328 329 if err := volumes.WaitForStatus(client, volume.ID, status, 60); err != nil { 330 return err 331 } 332 333 return nil 334 } 335 336 // ReImage will re-image a volume 337 func ReImage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, imageID string) error { 338 t.Logf("Attempting to re-image volume %s", volume.ID) 339 340 reimageOpts := volumeactions.ReImageOpts{ 341 ImageID: imageID, 342 ReImageReserved: false, 343 } 344 345 err := volumeactions.ReImage(client, volume.ID, reimageOpts).ExtractErr() 346 if err != nil { 347 return err 348 } 349 350 err = volumes.WaitForStatus(client, volume.ID, "available", 60) 351 if err != nil { 352 return err 353 } 354 355 vol, err := v3.Get(client, volume.ID).Extract() 356 if err != nil { 357 return err 358 } 359 360 if vol.VolumeImageMetadata == nil { 361 return fmt.Errorf("volume does not have VolumeImageMetadata map") 362 } 363 364 if strings.ToLower(vol.VolumeImageMetadata["image_id"]) != imageID { 365 return fmt.Errorf("volume image id '%s', expected '%s'", vol.VolumeImageMetadata["image_id"], imageID) 366 } 367 368 return nil 369 }