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 (
4749type 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
323329func (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.
377389func (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+ }
0 commit comments