Skip to content

Commit c2f789d

Browse files
committed
Add support for os-retype volumeaction
1 parent 633d735 commit c2f789d

8 files changed

Lines changed: 238 additions & 1 deletion

File tree

acceptance/openstack/blockstorage/extensions/extensions.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
1515
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
1616
v3 "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
17+
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumetypes"
1718
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
1819
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
1920
th "github.com/gophercloud/gophercloud/testhelper"
@@ -291,3 +292,24 @@ func SetBootable(t *testing.T, client *gophercloud.ServiceClient, volume *volume
291292

292293
return nil
293294
}
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+
}

acceptance/openstack/blockstorage/extensions/volumeactions_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/gophercloud/gophercloud/acceptance/clients"
99
blockstorage "github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v2"
10+
blockstorageV3 "github.com/gophercloud/gophercloud/acceptance/openstack/blockstorage/v3"
1011
compute "github.com/gophercloud/gophercloud/acceptance/openstack/compute/v2"
1112
"github.com/gophercloud/gophercloud/acceptance/tools"
1213
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
@@ -113,6 +114,36 @@ func TestVolumeActionsSetBootable(t *testing.T) {
113114
th.AssertNoErr(t, err)
114115
}
115116

117+
func TestVolumeActionsChangeType(t *testing.T) {
118+
// clients.RequireAdmin(t)
119+
120+
client, err := clients.NewBlockStorageV3Client()
121+
th.AssertNoErr(t, err)
122+
123+
volumeType1, err := blockstorageV3.CreateVolumeTypeNoExtraSpecs(t, client)
124+
th.AssertNoErr(t, err)
125+
defer blockstorageV3.DeleteVolumeType(t, client, volumeType1)
126+
127+
volumeType2, err := blockstorageV3.CreateVolumeTypeNoExtraSpecs(t, client)
128+
th.AssertNoErr(t, err)
129+
defer blockstorageV3.DeleteVolumeType(t, client, volumeType2)
130+
131+
volume, err := blockstorageV3.CreateVolumeWithType(t, client, volumeType1)
132+
th.AssertNoErr(t, err)
133+
defer blockstorageV3.DeleteVolume(t, client, volume)
134+
135+
tools.PrintResource(t, volume)
136+
137+
err = ChangeVolumeType(t, client, volume, volumeType2)
138+
th.AssertNoErr(t, err)
139+
140+
newVolume, err := volumes.Get(client, volume.ID).Extract()
141+
th.AssertNoErr(t, err)
142+
th.AssertEquals(t, newVolume.VolumeType, volumeType2.Name)
143+
144+
tools.PrintResource(t, newVolume)
145+
}
146+
116147
// Note(jtopjian): I plan to work on this at some point, but it requires
117148
// setting up a server with iscsi utils.
118149
/*

acceptance/openstack/blockstorage/v3/blockstorage.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,42 @@ func CreateVolume(t *testing.T, client *gophercloud.ServiceClient) (*volumes.Vol
7979
return volume, nil
8080
}
8181

82+
// CreateVolumeWithType will create a volume of the given volume type
83+
// with a random name and size of 1GB. An error will be returned if
84+
// the volume was unable to be created.
85+
func CreateVolumeWithType(t *testing.T, client *gophercloud.ServiceClient, vt *volumetypes.VolumeType) (*volumes.Volume, error) {
86+
volumeName := tools.RandomString("ACPTTEST", 16)
87+
volumeDescription := tools.RandomString("ACPTTEST-DESC", 16)
88+
t.Logf("Attempting to create volume: %s", volumeName)
89+
90+
createOpts := volumes.CreateOpts{
91+
Size: 1,
92+
Name: volumeName,
93+
Description: volumeDescription,
94+
VolumeType: vt.Name,
95+
}
96+
97+
volume, err := volumes.Create(client, createOpts).Extract()
98+
if err != nil {
99+
return volume, err
100+
}
101+
102+
err = volumes.WaitForStatus(client, volume.ID, "available", 60)
103+
if err != nil {
104+
return volume, err
105+
}
106+
107+
tools.PrintResource(t, volume)
108+
th.AssertEquals(t, volume.Name, volumeName)
109+
th.AssertEquals(t, volume.Description, volumeDescription)
110+
th.AssertEquals(t, volume.Size, 1)
111+
th.AssertEquals(t, volume.VolumeType, vt.Name)
112+
113+
t.Logf("Successfully created volume: %s", volume.ID)
114+
115+
return volume, nil
116+
}
117+
82118
// CreateVolumeType will create a volume type with a random name. An
83119
// error will be returned if the volume was unable to be created.
84120
func CreateVolumeType(t *testing.T, client *gophercloud.ServiceClient) (*volumetypes.VolumeType, error) {
@@ -110,6 +146,36 @@ func CreateVolumeType(t *testing.T, client *gophercloud.ServiceClient) (*volumet
110146
return vt, nil
111147
}
112148

149+
// CreateVolumeTypeNoExtraSpecs will create a volume type with a random name and
150+
// no extra specs. This is required to bypass cinder-scheduler filters and be able
151+
// to create a volume with this volumeType. An error will be returned if the volume
152+
// type was unable to be created.
153+
func CreateVolumeTypeNoExtraSpecs(t *testing.T, client *gophercloud.ServiceClient) (*volumetypes.VolumeType, error) {
154+
name := tools.RandomString("ACPTTEST", 16)
155+
description := "create_from_gophercloud"
156+
t.Logf("Attempting to create volume type: %s", name)
157+
158+
createOpts := volumetypes.CreateOpts{
159+
Name: name,
160+
ExtraSpecs: map[string]string{},
161+
Description: description,
162+
}
163+
164+
vt, err := volumetypes.Create(client, createOpts).Extract()
165+
if err != nil {
166+
return nil, err
167+
}
168+
169+
tools.PrintResource(t, vt)
170+
th.AssertEquals(t, vt.IsPublic, true)
171+
th.AssertEquals(t, vt.Name, name)
172+
th.AssertEquals(t, vt.Description, description)
173+
174+
t.Logf("Successfully created volume type: %s", vt.ID)
175+
176+
return vt, nil
177+
}
178+
113179
// DeleteSnapshot will delete a snapshot. A fatal error will occur if the
114180
// snapshot failed to be deleted.
115181
func DeleteSnapshot(t *testing.T, client *gophercloud.ServiceClient, snapshot *snapshots.Snapshot) {
@@ -145,6 +211,20 @@ func DeleteVolume(t *testing.T, client *gophercloud.ServiceClient, volume *volum
145211
t.Fatalf("Unable to delete volume %s: %v", volume.ID, err)
146212
}
147213

214+
// VolumeTypes can't be deleted until their volumes have been,
215+
// so block until the volume is deleted.
216+
err = tools.WaitFor(func() (bool, error) {
217+
_, err := volumes.Get(client, volume.ID).Extract()
218+
if err != nil {
219+
return true, nil
220+
}
221+
222+
return false, nil
223+
})
224+
if err != nil {
225+
t.Fatalf("Error waiting for volume to delete: %v", err)
226+
}
227+
148228
t.Logf("Successfully deleted volume: %s", volume.ID)
149229
}
150230

openstack/blockstorage/extensions/volumeactions/doc.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,17 @@ Example of Setting a Volume's Bootable status
9393
if err != nil {
9494
panic(err)
9595
}
96+
97+
Example of Changing Type of a Volume
98+
99+
changeTypeOpts := volumeactions.ChangeTypeOpts{
100+
NewType: "ssd",
101+
MigrationPolicy: volumeactions.MigrationPolicyOnDemand,
102+
}
103+
104+
err = volumeactions.ChangeType(client, volumeID, changeTypeOpts).ExtractErr()
105+
if err != nil {
106+
panic(err)
107+
}
96108
*/
97109
package volumeactions

