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  }