From bf9a309975f7fe2846f12d29fd844a7c20d37fff Mon Sep 17 00:00:00 2001 From: kayrus Date: Wed, 12 Jul 2023 20:40:46 +0200 Subject: [PATCH] [manila]: add reset and force delete actions to a snapshot --- .../sharedfilesystems/v2/replicas_test.go | 15 ++-- .../sharedfilesystems/v2/snapshots.go | 3 + .../sharedfilesystems/v2/snapshots_test.go | 75 +++++++++++++++++++ .../v2/snapshots/requests.go | 51 +++++++++++++ .../sharedfilesystems/v2/snapshots/results.go | 10 +++ .../v2/snapshots/testing/fixtures.go | 36 +++++++++ .../v2/snapshots/testing/request_test.go | 24 ++++++ .../sharedfilesystems/v2/snapshots/urls.go | 8 ++ 8 files changed, 215 insertions(+), 7 deletions(-) diff --git a/acceptance/openstack/sharedfilesystems/v2/replicas_test.go b/acceptance/openstack/sharedfilesystems/v2/replicas_test.go index d3179b199f..6fa28cada6 100644 --- a/acceptance/openstack/sharedfilesystems/v2/replicas_test.go +++ b/acceptance/openstack/sharedfilesystems/v2/replicas_test.go @@ -13,8 +13,9 @@ import ( th "github.com/gophercloud/gophercloud/testhelper" ) +// 2.56 is required for a /v2/replicas/XXX URL support // otherwise we need to set "X-OpenStack-Manila-API-Experimental: true" -const replicasMicroversion = "2.60" +const replicasPathMicroversion = "2.56" func TestReplicaCreate(t *testing.T) { clients.RequireManilaReplicas(t) @@ -23,7 +24,7 @@ func TestReplicaCreate(t *testing.T) { if err != nil { t.Fatalf("Unable to create a shared file system client: %v", err) } - client.Microversion = replicasMicroversion + client.Microversion = replicasPathMicroversion share, err := CreateShare(t, client) if err != nil { @@ -60,7 +61,7 @@ func TestReplicaPromote(t *testing.T) { if err != nil { t.Fatalf("Unable to create a shared file system client: %v", err) } - client.Microversion = replicasMicroversion + client.Microversion = replicasPathMicroversion share, err := CreateShare(t, client) if err != nil { @@ -136,7 +137,7 @@ func TestReplicaExportLocations(t *testing.T) { if err != nil { t.Fatalf("Unable to create a shared file system client: %v", err) } - client.Microversion = replicasMicroversion + client.Microversion = replicasPathMicroversion share, err := CreateShare(t, client) if err != nil { @@ -203,7 +204,7 @@ func TestReplicaListDetail(t *testing.T) { if err != nil { t.Fatalf("Unable to create a shared file system client: %v", err) } - client.Microversion = replicasMicroversion + client.Microversion = replicasPathMicroversion share, err := CreateShare(t, client) if err != nil { @@ -236,7 +237,7 @@ func TestReplicaResetStatus(t *testing.T) { if err != nil { t.Fatalf("Unable to create a shared file system client: %v", err) } - client.Microversion = replicasMicroversion + client.Microversion = replicasPathMicroversion share, err := CreateShare(t, client) if err != nil { @@ -278,7 +279,7 @@ func TestReplicaForceDelete(t *testing.T) { if err != nil { t.Fatalf("Unable to create a shared file system client: %v", err) } - client.Microversion = replicasMicroversion + client.Microversion = replicasPathMicroversion share, err := CreateShare(t, client) if err != nil { diff --git a/acceptance/openstack/sharedfilesystems/v2/snapshots.go b/acceptance/openstack/sharedfilesystems/v2/snapshots.go index 62e607d229..e96d02b9b9 100644 --- a/acceptance/openstack/sharedfilesystems/v2/snapshots.go +++ b/acceptance/openstack/sharedfilesystems/v2/snapshots.go @@ -54,6 +54,9 @@ func ListSnapshots(t *testing.T, client *gophercloud.ServiceClient) ([]snapshots 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.Errorf("Unable to delete snapshot %s: %v", snapshot.ID, err) } diff --git a/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go b/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go index a30bad98bb..de83e41cd1 100644 --- a/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go +++ b/acceptance/openstack/sharedfilesystems/v2/snapshots_test.go @@ -12,6 +12,10 @@ import ( th "github.com/gophercloud/gophercloud/testhelper" ) +// 2.7 is required for a /v2/snapshots/XXX/action URL support +// otherwise we need to set "X-OpenStack-Manila-API-Experimental: true" +const snapshotsPathMicroversion = "2.7" + func TestSnapshotCreate(t *testing.T) { client, err := clients.NewSharedFileSystemV2Client() if err != nil { @@ -119,3 +123,74 @@ func TestSnapshotListDetail(t *testing.T) { tools.PrintResource(t, &ss[i]) } } + +func TestSnapshotResetStatus(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + client.Microversion = snapshotsPathMicroversion + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + snapshot, err := CreateSnapshot(t, client, share.ID) + if err != nil { + t.Fatalf("Unable to create a snapshot: %v", err) + } + + defer DeleteSnapshot(t, client, snapshot) + + resetStatusOpts := &snapshots.ResetStatusOpts{ + Status: "error", + } + err = snapshots.ResetStatus(client, snapshot.ID, resetStatusOpts).ExtractErr() + if err != nil { + t.Fatalf("Unable to reset a snapshot status: %v", err) + } + + err = waitForSnapshotStatus(t, client, snapshot.ID, "error") + if err != nil { + t.Fatalf("Snapshot status error: %v", err) + } + + t.Logf("Snapshot %s status successfuly reset", snapshot.ID) +} + +func TestSnapshotForceDelete(t *testing.T) { + client, err := clients.NewSharedFileSystemV2Client() + if err != nil { + t.Fatalf("Unable to create a shared file system client: %v", err) + } + client.Microversion = snapshotsPathMicroversion + + share, err := CreateShare(t, client) + if err != nil { + t.Fatalf("Unable to create a share: %v", err) + } + + defer DeleteShare(t, client, share) + + snapshot, err := CreateSnapshot(t, client, share.ID) + if err != nil { + t.Fatalf("Unable to create a snapshot: %v", err) + } + + defer DeleteSnapshot(t, client, snapshot) + + err = snapshots.ForceDelete(client, snapshot.ID).ExtractErr() + if err != nil { + t.Fatalf("Unable to force delete a snapshot: %v", err) + } + + err = waitForSnapshotStatus(t, client, snapshot.ID, "deleted") + if err != nil { + t.Fatalf("Snapshot status error: %v", err) + } + + t.Logf("Snapshot %s was successfuly deleted", snapshot.ID) +} diff --git a/openstack/sharedfilesystems/v2/snapshots/requests.go b/openstack/sharedfilesystems/v2/snapshots/requests.go index 1ed6e8aef2..bbdde5eac1 100644 --- a/openstack/sharedfilesystems/v2/snapshots/requests.go +++ b/openstack/sharedfilesystems/v2/snapshots/requests.go @@ -163,3 +163,54 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) return } + +// ResetStatusOptsBuilder allows extensions to add additional parameters to the +// ResetStatus request. +type ResetStatusOptsBuilder interface { + ToSnapshotResetStatusMap() (map[string]interface{}, error) +} + +// ResetStatusOpts contains options for resetting a Snapshot status. +// For more information about these parameters, please, refer to the shared file systems API v2, +// Snapshot Actions, ResetStatus share documentation. +type ResetStatusOpts struct { + // Status is a snapshot status to reset to. Can be "available", "error", + // "creating", "deleting", "manage_starting", "manage_error", + // "unmanage_starting", "unmanage_error" or "error_deleting". + Status string `json:"status"` +} + +// ToSnapshotResetStatusMap assembles a request body based on the contents of a +// ResetStatusOpts. +func (opts ResetStatusOpts) ToSnapshotResetStatusMap() (map[string]interface{}, error) { + return gophercloud.BuildRequestBody(opts, "reset_status") +} + +// ResetStatus will reset the existing snapshot 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.ToSnapshotResetStatusMap() + if err != nil { + r.Err = err + return + } + + resp, err := client.Post(resetStatusURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} + +// ForceDelete will delete the existing snapshot in any state. ForceDeleteResult contains only the error. +// To extract it, call the ExtractErr method on the ForceDeleteResult. +func ForceDelete(client *gophercloud.ServiceClient, id string) (r ForceDeleteResult) { + b := map[string]interface{}{ + "force_delete": nil, + } + resp, err := client.Post(forceDeleteURL(client, id), b, nil, &gophercloud.RequestOpts{ + OkCodes: []int{202}, + }) + _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) + return +} diff --git a/openstack/sharedfilesystems/v2/snapshots/results.go b/openstack/sharedfilesystems/v2/snapshots/results.go index a3d45aaa94..44337b17a7 100644 --- a/openstack/sharedfilesystems/v2/snapshots/results.go +++ b/openstack/sharedfilesystems/v2/snapshots/results.go @@ -173,3 +173,13 @@ type GetResult struct { type UpdateResult struct { commonResult } + +// ResetStatusResult contains the response error from an ResetStatus request. +type ResetStatusResult struct { + gophercloud.ErrResult +} + +// ForceDeleteResult contains the response error from an ForceDelete request. +type ForceDeleteResult struct { + gophercloud.ErrResult +} diff --git a/openstack/sharedfilesystems/v2/snapshots/testing/fixtures.go b/openstack/sharedfilesystems/v2/snapshots/testing/fixtures.go index c02ef10c71..fb677918dd 100644 --- a/openstack/sharedfilesystems/v2/snapshots/testing/fixtures.go +++ b/openstack/sharedfilesystems/v2/snapshots/testing/fixtures.go @@ -204,3 +204,39 @@ func MockListDetailResponse(t *testing.T) { } }) } + +var resetStatusRequest = `{ + "reset_status": { + "status": "error" + } + }` + +// MockResetStatusResponse creates a mock reset status snapshot response +func MockResetStatusResponse(t *testing.T) { + th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID+"/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.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, resetStatusRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + }) +} + +var forceDeleteRequest = `{ + "force_delete": null + }` + +// MockForceDeleteResponse creates a mock force delete snapshot response +func MockForceDeleteResponse(t *testing.T) { + th.Mux.HandleFunc(snapshotEndpoint+"/"+snapshotID+"/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.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, forceDeleteRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go b/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go index e210b4adc9..52f9c33a23 100644 --- a/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go +++ b/openstack/sharedfilesystems/v2/snapshots/testing/request_test.go @@ -125,3 +125,27 @@ func TestListDetail(t *testing.T) { }, }) } + +func TestResetStatusSuccess(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockResetStatusResponse(t) + + c := client.ServiceClient() + + err := snapshots.ResetStatus(c, snapshotID, &snapshots.ResetStatusOpts{Status: "error"}).ExtractErr() + th.AssertNoErr(t, err) +} + +func TestForceDeleteSuccess(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockForceDeleteResponse(t) + + c := client.ServiceClient() + + err := snapshots.ForceDelete(c, snapshotID).ExtractErr() + th.AssertNoErr(t, err) +} diff --git a/openstack/sharedfilesystems/v2/snapshots/urls.go b/openstack/sharedfilesystems/v2/snapshots/urls.go index a07e3ec873..138d97f350 100644 --- a/openstack/sharedfilesystems/v2/snapshots/urls.go +++ b/openstack/sharedfilesystems/v2/snapshots/urls.go @@ -21,3 +21,11 @@ func getURL(c *gophercloud.ServiceClient, id string) string { func updateURL(c *gophercloud.ServiceClient, id string) string { return c.ServiceURL("snapshots", id) } + +func resetStatusURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("snapshots", id, "action") +} + +func forceDeleteURL(c *gophercloud.ServiceClient, id string) string { + return c.ServiceURL("snapshots", id, "action") +}