Skip to content

Commit c2baf4c

Browse files
[Feat.] New methods for attaching and detaching volumes for ecs v1 (#895)
1 parent 1a85427 commit c2baf4c

File tree

4 files changed

+226
-0
lines changed

4 files changed

+226
-0
lines changed

acceptance/openstack/ecs/v1/cloudservers_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package v1
33
import (
44
"testing"
55

6+
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
67
"github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients"
78
"github.com/opentelekomcloud/gophertelekomcloud/acceptance/openstack"
89
"github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools"
10+
"github.com/opentelekomcloud/gophertelekomcloud/openstack/blockstorage/v2/volumes"
911
"github.com/opentelekomcloud/gophertelekomcloud/openstack/common/tags"
1012
"github.com/opentelekomcloud/gophertelekomcloud/openstack/ecs/v1/cloudservers"
13+
"github.com/opentelekomcloud/gophertelekomcloud/openstack/ecs/v1/disk"
1114
"github.com/opentelekomcloud/gophertelekomcloud/openstack/ims/v2/images"
1215
th "github.com/opentelekomcloud/gophertelekomcloud/testhelper"
1316
)
@@ -182,3 +185,85 @@ func TestCloudServersIPV6(t *testing.T) {
182185
}
183186
th.AssertEquals(t, true, ipv6enabled)
184187
}
188+
189+
func TestCloudServerVolumeLifecycle(t *testing.T) {
190+
client, err := clients.NewComputeV1Client()
191+
th.AssertNoErr(t, err)
192+
193+
clientEvs, err := clients.NewBlockStorageV2Client()
194+
th.AssertNoErr(t, err)
195+
196+
az := clients.EnvOS.GetEnv("AVAILABILITY_ZONE")
197+
if az == "" {
198+
t.Skip("OS_AVAILABILITY_ZONE env vars is missing but ECSv1 test requires")
199+
}
200+
createVolumeOpts := volumes.CreateOpts{
201+
Size: 40,
202+
Name: tools.RandomString("tf-evs-disk-", 4),
203+
VolumeType: "SSD",
204+
AvailabilityZone: az,
205+
}
206+
207+
vol, err := volumes.Create(clientEvs, createVolumeOpts).Extract()
208+
th.AssertNoErr(t, err)
209+
210+
err = waitForEvsAvailable(clientEvs, 100, vol.ID)
211+
th.AssertNoErr(t, err)
212+
213+
t.Cleanup(func() {
214+
err = volumes.Delete(clientEvs, vol.ID, volumes.DeleteOpts{}).ExtractErr()
215+
th.AssertNoErr(t, err)
216+
})
217+
218+
// Get ECSv1 createOpts
219+
createOpts := openstack.GetCloudServerCreateOpts(t)
220+
221+
// Check ECSv1 createOpts
222+
openstack.DryRunCloudServerConfig(t, client, createOpts)
223+
t.Logf("CreateOpts are ok for creating a cloudServer")
224+
225+
// Create ECSv1 instance
226+
ecs := openstack.CreateCloudServer(t, client, createOpts)
227+
228+
t.Cleanup(func() {
229+
openstack.DeleteCloudServer(t, client, ecs.ID)
230+
})
231+
232+
t.Logf("Attaching volume to cloudserver: %s", vol.ID)
233+
attach, err := disk.Attach(client, disk.CreateOpts{
234+
ServerID: ecs.ID,
235+
VolumeAttachment: &disk.VolumeAttachment{
236+
VolumeID: vol.ID,
237+
},
238+
})
239+
th.AssertNoErr(t, err)
240+
241+
err = cloudservers.WaitForJobSuccess(client, 120, attach.JobID)
242+
th.AssertNoErr(t, err)
243+
244+
t.Logf("Get all attached volumes to cloudserver: %s", ecs.ID)
245+
attachments, err := disk.GetAttachments(client, ecs.ID)
246+
247+
tools.PrintResource(t, attachments)
248+
249+
t.Logf("Force Detaching volume from cloudserver: %s", vol.ID)
250+
detach, err := disk.Detach(client, ecs.ID, vol.ID, 1)
251+
th.AssertNoErr(t, err)
252+
253+
err = cloudservers.WaitForJobSuccess(client, 120, detach.JobID)
254+
th.AssertNoErr(t, err)
255+
}
256+
257+
func waitForEvsAvailable(client *golangsdk.ServiceClient, secs int, volId string) error {
258+
return golangsdk.WaitFor(secs, func() (bool, error) {
259+
vol, err := volumes.Get(client, volId).Extract()
260+
if err != nil {
261+
return false, err
262+
}
263+
264+
if vol.Status == "available" {
265+
return true, nil
266+
}
267+
return false, nil
268+
})
269+
}

