Skip to content

Commit ea86f48

Browse files
blockstorage: add manage-existing api call
Implemented missing blockstorage api call to manage existing volume. ref. https://docs.openstack.org/api-ref/block-storage/v3/#manage-an-existing-volume
1 parent 9e4535f commit ea86f48

7 files changed

Lines changed: 261 additions & 0 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Package manageablevolumes information and interaction with manageable volumes
3+
for the OpenStack Block Storage service.
4+
5+
NOTE: Requires at least microversion 3.8
6+
7+
Example to manage an existing volume
8+
9+
manageOpts := manageablevolumes.ManageExistingOpts{
10+
Host: "host@lvm#LVM",
11+
Ref: map[string]string{
12+
"source-name": "volume-73796b96-169f-4675-a5bc-73fc0f8f9a17",
13+
},
14+
Name: "New Volume",
15+
AvailabilityZone: "nova",
16+
Description: "Volume imported from existingLV",
17+
VolumeType: "lvm",
18+
Bootable: true,
19+
Metadata: map[string]string{
20+
"key1": "value1",
21+
"key2": "value2"
22+
},
23+
}
24+
25+
managedVolume, err := manageablevolumes.ManageExisting(context.TODO(), client, manageOpts).Extract()
26+
if err != nil {
27+
log.Fatal(err)
28+
}
29+
30+
fmt.Printf("Managed volume: %+v\n", managedVolume)
31+
*/
32+
package manageablevolumes
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package manageablevolumes
2+
3+
import (
4+
"context"
5+
6+
"github.com/gophercloud/gophercloud/v2"
7+
)
8+
9+
// ManageExistingOptsBuilder allows extentions to add additional parameters to the ManageExisting request.
10+
type ManageExistingOptsBuilder interface {
11+
ToManageExistingMap() (map[string]any, error)
12+
}
13+
14+
// ManageExistingOpts contains options for managing a existing volume.
15+
// This object is passed to the volumes.ManageExisting function.
16+
// For more information about the parameters, see the Volume object and OpenStack BlockStorage API Guide.
17+
type ManageExistingOpts struct {
18+
// The OpenStack Block Storage host where the existing resource resides.
19+
// Optional only if cluster field is provided.
20+
Host string `json:"host,omitempty"`
21+
// The OpenStack Block Storage cluster where the resource resides.
22+
// Optional only if host field is provided.
23+
Cluster string `json:"cluster,omitempty"`
24+
// A reference to the existing volume.
25+
// The internal structure of this reference depends on the volume driver implementation.
26+
// For details about the required elements in the structure, see the documentation for the volume driver.
27+
Ref map[string]string `json:"ref,omitempty"`
28+
// Human-readable display name for the volume.
29+
Name string `json:"name,omitempty"`
30+
// The availability zone.
31+
AvailabilityZone string `json:"availability_zone,omitempty"`
32+
// Human-readable description for the volume.
33+
Description string `json:"description,omitempty"`
34+
// The associated volume type
35+
VolumeType string `json:"volume_type,omitempty"`
36+
// Indicates whether this is a bootable volume.
37+
Bootable bool `json:"bootable,omitempty"`
38+
// One or more metadata key and value pairs to associate with the volume.
39+
Metadata map[string]string `json:"metadata,omitempty"`
40+
}
41+
42+
// ToManageExistingMap assembles a request body based on the contents of a ManageExistingOpts.
43+
func (opts ManageExistingOpts) ToManageExistingMap() (map[string]any, error) {
44+
return gophercloud.BuildRequestBody(opts, "volume")
45+
}
46+
47+
// ManageExisting will manage an existing volume based on the values in ManageExistingOpts.
48+
// To extract the Volume object from response, call the Extract method on the ManageExistingResult.
49+
func ManageExisting(ctx context.Context, client *gophercloud.ServiceClient, opts ManageExistingOptsBuilder) (r ManageExistingResult) {
50+
b, err := opts.ToManageExistingMap()
51+
if err != nil {
52+
r.Err = err
53+
return
54+
}
55+
56+
resp, err := client.Post(ctx, createURL(client), b, &r.Body, &gophercloud.RequestOpts{
57+
OkCodes: []int{202},
58+
})
59+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
60+
return
61+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package manageablevolumes
2+
3+
import (
4+
"github.com/gophercloud/gophercloud/v2"
5+
"github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/volumes"
6+
)
7+
8+
type ManageExistingResult struct {
9+
gophercloud.Result
10+
}
11+
12+
// Extract will get the Volume object out of the ManageExistingResult object.
13+
func (r ManageExistingResult) Extract() (*volumes.Volume, error) {
14+
var s volumes.Volume
15+
err := r.ExtractInto(&s)
16+
return &s, err
17+
}
18+
19+
// ExtractInto converts our response data into a volume struct
20+
func (r ManageExistingResult) ExtractInto(v any) error {
21+
return r.Result.ExtractIntoStructPtr(v, "volume")
22+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// manageablevolumes unit tests
2+
package testing
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package testing
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"testing"
7+
8+
th "github.com/gophercloud/gophercloud/v2/testhelper"
9+
fake "github.com/gophercloud/gophercloud/v2/testhelper/client"
10+
)
11+
12+
func MockManageExistingResponse(t *testing.T) {
13+
th.Mux.HandleFunc("/manageable_volumes", func(w http.ResponseWriter, r *http.Request) {
14+
th.TestMethod(t, r, "POST")
15+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
16+
th.TestHeader(t, r, "Content-Type", "application/json")
17+
th.TestHeader(t, r, "Accept", "application/json")
18+
th.TestJSONRequest(t, r, `
19+
{
20+
"volume": {
21+
"host": "host@lvm#LVM",
22+
"ref": {
23+
"source-name": "volume-73796b96-169f-4675-a5bc-73fc0f8f9a17"
24+
},
25+
"name": "New Volume",
26+
"availability_zone": "nova",
27+
"description": "Volume imported from existingLV",
28+
"volume_type": "lvm",
29+
"bootable": true,
30+
"metadata": {
31+
"key1": "value1",
32+
"key2": "value2"
33+
}
34+
}
35+
}
36+
`)
37+
38+
w.Header().Add("Content-Type", "application/json")
39+
w.WriteHeader(http.StatusAccepted)
40+
41+
fmt.Fprint(w, `
42+
{
43+
"volume": {
44+
"id": "23cf872b-c781-4cd4-847d-5f2ec8cbd91c",
45+
"status": "creating",
46+
"size": 0,
47+
"availability_zone": "nova",
48+
"created_at": "2025-03-20T11:58:05.000000",
49+
"updated_at": "2025-03-20T11:58:05.000000",
50+
"name": "New Volume",
51+
"description": "Volume imported from existingLV",
52+
"volume_type": "lvm",
53+
"snapshot_id": null,
54+
"source_volid": null,
55+
"metadata": {
56+
"key1": "value1",
57+
"key2": "value2"
58+
},
59+
"links": [
60+
{
61+
"href": "http://10.0.2.15:8776/v3/87c8522052ca4eed98bc672b4c1a3ddb/volumes/23cf872b-c781-4cd4-847d-5f2ec8cbd91c",
62+
"rel": "self"
63+
},
64+
{
65+
"href": "http://10.0.2.15:8776/87c8522052ca4eed98bc672b4c1a3ddb/volumes/23cf872b-c781-4cd4-847d-5f2ec8cbd91c",
66+
"rel": "bookmark"
67+
}
68+
],
69+
"user_id": "eae1472b5fc5496998a3d06550929e7e",
70+
"bootable": "true",
71+
"encrypted": false,
72+
"replication_status": null,
73+
"consistencygroup_id": null,
74+
"multiattach": false,
75+
"attachments": [],
76+
"created_at": "2014-07-18T00:12:54.000000",
77+
"migration_status": null,
78+
"group_id": null,
79+
"provider_id": null,
80+
"shared_targets": true,
81+
"service_uuid": null,
82+
"cluster_name": null,
83+
"volume_type_id": "a218796e-605b-4b6f-9dfc-8be95a0d7d03",
84+
"consumes_quota": true,
85+
"os-vol-mig-status-attr:migstat": null,
86+
"os-vol-mig-status-attr:name_id": null,
87+
"os-vol-tenant-attr:tenant_id": "87c8522052ca4eed98bc672b4c1a3ddb",
88+
"os-vol-host-attr:host": "host@lvm#LVM"
89+
}
90+
}
91+
`)
92+
})
93+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package testing
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/manageablevolumes"
8+
th "github.com/gophercloud/gophercloud/v2/testhelper"
9+
"github.com/gophercloud/gophercloud/v2/testhelper/client"
10+
)
11+
12+
func TestManageExisting(t *testing.T) {
13+
th.SetupHTTP()
14+
defer th.TeardownHTTP()
15+
16+
MockManageExistingResponse(t)
17+
18+
options := &manageablevolumes.ManageExistingOpts{
19+
Host: "host@lvm#LVM",
20+
Ref: map[string]string{"source-name": "volume-73796b96-169f-4675-a5bc-73fc0f8f9a17"},
21+
Name: "New Volume",
22+
AvailabilityZone: "nova",
23+
Description: "Volume imported from existingLV",
24+
VolumeType: "lvm",
25+
Bootable: true,
26+
Metadata: map[string]string{
27+
"key1": "value1",
28+
"key2": "value2",
29+
},
30+
}
31+
n, err := manageablevolumes.ManageExisting(context.TODO(), client.ServiceClient(), options).Extract()
32+
th.AssertNoErr(t, err)
33+
34+
th.AssertEquals(t, n.Host, "host@lvm#LVM")
35+
th.AssertEquals(t, n.Name, "New Volume")
36+
th.AssertEquals(t, n.AvailabilityZone, "nova")
37+
th.AssertEquals(t, n.Description, "Volume imported from existingLV")
38+
th.AssertEquals(t, n.Bootable, "true")
39+
th.AssertDeepEquals(t, n.Metadata, map[string]string{
40+
"key1": "value1",
41+
"key2": "value2",
42+
})
43+
th.AssertEquals(t, n.ID, "23cf872b-c781-4cd4-847d-5f2ec8cbd91c")
44+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package manageablevolumes
2+
3+
import "github.com/gophercloud/gophercloud/v2"
4+
5+
func createURL(c *gophercloud.ServiceClient) string {
6+
return c.ServiceURL("manageable_volumes")
7+
}

0 commit comments

Comments
 (0)