Skip to content

Commit 4f48f82

Browse files
authored
[PWX-24652] Oracle attach() detach() drive API implementation (#104)
* [PWX-24652] Implemented Attach() and Detach() API for oracle cloud Signed-off-by: Vinayak Shinde <vinayakshnd@gmail.com>
1 parent f357e29 commit 4f48f82

2 files changed

Lines changed: 179 additions & 16 deletions

File tree

oracle/oracle.go

Lines changed: 178 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"io/ioutil"
88
"net/http"
99
"os"
10+
"strings"
11+
"sync"
1012

1113
"github.com/libopenstorage/cloudops"
1214
"github.com/oracle/oci-go-sdk/v65/common"
@@ -47,15 +49,17 @@ const (
4749
type oracleOps struct {
4850
cloudops.Compute
4951
cloudops.Storage
50-
instance string
51-
region string
52-
availabilityDomain string
53-
compartmentID string
54-
tenancyID string
55-
poolID string
56-
storage core.BlockstorageClient
57-
compute core.ComputeClient
58-
containerEngine containerengine.ContainerEngineClient
52+
instance string
53+
region string
54+
availabilityDomain string
55+
compartmentID string
56+
tenancyID string
57+
poolID string
58+
volumeAttachmentMapping map[string]*string
59+
storage core.BlockstorageClient
60+
compute core.ComputeClient
61+
containerEngine containerengine.ContainerEngineClient
62+
mutex sync.Mutex
5963
}
6064

6165
// NewClient creates a new cloud operations client for Oracle cloud
@@ -85,6 +89,7 @@ func NewClient() (cloudops.Ops, error) {
8589
return nil, err
8690
}
8791

92+
oracleOps.volumeAttachmentMapping = map[string]*string{}
8893
// TODO: [PWX-18717] wrap around exponentialBackoffOps
8994
return oracleOps, nil
9095
}
@@ -301,7 +306,8 @@ func (o *oracleOps) DeviceMappings() (map[string]string, error) {
301306
m := make(map[string]string)
302307
var devicePath, volID string
303308
volumeAttachmentReq := core.ListVolumeAttachmentsRequest{
304-
InstanceId: common.String(o.instance),
309+
CompartmentId: common.String(o.compartmentID),
310+
InstanceId: common.String(o.instance),
305311
}
306312
volumeAttachmentResp, err := o.compute.ListVolumeAttachments(context.Background(), volumeAttachmentReq)
307313
if err != nil {
@@ -322,8 +328,10 @@ func (o *oracleOps) DeviceMappings() (map[string]string, error) {
322328

323329
func (o *oracleOps) DevicePath(volumeID string) (string, error) {
324330
volumeAttachmentReq := core.ListVolumeAttachmentsRequest{
325-
VolumeId: common.String(volumeID),
331+
CompartmentId: common.String(o.compartmentID),
332+
VolumeId: common.String(volumeID),
326333
}
334+
327335
volumeAttachmentResp, err := o.compute.ListVolumeAttachments(context.Background(), volumeAttachmentReq)
328336
if err != nil {
329337
return "", err
@@ -333,11 +341,13 @@ func (o *oracleOps) DevicePath(volumeID string) (string, error) {
333341
return "", cloudops.NewStorageError(cloudops.ErrVolDetached,
334342
"Volume is detached", volumeID)
335343
}
344+
336345
volumeAttachment := volumeAttachmentResp.Items[0]
337346
if volumeAttachment.GetInstanceId() == nil {
338347
return "", cloudops.NewStorageError(cloudops.ErrVolInval,
339348
"Unable to determine volume instance attachment", "")
340349
}
350+
341351
if o.instance != *volumeAttachment.GetInstanceId() {
342352
return "", cloudops.NewStorageError(cloudops.ErrVolAttachedOnRemoteNode,
343353
fmt.Sprintf("Volume attached on %q current instance %q",
@@ -350,10 +360,12 @@ func (o *oracleOps) DevicePath(volumeID string) (string, error) {
350360
fmt.Sprintf("Invalid state %q, volume is not attached",
351361
volumeAttachment.GetLifecycleState()), "")
352362
}
363+
353364
if volumeAttachment.GetDevice() == nil {
354365
return "", cloudops.NewStorageError(cloudops.ErrVolInval,
355366
"Unable to determine volume attachment path", "")
356367
}
368+
357369
return *volumeAttachment.GetDevice(), nil
358370
}
359371

@@ -368,14 +380,14 @@ func (o *oracleOps) Inspect(volumeIds []*string) ([]interface{}, error) {
368380
if err != nil {
369381
return nil, err
370382
}
371-
oracleVols = append(oracleVols, getVolResp.Volume)
383+
oracleVols = append(oracleVols, &getVolResp.Volume)
372384
}
373385
return oracleVols, nil
374386
}
375387

376388
// Create volume based on input template volume and also apply given labels.
377389
func (o *oracleOps) Create(template interface{}, labels map[string]string) (interface{}, error) {
378-
vol, ok := template.(core.Volume)
390+
vol, ok := template.(*core.Volume)
379391
if !ok {
380392
return nil, cloudops.NewStorageError(cloudops.ErrVolInval,
381393
"Invalid volume template given", "")
@@ -412,8 +424,8 @@ func (o *oracleOps) waitVolumeStatus(volID string, desiredStatus core.VolumeLife
412424
if err != nil {
413425
return nil, true, err
414426
}
415-
if getVolResp.Volume.LifecycleState == core.VolumeLifecycleStateAvailable {
416-
return getVolResp.Volume, false, nil
427+
if getVolResp.Volume.LifecycleState == desiredStatus {
428+
return &getVolResp.Volume, false, nil
417429
}
418430

419431
logrus.Debugf("volume [%s] is still in [%s] state", volID, getVolResp.Volume.LifecycleState)
@@ -444,3 +456,154 @@ func (o *oracleOps) Delete(volumeID string) error {
444456
}
445457
return nil
446458
}
459+
460+
// Attach volumeID, accepts attachOptions as opaque data
461+
// Return attach path.
462+
func (o *oracleOps) Attach(volumeID string, options map[string]string) (string, error) {
463+
o.mutex.Lock()
464+
defer o.mutex.Unlock()
465+
466+
devices, err := o.FreeDevices([]interface{}{}, "")
467+
if err != nil {
468+
return "", err
469+
}
470+
471+
for _, device := range devices {
472+
attachVolReq := core.AttachVolumeRequest{
473+
AttachVolumeDetails: core.AttachParavirtualizedVolumeDetails{
474+
InstanceId: common.String(o.instance),
475+
VolumeId: common.String(volumeID),
476+
Device: common.String(device),
477+
IsShareable: common.Bool(false),
478+
IsReadOnly: common.Bool(false),
479+
},
480+
}
481+
482+
attachVolResp, err := o.compute.AttachVolume(context.Background(), attachVolReq)
483+
if err != nil {
484+
if strings.Contains(err.Error(), "is already in use") {
485+
logrus.Infof("Skipping device: %s as it's in use. Will try next free device", device)
486+
continue
487+
}
488+
return "", err
489+
}
490+
491+
if attachVolResp.GetLifecycleState() != core.VolumeAttachmentLifecycleStateAttached {
492+
err = o.waitVolumeAttachmentStatus(
493+
attachVolResp.GetId(),
494+
core.VolumeAttachmentLifecycleStateAttached,
495+
)
496+
if err != nil {
497+
return "", err
498+
}
499+
}
500+
devicePath, err := o.DevicePath(volumeID)
501+
if err != nil {
502+
logrus.Errorf("Error while getting device path. Error: %v", err)
503+
}
504+
o.volumeAttachmentMapping[volumeID] = attachVolResp.GetId()
505+
return devicePath, err
506+
}
507+
return "", fmt.Errorf("failed to attach any of the free devices. Attempted: %v", devices)
508+
}
509+
510+
func (o *oracleOps) waitVolumeAttachmentStatus(volumeAttachmentID *string, desiredStatus core.VolumeAttachmentLifecycleStateEnum) error {
511+
getVolAttachmentReq := core.GetVolumeAttachmentRequest{
512+
VolumeAttachmentId: volumeAttachmentID,
513+
}
514+
f := func() (interface{}, bool, error) {
515+
getVolAttachmentResp, err := o.compute.GetVolumeAttachment(context.Background(), getVolAttachmentReq)
516+
if err != nil {
517+
return nil, true, err
518+
}
519+
if getVolAttachmentResp.GetLifecycleState() == desiredStatus {
520+
return nil, false, nil
521+
}
522+
523+
logrus.Debugf("volume [%s] is still in [%s] state", *getVolAttachmentResp.GetVolumeId(), getVolAttachmentResp.GetLifecycleState())
524+
return nil, true, fmt.Errorf("volume [%s] is still in [%s] state", *getVolAttachmentResp.GetVolumeId(), getVolAttachmentResp.GetLifecycleState())
525+
}
526+
_, err := task.DoRetryWithTimeout(f, cloudops.ProviderOpsTimeout, cloudops.ProviderOpsRetryInterval)
527+
return err
528+
}
529+
530+
// Detach volumeID.
531+
func (o *oracleOps) Detach(volumeID string) error {
532+
return o.detachInternal(volumeID, o.instance)
533+
}
534+
535+
// DetachFrom detaches the disk/volume with given ID from the given instance ID
536+
func (o *oracleOps) DetachFrom(volumeID, instanceID string) error {
537+
return o.detachInternal(volumeID, instanceID)
538+
}
539+
540+
func (o *oracleOps) detachInternal(volumeID, instanceID string) error {
541+
attachmentID, ok := o.volumeAttachmentMapping[volumeID]
542+
if !ok {
543+
logrus.Warnf("could not find volume attachment ID for volume [%s] locally", volumeID)
544+
listVolAttachmentReq := core.ListVolumeAttachmentsRequest{
545+
VolumeId: common.String(volumeID),
546+
InstanceId: common.String(instanceID),
547+
CompartmentId: common.String(o.compartmentID),
548+
AvailabilityDomain: common.String(o.availabilityDomain),
549+
}
550+
listVolAttachmentResp, err := o.compute.ListVolumeAttachments(context.Background(), listVolAttachmentReq)
551+
if err != nil {
552+
logrus.Errorf("error while getting attachments for volume [%s]. Response: [%+v]. Error: [%v]",
553+
volumeID, listVolAttachmentResp, err)
554+
return err
555+
}
556+
if len(listVolAttachmentResp.Items) > 0 {
557+
attachmentID = listVolAttachmentResp.Items[0].GetId()
558+
} else {
559+
return fmt.Errorf("volume [%s] is not attached to node [%s]", volumeID, instanceID)
560+
}
561+
}
562+
detachVolReq := core.DetachVolumeRequest{
563+
VolumeAttachmentId: attachmentID,
564+
}
565+
detachVolResp, err := o.compute.DetachVolume(context.Background(), detachVolReq)
566+
if err != nil {
567+
logrus.Errorf("error while detaching volume [%s] from instance [%s]. Response: [%+v]. Error: [%v]",
568+
volumeID, instanceID, detachVolResp, err)
569+
return err
570+
}
571+
err = o.waitVolumeAttachmentStatus(
572+
attachmentID,
573+
core.VolumeAttachmentLifecycleStateDetached,
574+
)
575+
if err == nil {
576+
o.mutex.Lock()
577+
delete(o.volumeAttachmentMapping, volumeID)
578+
o.mutex.Unlock()
579+
}
580+
return err
581+
}
582+
583+
// FreeDevices returns free block devices on the instance.
584+
// blockDeviceMappings is a data structure that contains all block devices on
585+
// the instance and where they are mapped to
586+
func (o *oracleOps) FreeDevices(
587+
blockDeviceMappings []interface{},
588+
rootDeviceName string) ([]string, error) {
589+
freeDevices := []string{}
590+
listDevicesReq := core.ListInstanceDevicesRequest{
591+
InstanceId: common.String(o.instance),
592+
IsAvailable: common.Bool(true),
593+
}
594+
respListDevices, err := o.compute.ListInstanceDevices(context.Background(), listDevicesReq)
595+
if err != nil {
596+
return freeDevices, err
597+
}
598+
for _, d := range respListDevices.Items {
599+
freeDevices = append(freeDevices, *d.Name)
600+
}
601+
return freeDevices, nil
602+
}
603+
604+
func (o *oracleOps) GetDeviceID(vol interface{}) (string, error) {
605+
if d, ok := vol.(*core.Volume); ok {
606+
return *d.Id, nil
607+
}
608+
return "", fmt.Errorf("invalid type: %v given to GetDeviceID", vol)
609+
}

oracle/oracle_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestAll(t *testing.T) {
3131

3232
compartmentID, _ := cloudops.GetEnvValueStrict(fmt.Sprintf("%s_%s", envPrefix, envCompartmentID))
3333
availabilityDomain, _ := cloudops.GetEnvValueStrict(fmt.Sprintf("%s_%s", envPrefix, envAvailabilityDomain))
34-
oracleVol := core.Volume{
34+
oracleVol := &core.Volume{
3535
SizeInGBs: common.Int64(newDiskSizeInGB),
3636
CompartmentId: common.String(compartmentID),
3737
DisplayName: &diskName,

0 commit comments

Comments
 (0)