openstack/blockstorage/extensions/volumeactions/requests.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func Attach(client *gophercloud.ServiceClient, id string, opts AttachOptsBuilder
5454
return
5555
}
5656

57-
// BeginDetach will mark the volume as detaching.
57+
// BeginDetaching will mark the volume as detaching.
5858
func BeginDetaching(client *gophercloud.ServiceClient, id string) (r BeginDetachingResult) {
5959
b := map[string]interface{}{"os-begin_detaching": make(map[string]interface{})}
6060
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
@@ -343,3 +343,51 @@ func SetBootable(client *gophercloud.ServiceClient, id string, opts BootableOpts
343343
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
344344
return
345345
}
346+
347+
// MigrationPolicy type represents a migration_policy when changing types.
348+
type MigrationPolicy string
349+
350+
// Supported attributes for MigrationPolicy attribute for changeType operations.
351+
const (
352+
MigrationPolicyNever MigrationPolicy = "never"
353+
MigrationPolicyOnDemand MigrationPolicy = "on-demand"
354+
)
355+
356+
// ChangeTypeOptsBuilder allows extensions to add additional parameters to the
357+
// ChangeType request.
358+
type ChangeTypeOptsBuilder interface {
359+
ToVolumeChangeTypeMap() (map[string]interface{}, error)
360+
}
361+
362+
// ChangeTypeOpts contains options for changing the type of an existing Volume.
363+
// This object is passed to the volumes.ChangeType function.
364+
type ChangeTypeOpts struct {
365+
// NewType is the name of the new volume type of the volume.
366+
NewType string `json:"new_type" required:"true"`
367+
368+
// MigrationPolicy specifies if the volume should be migrated when it is
369+
// re-typed. Possible values are "on-demand" or "never". If not specified,
370+
// the default is "never".
371+
MigrationPolicy MigrationPolicy `json:"migration_policy,omitempty"`
372+
}
373+
374+
// ToVolumeChangeTypeMap assembles a request body based on the contents of an
375+
// ChangeTypeOpts.
376+
func (opts ChangeTypeOpts) ToVolumeChangeTypeMap() (map[string]interface{}, error) {
377+
return gophercloud.BuildRequestBody(opts, "os-retype")
378+
}
379+
380+
// ChangeType will change the volume type of the volume based on the provided information.
381+
// This operation does not return a response body.
382+
func ChangeType(client *gophercloud.ServiceClient, id string, opts ChangeTypeOptsBuilder) (r ChangeTypeResult) {
383+
b, err := opts.ToVolumeChangeTypeMap()
384+
if err != nil {
385+
r.Err = err
386+
return
387+
}
388+
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
389+
OkCodes: []int{202},
390+
})
391+
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
392+
return
393+
}

