Skip to content

Commit

Permalink
Merge pull request #2696 from kayrus/volume-actions
Browse files Browse the repository at this point in the history
[cinder]: add reset and force delete actions to volumes and snapshots
  • Loading branch information
mandre committed Jul 19, 2023
2 parents 3df246e + 223eac3 commit 18eebcb
Show file tree
Hide file tree
Showing 15 changed files with 472 additions and 8 deletions.
19 changes: 19 additions & 0 deletions acceptance/openstack/blockstorage/extensions/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,25 @@ func ChangeVolumeType(t *testing.T, client *gophercloud.ServiceClient, volume *v
return nil
}

// ResetVolumeStatus will reset the status of a volume.
func ResetVolumeStatus(t *testing.T, client *gophercloud.ServiceClient, volume *v3.Volume, status string) error {
t.Logf("Attempting to reset the status of volume %s from %s to %s", volume.ID, volume.Status, status)

resetOpts := volumeactions.ResetStatusOpts{
Status: status,
}
err := volumeactions.ResetStatus(client, volume.ID, resetOpts).ExtractErr()
if err != nil {
return err
}

if err := volumes.WaitForStatus(client, volume.ID, status, 60); err != nil {
return err
}

return nil
}

// ReImage will re-image a volume
func ReImage(t *testing.T, client *gophercloud.ServiceClient, volume *volumes.Volume, imageID string) error {
t.Logf("Attempting to re-image volume %s", volume.ID)
Expand Down
17 changes: 17 additions & 0 deletions acceptance/openstack/blockstorage/extensions/volumeactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,23 @@ func TestVolumeActionsChangeType(t *testing.T) {
tools.PrintResource(t, newVolume)
}

func TestVolumeActionsResetStatus(t *testing.T) {
client, err := clients.NewBlockStorageV3Client()
th.AssertNoErr(t, err)

volume, err := blockstorageV3.CreateVolume(t, client)
th.AssertNoErr(t, err)
defer blockstorageV3.DeleteVolume(t, client, volume)

tools.PrintResource(t, volume)

err = ResetVolumeStatus(t, client, volume, "error")
th.AssertNoErr(t, err)

err = ResetVolumeStatus(t, client, volume, "available")
th.AssertNoErr(t, err)
}

func TestVolumeActionsReImage(t *testing.T) {
clients.SkipReleasesBelow(t, "stable/yoga")

Expand Down
21 changes: 21 additions & 0 deletions acceptance/openstack/blockstorage/v3/blockstorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func CreateSnapshot(t *testing.T, client *gophercloud.ServiceClient, volume *vol
return snapshot, err
}

snapshot, err = snapshots.Get(client, snapshot.ID).Extract()
if err != nil {
return snapshot, err
}

tools.PrintResource(t, snapshot)
th.AssertEquals(t, snapshot.Name, snapshotName)
th.AssertEquals(t, snapshot.VolumeID, volume.ID)
Expand Down Expand Up @@ -70,6 +75,11 @@ func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Vol
return volume, err
}

volume, err = volumes.Get(client, volume.ID).Extract()
if err != nil {
return volume, err
}

tools.PrintResource(t, volume)
th.AssertEquals(t, volume.Name, volumeName)
th.AssertEquals(t, volume.Description, volumeDescription)
Expand Down Expand Up @@ -105,6 +115,11 @@ func CreateVolumeWithType(t *testing.T, client *gophercloud.ServiceClient, vt *v
return volume, err
}

volume, err = volumes.Get(client, volume.ID).Extract()
if err != nil {
return volume, err
}

tools.PrintResource(t, volume)
th.AssertEquals(t, volume.Name, volumeName)
th.AssertEquals(t, volume.Description, volumeDescription)
Expand Down Expand Up @@ -243,6 +258,9 @@ func CreatePrivateVolumeType(t *testing.T, client *gophercloud.ServiceClient) (*
func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) {
err := snapshots.Delete(client, snapshot.ID).ExtractErr()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
return
}
t.Fatalf("Unable to delete snapshot %s: %+v", snapshot.ID, err)
}

