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. 4package extensions 5 6import ( 7 "fmt" 8 "strings" 9 "testing" 10 11 "github.com/gophercloud/gophercloud" 12 "github.com/gophercloud/gophercloud/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/compute/v2/images" 18 "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" 19 th "github.com/gophercloud/gophercloud/testhelper" 20) 21 22// CreateUploadImage will upload volume it as volume-baked image. An name of new image or err will be 23// returned 24func CreateUploadImage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) (volumeactions.VolumeImage, error) { 25 if testing.Short() { 26 t.Skip("Skipping test that requires volume-backed image uploading in short mode.") 27 } 28 29 imageName := tools.RandomString("ACPTTEST", 16) 30 uploadImageOpts := volumeactions.UploadImageOpts{ 31 ImageName: imageName, 32 Force: true, 33 } 34 35 volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts).Extract() 36 if err != nil { 37 return volumeImage, err 38 } 39 40 t.Logf("Uploading volume %s as volume-backed image %s", volume.ID, imageName) 41 42 if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { 43 return volumeImage, err 44 } 45 46 t.Logf("Uploaded volume %s as volume-backed image %s", volume.ID, imageName) 47 48 return volumeImage, nil 49 50} 51 52// DeleteUploadedImage deletes uploaded image. An error will be returned 53// if the deletion request failed. 54func DeleteUploadedImage(t *testing.T, client *gophercloud.ServiceClient, imageID string) error { 55 if testing.Short() { 56 t.Skip("Skipping test that requires volume-backed image removing in short mode.") 57 } 58 59 t.Logf("Removing image %s", imageID) 60 61 err := images.Delete(client, imageID).ExtractErr() 62 if err != nil { 63 return err 64 } 65 66 return nil 67} 68 69// CreateVolumeAttach will attach a volume to an instance. An error will be 70// returned if the attachment failed. 71func CreateVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, server *servers.Server) error { 72 if testing.Short() { 73 t.Skip("Skipping test that requires volume attachment in short mode.") 74 } 75 76 attachOpts := volumeactions.AttachOpts{ 77 MountPoint: "/mnt", 78 Mode: "rw", 79 InstanceUUID: server.ID, 80 } 81 82 t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID) 83 84 if err := volumeactions.Attach(client, volume.ID, attachOpts).ExtractErr(); err != nil { 85 return err 86 } 87 88 if err := volumes.WaitForStatus(client, volume.ID, "in-use", 60); err != nil { 89 return err 90 } 91 92 t.Logf("Attached volume %s to server %s", volume.ID, server.ID) 93 94 return nil 95} 96 97// CreateVolumeReserve creates a volume reservation. An error will be returned 98// if the reservation failed. 99func CreateVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 100 if testing.Short() { 101 t.Skip("Skipping test that requires volume reservation in short mode.") 102 } 103 104 t.Logf("Attempting to reserve volume %s", volume.ID) 105 106 if err := volumeactions.Reserve(client, volume.ID).ExtractErr(); err != nil { 107 return err 108 } 109 110 t.Logf("Reserved volume %s", volume.ID) 111 112 return nil 113} 114 115// DeleteVolumeAttach will detach a volume from an instance. A fatal error will 116// occur if the snapshot failed to be deleted. This works best when used as a 117// deferred function. 118func DeleteVolumeAttach(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { 119 t.Logf("Attepting to detach volume volume: %s", volume.ID) 120 121 detachOpts := volumeactions.DetachOpts{ 122 AttachmentID: volume.Attachments[0].AttachmentID, 123 } 124 125 if err := volumeactions.Detach(client, volume.ID, detachOpts).ExtractErr(); err != nil { 126 t.Fatalf("Unable to detach volume %s: %v", volume.ID, err) 127 } 128 129 if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { 130 t.Fatalf("Volume %s failed to become unavailable in 60 seconds: %v", volume.ID, err) 131 } 132 133 t.Logf("Detached volume: %s", volume.ID) 134} 135 136// DeleteVolumeReserve deletes a volume reservation. A fatal error will occur 137// if the deletion request failed. This works best when used as a deferred 138// function. 139func DeleteVolumeReserve(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) { 140 if testing.Short() { 141 t.Skip("Skipping test that requires volume reservation in short mode.") 142 } 143 144 t.Logf("Attempting to unreserve volume %s", volume.ID) 145 146 if err := volumeactions.Unreserve(client, volume.ID).ExtractErr(); err != nil { 147 t.Fatalf("Unable to unreserve volume %s: %v", volume.ID, err) 148 } 149 150 t.Logf("Unreserved volume %s", volume.ID) 151} 152 153// ExtendVolumeSize will extend the size of a volume. 154func ExtendVolumeSize(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 155 t.Logf("Attempting to extend the size of volume %s", volume.ID) 156 157 extendOpts := volumeactions.ExtendSizeOpts{ 158 NewSize: 2, 159 } 160 161 err := volumeactions.ExtendSize(client, volume.ID, extendOpts).ExtractErr() 162 if err != nil { 163 return err 164 } 165 166 if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { 167 return err 168 } 169 170 return nil 171} 172 173// SetImageMetadata will apply the metadata to a volume. 174func SetImageMetadata(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 175 t.Logf("Attempting to apply image metadata to volume %s", volume.ID) 176 177 imageMetadataOpts := volumeactions.ImageMetadataOpts{ 178 Metadata: map[string]string{ 179 "image_name": "testimage", 180 }, 181 } 182 183 err := volumeactions.SetImageMetadata(client, volume.ID, imageMetadataOpts).ExtractErr() 184 if err != nil { 185 return err 186 } 187 188 return nil 189} 190 191// CreateBackup will create a backup based on a volume. An error will be 192// will be returned if the backup could not be created. 193func CreateBackup(t *testing.T, client *gophercloud.ServiceClient, volumeID string) (*backups.Backup, error) { 194 t.Logf("Attempting to create a backup of volume %s", volumeID) 195 196 backupName := tools.RandomString("ACPTTEST", 16) 197 createOpts := backups.CreateOpts{ 198 VolumeID: volumeID, 199 Name: backupName, 200 } 201 202 backup, err := backups.Create(client, createOpts).Extract() 203 if err != nil { 204 return nil, err 205 } 206 207 err = WaitForBackupStatus(client, backup.ID, "available", 120) 208 if err != nil { 209 return nil, err 210 } 211 212 backup, err = backups.Get(client, backup.ID).Extract() 213 if err != nil { 214 return nil, err 215 } 216 217 t.Logf("Successfully created backup %s", backup.ID) 218 tools.PrintResource(t, backup) 219 220 th.AssertEquals(t, backup.Name, backupName) 221 222 return backup, nil 223} 224 225// DeleteBackup will delete a backup. A fatal error will occur if the backup 226// could not be deleted. This works best when used as a deferred function. 227func DeleteBackup(t *testing.T, client *gophercloud.ServiceClient, backupID string) { 228 if err := backups.Delete(client, backupID).ExtractErr(); err != nil { 229 t.Fatalf("Unable to delete backup %s: %s", backupID, err) 230 } 231 232 t.Logf("Deleted backup %s", backupID) 233} 234 235// WaitForBackupStatus will continually poll a backup, checking for a particular 236// status. It will do this for the amount of seconds defined. 237func WaitForBackupStatus(client *gophercloud.ServiceClient, id, status string, secs int) error { 238 return gophercloud.WaitFor(secs, func() (bool, error) { 239 current, err := backups.Get(client, id).Extract() 240 if err != nil { 241 return false, err 242 } 243 244 if current.Status == status { 245 return true, nil 246 } 247 248 return false, nil 249 }) 250} 251 252// SetBootable will set a bootable status to a volume. 253func SetBootable(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume) error { 254 t.Logf("Attempting to apply bootable status to volume %s", volume.ID) 255 256 bootableOpts := volumeactions.BootableOpts{ 257 Bootable: true, 258 } 259 260 err := volumeactions.SetBootable(client, volume.ID, bootableOpts).ExtractErr() 261 if err != nil { 262 return err 263 } 264 265 vol, err := v3.Get(client, volume.ID).Extract() 266 if err != nil { 267 return err 268 } 269 270 if strings.ToLower(vol.Bootable) != "true" { 271 return fmt.Errorf("Volume bootable status is %q, expected 'true'", vol.Bootable) 272 } 273 274 bootableOpts = volumeactions.BootableOpts{ 275 Bootable: false, 276 } 277 278 err = volumeactions.SetBootable(client, volume.ID, bootableOpts).ExtractErr() 279 if err != nil { 280 return err 281 } 282 283 vol, err = v3.Get(client, volume.ID).Extract() 284 if err != nil { 285 return err 286 } 287 288 if strings.ToLower(vol.Bootable) == "true" { 289 return fmt.Errorf("Volume bootable status is %q, expected 'false'", vol.Bootable) 290 } 291 292 return nil 293} 294