openstack/ecs/v1/disk/Attach.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package disk
2+
3+
import (
4+
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
5+
"github.com/opentelekomcloud/gophertelekomcloud/internal/build"
6+
"github.com/opentelekomcloud/gophertelekomcloud/internal/extract"
7+
)
8+
9+
type CreateOpts struct {
10+
// ID of the ECS to which the disk will be attached.
11+
ServerID string `json:"-"`
12+
// Specifies the ECS attachment information.
13+
VolumeAttachment *VolumeAttachment `json:"volumeAttachment" required:"true"`
14+
// Specifies whether to check the request without actually attaching the disk.
15+
// If set to true, only a pre-check is performed and no disk is attached.
16+
// If set to false or omitted, the disk is attached after the check passes.
17+
DryRun *bool `json:"dry_run,omitempty"`
18+
}
19+
20+
type VolumeAttachment struct {
21+
// ID of the disk to be attached. The value is in UUID format.
22+
VolumeID string `json:"volumeId" required:"true"`
23+
// Disk device name, such as /dev/sda or /dev/vdb.
24+
Device string `json:"device,omitempty"`
25+
// Disk type, for example SSD.
26+
VolumeType string `json:"volume_type,omitempty"`
27+
// Number of disks to attach.
28+
Count int `json:"count,omitempty"`
29+
// Indicates whether to attach the disk in passthrough (SCSI) mode.
30+
// If set to "true", the disk device type is SCSI.
31+
// If set to "false", the disk device type is VBD.
32+
HwPassthrough string `json:"hw:passthrough,omitempty"`
33+
}
34+
35+
func Attach(client *golangsdk.ServiceClient, opts CreateOpts) (*JobResponse, error) {
36+
b, err := build.RequestBody(opts, "")
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
// POST /v1/{project_id}/cloudservers/{server_id}/attachvolume
42+
raw, err := client.Post(client.ServiceURL("cloudservers", opts.ServerID, "attachvolume"), b, nil, &golangsdk.RequestOpts{
43+
OkCodes: []int{200},
44+
})
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
var res JobResponse
50+
51+
err = extract.Into(raw.Body, &res)
52+
return &res, err
53+
}
54+
55+
type JobResponse struct {
56+
// ID of the task.
57+
JobID string `json:"job_id"`
58+
}

openstack/ecs/v1/disk/Detach.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package disk
2+
3+
import (
4+
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
5+
"github.com/opentelekomcloud/gophertelekomcloud/internal/extract"
6+
)
7+
8+
func Detach(client *golangsdk.ServiceClient, serverID, volumeID string, deleteFlag int) (*JobResponse, error) {
9+
url := client.ServiceURL("cloudservers", serverID, "detachvolume", volumeID)
10+
if deleteFlag != 0 {
11+
url += "?delete_flag=1"
12+
}
13+
14+
raw, err := client.Delete(url, &golangsdk.RequestOpts{
15+
OkCodes: []int{200},
16+
})
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
var res JobResponse
22+
err = extract.Into(raw.Body, &res)
23+
return &res, err
24+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package disk
2+
3+
import (
4+
golangsdk "github.com/opentelekomcloud/gophertelekomcloud"
5+
"github.com/opentelekomcloud/gophertelekomcloud/internal/extract"
6+
)
7+
8+
func GetAttachments(client *golangsdk.ServiceClient, serverID string) (*Attachments, error) {
9+
// GET /v1/{project_id}/cloudservers/{server_id}/block_device
10+
raw, err := client.Get(client.ServiceURL("cloudservers", serverID, "block_device"),
11+
nil, nil)
12+
if err != nil {
13+
return nil, err
14+
}
15+
16+
var res Attachments
17+
err = extract.Into(raw.Body, &res)
18+
return &res, err
19+
}
20+
21+
type Attachments struct {
22+
// Specifies the disks attached to an ECS.
23+
VolumeAttachments []Volume `json:"volumeAttachments"`
24+
// Specifies the number of disks that can be attached to an ECS.
25+
AttachableQuantity *AttachableQuantity `json:"attachableQuantity"`
26+
}
27+
28+
type Volume struct {
29+
// Specifies the ECS ID in UUID format.
30+
ServerID string `json:"serverId"`
31+
// Specifies the EVS disk ID in UUID format.
32+
VolumeID string `json:"volumeId"`
33+
// Specifies the mount ID, which is the same as the EVS disk ID.
34+
// The value is in UUID format.
35+
ID string `json:"id"`
36+
// Specifies the EVS disk size in GB.
37+
Size int `json:"size"`
38+
// Specifies the drive letter of the EVS disk, displayed as the
39+
// device name on the console, for example /dev/vda or /dev/vdb.
40+
Device string `json:"device"`
41+
// Specifies the PCI address.
42+
PCIAddress string `json:"pciAddress"`
43+
// Specifies the EVS disk boot sequence.
44+
// 0 indicates the system disk; a non-zero value indicates a data disk.
45+
BootIndex int `json:"bootIndex"`
46+
// Specifies the disk bus type.
47+
// Options: virtio and scsi.
48+
Bus string `json:"bus"`
49+
}
50+
51+
// AttachableQuantity describes how many additional disks can be attached.
52+
type AttachableQuantity struct {
53+
// Specifies the number of SCSI disks that can be attached to an ECS.
54+
FreeSCSI int `json:"free_scsi"`
55+
// Specifies the number of virtio_blk disks that can be attached to an ECS.
56+
FreeBLK int `json:"free_blk"`
57+
// Specifies the total number of disks that can be attached to an ECS.
58+
FreeDisk int `json:"free_disk"`
59+
}

0 commit comments

Comments
 (0)