Expand Down Expand Up @@ -270,6 +288,9 @@ func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volum

err := volumes.Delete(client, volume.ID, volumes.DeleteOpts{}).ExtractErr()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
return
}
t.Fatalf("Unable to delete volume %s: %v", volume.ID, err)
}

Expand Down
2 changes: 1 addition & 1 deletion acceptance/openstack/blockstorage/v3/quotaset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func TestQuotasetUpdate(t *testing.T) {
// test that resultQuotas.Extra is populated with the 3 new quota types
// for the new volumeType foo, don't take into account other volume types
count := 0
for k, _ := range resultQuotas.Extra {
for k := range resultQuotas.Extra {
tools.PrintResource(t, k)
switch k {
case
Expand Down
124 changes: 124 additions & 0 deletions acceptance/openstack/blockstorage/v3/snapshots_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
package v3

import (
"fmt"
"testing"

"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/acceptance/clients"
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots"
Expand Down Expand Up @@ -73,3 +75,125 @@ func TestSnapshots(t *testing.T) {

th.AssertNoErr(t, err)
}

func TestSnapshotsResetStatus(t *testing.T) {
clients.RequireLong(t)

client, err := clients.NewBlockStorageV3Client()
th.AssertNoErr(t, err)

volume1, err := CreateVolume(t, client)
th.AssertNoErr(t, err)
defer DeleteVolume(t, client, volume1)

snapshot1, err := CreateSnapshot(t, client, volume1)
th.AssertNoErr(t, err)
defer DeleteSnapshot(t, client, snapshot1)

// Reset snapshot status to error
resetOpts := snapshots.ResetStatusOpts{
Status: "error",
}
t.Logf("Attempting to reset snapshot status to %s", resetOpts.Status)
err = snapshots.ResetStatus(client, snapshot1.ID, resetOpts).ExtractErr()
th.AssertNoErr(t, err)

snapshot, err := snapshots.Get(client, snapshot1.ID).Extract()
th.AssertNoErr(t, err)

if snapshot.Status != resetOpts.Status {
th.AssertNoErr(t, fmt.Errorf("unexpected %q snapshot status", snapshot.Status))
}

// Reset snapshot status to available
resetOpts = snapshots.ResetStatusOpts{
Status: "available",
}
t.Logf("Attempting to reset snapshot status to %s", resetOpts.Status)
err = snapshots.ResetStatus(client, snapshot1.ID, resetOpts).ExtractErr()
th.AssertNoErr(t, err)

snapshot, err = snapshots.Get(client, snapshot1.ID).Extract()
th.AssertNoErr(t, err)

if snapshot.Status != resetOpts.Status {
th.AssertNoErr(t, fmt.Errorf("unexpected %q snapshot status", snapshot.Status))
}
}

func TestSnapshotsUpdateStatus(t *testing.T) {
clients.RequireLong(t)

client, err := clients.NewBlockStorageV3Client()
th.AssertNoErr(t, err)

volume1, err := CreateVolume(t, client)
th.AssertNoErr(t, err)
defer DeleteVolume(t, client, volume1)

snapshot1, err := CreateSnapshot(t, client, volume1)
th.AssertNoErr(t, err)
defer DeleteSnapshot(t, client, snapshot1)

// Update snapshot status to error
resetOpts := snapshots.ResetStatusOpts{
Status: "creating",
}
t.Logf("Attempting to update snapshot status to %s", resetOpts.Status)
err = snapshots.ResetStatus(client, snapshot1.ID, resetOpts).ExtractErr()
th.AssertNoErr(t, err)

snapshot, err := snapshots.Get(client, snapshot1.ID).Extract()
th.AssertNoErr(t, err)

if snapshot.Status != resetOpts.Status {
th.AssertNoErr(t, fmt.Errorf("unexpected %q snapshot status", snapshot.Status))
}

// Update snapshot status to available
updateOpts := snapshots.UpdateStatusOpts{
Status: "available",
}
t.Logf("Attempting to update snapshot status to %s", updateOpts.Status)
err = snapshots.UpdateStatus(client, snapshot1.ID, updateOpts).ExtractErr()
th.AssertNoErr(t, err)

snapshot, err = snapshots.Get(client, snapshot1.ID).Extract()
th.AssertNoErr(t, err)

if snapshot.Status != updateOpts.Status {
th.AssertNoErr(t, fmt.Errorf("unexpected %q snapshot status", snapshot.Status))
}
}

func TestSnapshotsForceDelete(t *testing.T) {
clients.RequireLong(t)

client, err := clients.NewBlockStorageV3Client()
th.AssertNoErr(t, err)

volume, err := CreateVolume(t, client)
th.AssertNoErr(t, err)
defer DeleteVolume(t, client, volume)

snapshot, err := CreateSnapshot(t, client, volume)
th.AssertNoErr(t, err)
defer DeleteSnapshot(t, client, snapshot)

// Force delete snapshot
t.Logf("Attempting to force delete %s snapshot", snapshot.ID)
err = snapshots.ForceDelete(client, snapshot.ID).ExtractErr()
th.AssertNoErr(t, err)

err = tools.WaitFor(func() (bool, error) {
_, err := snapshots.Get(client, snapshot.ID).Extract()
if err != nil {
if _, ok := err.(gophercloud.ErrDefault404); ok {
return true, nil
}
}

return false, nil
})
th.AssertNoErr(t, err)
}
40 changes: 40 additions & 0 deletions openstack/blockstorage/extensions/volumeactions/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,43 @@ func ReImage(client *gophercloud.ServiceClient, id string, opts ReImageOpts) (r
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// ResetStatusOptsBuilder allows extensions to add additional parameters to the
// ResetStatus request.
type ResetStatusOptsBuilder interface {
ToResetStatusMap() (map[string]interface{}, error)
}

// ResetStatusOpts contains options for resetting a Volume status.
// For more information about these parameters, please, refer to the Block Storage API V3,
// Volume Actions, ResetStatus volume documentation.
type ResetStatusOpts struct {
// Status is a volume status to reset to.
Status string `json:"status"`
// MigrationStatus is a volume migration status to reset to.
MigrationStatus string `json:"migration_status,omitempty"`
// AttachStatus is a volume attach status to reset to.
AttachStatus string `json:"attach_status,omitempty"`
}

// ToResetStatusMap assembles a request body based on the contents of a
// ResetStatusOpts.
func (opts ResetStatusOpts) ToResetStatusMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "os-reset_status")
}

// ResetStatus will reset the existing volume status. ResetStatusResult contains only the error.
// To extract it, call the ExtractErr method on the ResetStatusResult.
func ResetStatus(client *gophercloud.ServiceClient, id string, opts ResetStatusOptsBuilder) (r ResetStatusResult) {
b, err := opts.ToResetStatusMap()
if err != nil {
r.Err = err
return
}

resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
5 changes: 5 additions & 0 deletions openstack/blockstorage/extensions/volumeactions/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,8 @@ type ChangeTypeResult struct {
type ReImageResult struct {
gophercloud.ErrResult
}

// ResetStatusResult contains the response error from a ResetStatus request.
type ResetStatusResult struct {
gophercloud.ErrResult
}
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,24 @@ func MockChangeTypeResponse(t *testing.T) {
fmt.Fprintf(w, `{}`)
})
}

func MockResetStatusResponse(t *testing.T) {
th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action",
func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
th.TestHeader(t, r, "Content-Type", "application/json")
th.TestJSONRequest(t, r, `
{
"os-reset_status":
{
"status": "error",
"attach_status": "detached",
"migration_status": "migrating"
}
}
`)

w.WriteHeader(http.StatusAccepted)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,19 @@ func TestChangeType(t *testing.T) {
err := volumeactions.ChangeType(client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr()
th.AssertNoErr(t, err)
}

func TestResetStatus(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

MockResetStatusResponse(t)

options := &volumeactions.ResetStatusOpts{
Status: "error",
AttachStatus: "detached",
MigrationStatus: "migrating",
}

err := volumeactions.ResetStatus(client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr()
th.AssertNoErr(t, err)
}
Loading

0 comments on commit 18eebcb

Please sign in to comment.