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  }