github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/internal/acceptance/openstack/blockstorage/v2/blockstorage.go (about) 1 // Package v2 contains common functions for creating block storage based 2 // resources for use in acceptance tests. See the `*_test.go` files for 3 // example usages. 4 package v2 5 6 import ( 7 "context" 8 "fmt" 9 "net/http" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/vnpaycloud-console/gophercloud/v2" 15 "github.com/vnpaycloud-console/gophercloud/v2/internal/acceptance/clients" 16 "github.com/vnpaycloud-console/gophercloud/v2/internal/acceptance/tools" 17 "github.com/vnpaycloud-console/gophercloud/v2/openstack/blockstorage/v2/backups" 18 "github.com/vnpaycloud-console/gophercloud/v2/openstack/blockstorage/v2/snapshots" 19 "github.com/vnpaycloud-console/gophercloud/v2/openstack/blockstorage/v2/volumes" 20 "github.com/vnpaycloud-console/gophercloud/v2/openstack/compute/v2/servers" 21 "github.com/vnpaycloud-console/gophercloud/v2/openstack/image/v2/images" 22 th "github.com/vnpaycloud-console/gophercloud/v2/testhelper" 23 ) 24 25 // CreateSnapshot will create a snapshot of the specified volume. 26 // Snapshot will be assigned a random name and description. 27 func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) { 28 snapshotName := tools.RandomString("ACPTTEST", 16) 29 snapshotDescription := tools.RandomString("ACPTTEST", 16) 30 t.Logf("Attempting to create snapshot: %s", snapshotName) 31 32 createOpts := snapshots.CreateOpts{ 33 VolumeID: volume.ID, 34 Name: snapshotName, 35 Description: snapshotDescription, 36 } 37 38 snapshot, err := snapshots.Create(context.TODO(), client, createOpts).Extract() 39 if err != nil { 40 return snapshot, err 41 } 42 43 ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) 44 defer cancel() 45 46 err = snapshots.WaitForStatus(ctx, client, snapshot.ID, "available") 47 if err != nil { 48 return snapshot, err 49 } 50 51 t.Logf("Successfully created snapshot: %s", snapshot.ID) 52 53 return snapshot, nil 54 } 55 56 // CreateVolume will create a volume with a random name and size of 1GB. An 57 // error will be returned if the volume was unable to be created. 58 func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) { 59 volumeName := tools.RandomString("ACPTTEST", 16) 60 volumeDescription := tools.RandomString("ACPTTEST-DESC", 16) 61 t.Logf("Attempting to create volume: %s", volumeName) 62 63 createOpts := volumes.CreateOpts{ 64 Size: 1, 65 Name: volumeName, 66 Description: volumeDescription, 67 } 68 69 volume, err := volumes.Create(context.TODO(), client, createOpts, nil).Extract() 70 if err != nil { 71 return volume, err 72 } 73 74 ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) 75 defer cancel() 76 77 err = volumes.WaitForStatus(ctx, client, volume.ID, "available") 78 if err != nil { 79 return volume, err 80 } 81 82 tools.PrintResource(t, volume) 83 th.AssertEquals(t, volume.Name, volumeName) 84 th.AssertEquals(t, volume.Description, volumeDescription) 85 th.AssertEquals(t, volume.Size, 1) 86 87 t.Logf("Successfully created volume: %s", volume.ID) 88 89 return volume, nil 90 } 91 92 // CreateVolumeFromImage will create a volume from with a random name and size of 93 // 1GB. An error will be returned if the volume was unable to be created. 94 func CreateVolumeFromImage(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Volume, error) { 95 choices, err := clients.AcceptanceTestChoicesFromEnv() 96 if err != nil { 97 t.Fatal(err) 98 } 99 100 volumeName := tools.RandomString("ACPTTEST", 16) 101 t.Logf("Attempting to create volume: %s", volumeName) 102 103 createOpts := volumes.CreateOpts{ 104 Size: 1, 105 Name: volumeName, 106 ImageID: choices.ImageID, 107 } 108 109 volume, err := volumes.Create(context.TODO(), client, createOpts, nil).Extract() 110 if err != nil { 111 return volume, err 112 } 113 114 ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) 115 defer cancel() 116 117 err = volumes.WaitForStatus(ctx, client, volume.ID, "available") 118 if err != nil { 119 return volume, err 120 } 121 122 newVolume, err := volumes.Get(context.TODO(), client, volume.ID).Extract() 123 if err != nil { 124 return nil, err 125 } 126 127 th.AssertEquals(t, newVolume.Name, volumeName) 128 th.AssertEquals(t, newVolume.Size, 1) 129 130 t.Logf("Successfully created volume from image: %s", newVolume.ID) 131 132 return newVolume, nil 133 } 134 135 // DeleteVolume will delete a volume. A fatal error will occur if the volume 136 // failed to be deleted. This works best when used as a deferred function. 137 func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { 138 t.Logf("Attempting to delete volume: %s", volume.ID) 139 140 err := volumes.Delete(context.TODO(), client, volume.ID, volumes.DeleteOpts{}).ExtractErr() 141 if err != nil { 142 t.Fatalf("Unable to delete volume %s: %v", volume.ID, err) 143 } 144 145 t.Logf("Successfully deleted volume: %s", volume.ID) 146 } 147 148 // DeleteSnapshot will delete a snapshot. A fatal error will occur if the 149 // snapshot failed to be deleted. 150 func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) { 151 t.Logf("Attempting to delete snapshot: %s", snapshot.ID) 152 153 err := snapshots.Delete(context.TODO(), client, snapshot.ID).ExtractErr() 154 if err != nil { 155 t.Fatalf("Unable to delete snapshot %s: %+v", snapshot.ID, err) 156 } 157 158 // Volumes can't be deleted until their snapshots have been, 159 // so block until the snapshot is deleted. 160 err = tools.WaitFor(func(ctx context.Context) (bool, error) { 161 _, err := snapshots.Get(ctx, client, snapshot.ID).Extract() 162 if err != nil { 163 return true, nil 164 } 165 166 return false, nil 167 }) 168 if err != nil { 169 t.Fatalf("Error waiting for snapshot to delete: %v", err) 170 } 171 172 t.Logf("Successfully deleted snapshot: %s", snapshot.ID) 173 } 174 175 // CreateBackup will create a backup based on a volume. An error will be 176 // will be returned if the backup could not be created. 177 func CreateBackup(t *testing.T, client *gophercloud.ServiceClient, volumeID string) (*backups.Backup, error) { 178 t.Logf("Attempting to create a backup of volume %s", volumeID) 179 180 backupName := tools.RandomString("ACPTTEST", 16) 181 createOpts := backups.CreateOpts{ 182 VolumeID: volumeID, 183 Name: backupName, 184 } 185 186 backup, err := backups.Create(context.TODO(), client, createOpts).Extract() 187 if err != nil { 188 return nil, err 189 } 190 191 err = WaitForBackupStatus(client, backup.ID, "available") 192 if err != nil { 193 return nil, err 194 } 195 196 backup, err = backups.Get(context.TODO(), client, backup.ID).Extract() 197 if err != nil { 198 return nil, err 199 } 200 201 t.Logf("Successfully created backup %s", backup.ID) 202 tools.PrintResource(t, backup) 203 204 th.AssertEquals(t, backup.Name, backupName) 205 206 return backup, nil 207 } 208 209 // DeleteBackup will delete a backup. A fatal error will occur if the backup 210 // could not be deleted. This works best when used as a deferred function. 211 func DeleteBackup(t *testing.T, client *gophercloud.ServiceClient, backupID string) { 212 if err := backups.Delete(context.TODO(), client, backupID).ExtractErr(); err != nil { 213 if gophercloud.ResponseCodeIs(err, http.StatusNotFound) { 214 t.Logf("Backup %s is already deleted", backupID) 215 return 216 } 217 t.Fatalf("Unable to delete backup %s: %s", backupID, err) 218 } 219 220 t.Logf("Deleted backup %s", backupID) 221 } 222 223 // WaitForBackupStatus will continually poll a backup, checking for a particular 224 // status. It will do this for the amount of seconds defined. 225 func WaitForBackupStatus(client *gophercloud.ServiceClient, id, status string) error { 226 return tools.WaitFor(func(ctx context.Context) (bool, error) { 227 current, err := backups.Get(ctx, client, id).Extract() 228 if err != nil { 229 if gophercloud.ResponseCodeIs(err, http.StatusNotFound) && status == "deleted" { 230 return true, nil 231 } 232 return false, err 233 } 234 235 if current.Status == status { 236 return true, nil 237 } 238 239 return false, nil 240 }) 241 } 242 243 // ResetBackupStatus will reset the status of a backup. 244 func ResetBackupStatus(t *testing.T, client *gophercloud.ServiceClient, backup *backups.Backup, status string) error { 245 t.Logf("Attempting to reset the status of backup %s from %s to %s", backup.ID, backup.Status, status) 246 247 resetOpts := backups.ResetStatusOpts{ 248 Status: status, 249 } 250 err := backups.ResetStatus(context.TODO(), client, backup.ID, resetOpts).ExtractErr() 251 if err != nil { 252 return err 253 } 254 255 return WaitForBackupStatus(client, backup.ID, status) 256 } 257 258 // CreateUploadImage will upload volume it as volume-baked image. An name of new image or err will be 259 // returned 260 func CreateUploadImage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (volumes.VolumeImage, error) { 261 if testing.Short() { 262 t.Skip("Skipping test that requires volume-backed image uploading in short mode.") 263 } 264 265 imageName := tools.RandomString("ACPTTEST", 16) 266 uploadImageOpts := volumes.UploadImageOpts{ 267 ImageName: imageName, 268 Force: true, 269 } 270 271 volumeImage, err := volumes.UploadImage(context.TODO(), client, volume.ID, uploadImageOpts).Extract() 272 if err != nil { 273 return volumeImage, err 274 } 275 276 t.Logf("Uploading volume %s as volume-backed image %s", volume.ID, imageName) 277 278 ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) 279 defer cancel() 280 281 if err := volumes.WaitForStatus(ctx, client, volume.ID, "available"); err != nil { 282 return volumeImage, err 283 } 284 285 t.Logf("Uploaded volume %s as volume-backed image %s", volume.ID, imageName) 286 287 return volumeImage, nil 288 289 } 290 291 // DeleteUploadedImage deletes uploaded image. An error will be returned 292 // if the deletion request failed. 293 func DeleteUploadedImage(t *testing.T, client *gophercloud.ServiceClient, imageID string) error { 294 if testing.Short() { 295 t.Skip("Skipping test that requires volume-backed image removing in short mode.") 296 } 297 298 t.Logf("Removing image %s", imageID) 299 300 err := images.Delete(context.TODO(), client, imageID).ExtractErr() 301 if err != nil { 302 return err 303 } 304 305 return nil 306 } 307 308 // CreateVolumeAttach will attach a volume to an instance. An error will be 309 // returned if the attachment failed. 310 func CreateVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, server *servers.Server) error { 311 if testing.Short() { 312 t.Skip("Skipping test that requires volume attachment in short mode.") 313 } 314 315 attachOpts := volumes.AttachOpts{ 316 MountPoint: "/mnt", 317 Mode: "rw", 318 InstanceUUID: server.ID, 319 } 320 321 t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID) 322 323 if err := volumes.Attach(context.TODO(), client, volume.ID, attachOpts).ExtractErr(); err != nil { 324 return err 325 } 326 327 ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) 328 defer cancel() 329 330 if err := volumes.WaitForStatus(ctx, client, volume.ID, "in-use"); err != nil { 331 return err 332 } 333 334 t.Logf("Attached volume %s to server %s", volume.ID, server.ID) 335 336 return nil 337 } 338 339 // CreateVolumeReserve creates a volume reservation. An error will be returned 340 // if the reservation failed. 341 func CreateVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 342 if testing.Short() { 343 t.Skip("Skipping test that requires volume reservation in short mode.") 344 } 345 346 t.Logf("Attempting to reserve volume %s", volume.ID) 347 348 if err := volumes.Reserve(context.TODO(), client, volume.ID).ExtractErr(); err != nil { 349 return err 350 } 351 352 t.Logf("Reserved volume %s", volume.ID) 353 354 return nil 355 } 356 357 // DeleteVolumeAttach will detach a volume from an instance. A fatal error will 358 // occur if the snapshot failed to be deleted. This works best when used as a 359 // deferred function. 360 func DeleteVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { 361 t.Logf("Attepting to detach volume volume: %s", volume.ID) 362 363 detachOpts := volumes.DetachOpts{ 364 AttachmentID: volume.Attachments[0].AttachmentID, 365 } 366 367 if err := volumes.Detach(context.TODO(), client, volume.ID, detachOpts).ExtractErr(); err != nil { 368 t.Fatalf("Unable to detach volume %s: %v", volume.ID, err) 369 } 370 371 ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) 372 defer cancel() 373 374 if err := volumes.WaitForStatus(ctx, client, volume.ID, "available"); err != nil { 375 t.Fatalf("Volume %s failed to become unavailable in 60 seconds: %v", volume.ID, err) 376 } 377 378 t.Logf("Detached volume: %s", volume.ID) 379 } 380 381 // DeleteVolumeReserve deletes a volume reservation. A fatal error will occur 382 // if the deletion request failed. This works best when used as a deferred 383 // function. 384 func DeleteVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { 385 if testing.Short() { 386 t.Skip("Skipping test that requires volume reservation in short mode.") 387 } 388 389 t.Logf("Attempting to unreserve volume %s", volume.ID) 390 391 if err := volumes.Unreserve(context.TODO(), client, volume.ID).ExtractErr(); err != nil { 392 t.Fatalf("Unable to unreserve volume %s: %v", volume.ID, err) 393 } 394 395 t.Logf("Unreserved volume %s", volume.ID) 396 } 397 398 // ExtendVolumeSize will extend the size of a volume. 399 func ExtendVolumeSize(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 400 t.Logf("Attempting to extend the size of volume %s", volume.ID) 401 402 extendOpts := volumes.ExtendSizeOpts{ 403 NewSize: 2, 404 } 405 406 err := volumes.ExtendSize(context.TODO(), client, volume.ID, extendOpts).ExtractErr() 407 if err != nil { 408 return err 409 } 410 411 ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) 412 defer cancel() 413 414 if err := volumes.WaitForStatus(ctx, client, volume.ID, "available"); err != nil { 415 return err 416 } 417 418 return nil 419 } 420 421 // SetImageMetadata will apply the metadata to a volume. 422 func SetImageMetadata(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 423 t.Logf("Attempting to apply image metadata to volume %s", volume.ID) 424 425 imageMetadataOpts := volumes.ImageMetadataOpts{ 426 Metadata: map[string]string{ 427 "image_name": "testimage", 428 }, 429 } 430 431 err := volumes.SetImageMetadata(context.TODO(), client, volume.ID, imageMetadataOpts).ExtractErr() 432 if err != nil { 433 return err 434 } 435 436 return nil 437 } 438 439 // SetBootable will set a bootable status to a volume. 440 func SetBootable(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 441 t.Logf("Attempting to apply bootable status to volume %s", volume.ID) 442 443 bootableOpts := volumes.BootableOpts{ 444 Bootable: true, 445 } 446 447 err := volumes.SetBootable(context.TODO(), client, volume.ID, bootableOpts).ExtractErr() 448 if err != nil { 449 return err 450 } 451 452 vol, err := volumes.Get(context.TODO(), client, volume.ID).Extract() 453 if err != nil { 454 return err 455 } 456 457 if strings.ToLower(vol.Bootable) != "true" { 458 return fmt.Errorf("Volume bootable status is %q, expected 'true'", vol.Bootable) 459 } 460 461 bootableOpts = volumes.BootableOpts{ 462 Bootable: false, 463 } 464 465 err = volumes.SetBootable(context.TODO(), client, volume.ID, bootableOpts).ExtractErr() 466 if err != nil { 467 return err 468 } 469 470 vol, err = volumes.Get(context.TODO(), client, volume.ID).Extract() 471 if err != nil { 472 return err 473 } 474 475 if strings.ToLower(vol.Bootable) == "true" { 476 return fmt.Errorf("Volume bootable status is %q, expected 'false'", vol.Bootable) 477 } 478 479 return nil 480 } 481 482 // ResetVolumeStatus will reset the status of a volume. 483 func ResetVolumeStatus(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, status string) error { 484 t.Logf("Attempting to reset the status of volume %s from %s to %s", volume.ID, volume.Status, status) 485 486 resetOpts := volumes.ResetStatusOpts{ 487 Status: status, 488 } 489 err := volumes.ResetStatus(context.TODO(), client, volume.ID, resetOpts).ExtractErr() 490 if err != nil { 491 return err 492 } 493 494 ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) 495 defer cancel() 496 497 if err := volumes.WaitForStatus(ctx, client, volume.ID, status); err != nil { 498 return err 499 } 500 501 return nil 502 }