openstack/blockstorage/extensions/volumeactions/results.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,8 @@ func (r UploadImageResult) Extract() (VolumeImage, error) {
209209
type ForceDeleteResult struct {
210210
gophercloud.ErrResult
211211
}
212+
213+
// ChangeTypeResult contains the response body and error from an ChangeType request.
214+
type ChangeTypeResult struct {
215+
gophercloud.ErrResult
216+
}

openstack/blockstorage/extensions/volumeactions/testing/fixtures.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,27 @@ func MockSetBootableResponse(t *testing.T) {
326326
w.WriteHeader(http.StatusOK)
327327
})
328328
}
329+
330+
func MockChangeTypeResponse(t *testing.T) {
331+
th.Mux.HandleFunc("/volumes/cd281d77-8217-4830-be95-9528227c105c/action",
332+
func(w http.ResponseWriter, r *http.Request) {
333+
th.TestMethod(t, r, "POST")
334+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
335+
th.TestHeader(t, r, "Content-Type", "application/json")
336+
th.TestHeader(t, r, "Accept", "application/json")
337+
th.TestJSONRequest(t, r, `
338+
{
339+
"os-retype":
340+
{
341+
"new_type": "ssd",
342+
"migration_policy": "on-demand"
343+
}
344+
}
345+
`)
346+
347+
w.Header().Add("Content-Type", "application/json")
348+
w.WriteHeader(http.StatusAccepted)
349+
350+
fmt.Fprintf(w, `{}`)
351+
})
352+
}

openstack/blockstorage/extensions/volumeactions/testing/requests_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,18 @@ func TestSetBootable(t *testing.T) {
194194
err := volumeactions.SetBootable(client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr()
195195
th.AssertNoErr(t, err)
196196
}
197+
198+
func TestChangeType(t *testing.T) {
199+
th.SetupHTTP()
200+
defer th.TeardownHTTP()
201+
202+
MockChangeTypeResponse(t)
203+
204+
options := &volumeactions.ChangeTypeOpts{
205+
NewType: "ssd",
206+
MigrationPolicy: "on-demand",
207+
}
208+
209+
err := volumeactions.ChangeType(client.ServiceClient(), "cd281d77-8217-4830-be95-9528227c105c", options).ExtractErr()
210+
th.AssertNoErr(t, err)
211+
}

0 commit comments

Comments
 (0)