github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oci/storage_volumes.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package oci
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/juju/clock"
    13  	"github.com/juju/errors"
    14  
    15  	"github.com/juju/juju/core/instance"
    16  	envcontext "github.com/juju/juju/environs/context"
    17  	"github.com/juju/juju/environs/tags"
    18  	allProvidersCommon "github.com/juju/juju/provider/common"
    19  	"github.com/juju/juju/provider/oci/common"
    20  	"github.com/juju/juju/storage"
    21  
    22  	ociCore "github.com/oracle/oci-go-sdk/core"
    23  )
    24  
    25  func mibToGib(m uint64) uint64 {
    26  	return (m + 1023) / 1024
    27  }
    28  
    29  // isAuthFailure is a helper function that's used to reduce line noise.
    30  // It's typically called within err != nil blocks.
    31  var isAuthFailure = func(err error, ctx envcontext.ProviderCallContext) bool {
    32  	return allProvidersCommon.MaybeHandleCredentialError(common.IsAuthorisationFailure, err, ctx)
    33  }
    34  
    35  type volumeSource struct {
    36  	env        *Environ
    37  	envName    string
    38  	modelUUID  string
    39  	storageAPI common.OCIStorageClient
    40  	computeAPI common.OCIComputeClient
    41  	clock      clock.Clock
    42  }
    43  
    44  var _ storage.VolumeSource = (*volumeSource)(nil)
    45  
    46  func (v *volumeSource) getVolumeStatus(resourceID *string) (string, error) {
    47  	request := ociCore.GetVolumeRequest{
    48  		VolumeId: resourceID,
    49  	}
    50  
    51  	response, err := v.storageAPI.GetVolume(context.Background(), request)
    52  	if err != nil {
    53  		if v.env.isNotFound(response.RawResponse) {
    54  			return "", errors.NotFoundf("volume not found: %s", *resourceID)
    55  		} else {
    56  			return "", err
    57  		}
    58  	}
    59  	return string(response.Volume.LifecycleState), nil
    60  }
    61  
    62  func (v *volumeSource) createVolume(ctx envcontext.ProviderCallContext, p storage.VolumeParams, instanceMap map[instance.Id]*ociInstance) (_ *storage.Volume, err error) {
    63  	var details ociCore.CreateVolumeResponse
    64  	defer func() {
    65  		if err != nil && details.Id != nil {
    66  			req := ociCore.DeleteVolumeRequest{
    67  				VolumeId: details.Id,
    68  			}
    69  			response, nestedErr := v.storageAPI.DeleteVolume(context.Background(), req)
    70  			if nestedErr != nil && !v.env.isNotFound(response.RawResponse) {
    71  				logger.Warningf("failed to cleanup volume: %s", *details.Id)
    72  				return
    73  			}
    74  			nestedErr = v.env.waitForResourceStatus(
    75  				v.getVolumeStatus, details.Id,
    76  				string(ociCore.VolumeLifecycleStateTerminated),
    77  				5*time.Minute)
    78  			if nestedErr != nil && !errors.IsNotFound(nestedErr) {
    79  				logger.Warningf("failed to cleanup volume: %s", *details.Id)
    80  				return
    81  			}
    82  		}
    83  	}()
    84  	if err := v.ValidateVolumeParams(p); err != nil {
    85  		return nil, errors.Trace(err)
    86  	}
    87  	if p.Attachment == nil {
    88  		return nil, errors.Errorf("volume %s has no attachments", p.Tag.String())
    89  	}
    90  	instanceId := p.Attachment.InstanceId
    91  	instance, ok := instanceMap[instanceId]
    92  	if !ok {
    93  		ociInstances, err := v.env.getOciInstances(ctx, instanceId)
    94  		if err != nil {
    95  			common.HandleCredentialError(err, ctx)
    96  			return nil, errors.Trace(err)
    97  		}
    98  		instance = ociInstances[0]
    99  		instanceMap[instanceId] = instance
   100  	}
   101  
   102  	availabilityZone := instance.availabilityZone()
   103  	name := p.Tag.String()
   104  
   105  	volTags := map[string]string{}
   106  	if p.ResourceTags != nil {
   107  		volTags = p.ResourceTags
   108  	}
   109  	volTags[tags.JujuModel] = v.modelUUID
   110  
   111  	size := int(p.Size)
   112  	requestDetails := ociCore.CreateVolumeDetails{
   113  		AvailabilityDomain: &availabilityZone,
   114  		CompartmentId:      v.env.ecfg().compartmentID(),
   115  		DisplayName:        &name,
   116  		SizeInMBs:          &size,
   117  		FreeformTags:       volTags,
   118  	}
   119  
   120  	request := ociCore.CreateVolumeRequest{
   121  		CreateVolumeDetails: requestDetails,
   122  	}
   123  
   124  	result, err := v.storageAPI.CreateVolume(context.Background(), request)
   125  	if err != nil {
   126  		return nil, errors.Trace(err)
   127  	}
   128  	err = v.env.waitForResourceStatus(
   129  		v.getVolumeStatus, result.Volume.Id,
   130  		string(ociCore.VolumeLifecycleStateAvailable),
   131  		5*time.Minute)
   132  	if err != nil {
   133  		return nil, errors.Trace(err)
   134  	}
   135  
   136  	volumeDetails, err := v.storageAPI.GetVolume(
   137  		context.Background(), ociCore.GetVolumeRequest{VolumeId: result.Volume.Id})
   138  	if err != nil {
   139  		common.HandleCredentialError(err, ctx)
   140  		return nil, errors.Trace(err)
   141  	}
   142  
   143  	return &storage.Volume{p.Tag, makeVolumeInfo(volumeDetails.Volume)}, nil
   144  }
   145  
   146  func makeVolumeInfo(vol ociCore.Volume) storage.VolumeInfo {
   147  	var size uint64
   148  	if vol.SizeInMBs != nil {
   149  		size = uint64(*vol.SizeInMBs)
   150  	} else if vol.SizeInGBs != nil {
   151  		size = uint64(*vol.SizeInGBs * 1024)
   152  	}
   153  
   154  	return storage.VolumeInfo{
   155  		VolumeId:   *vol.Id,
   156  		Size:       size,
   157  		Persistent: true,
   158  	}
   159  }
   160  
   161  func (v *volumeSource) CreateVolumes(ctx envcontext.ProviderCallContext, params []storage.VolumeParams) ([]storage.CreateVolumesResult, error) {
   162  	logger.Debugf("Creating volumes: %v", params)
   163  	if params == nil {
   164  		return []storage.CreateVolumesResult{}, nil
   165  	}
   166  	var credErr error
   167  
   168  	results := make([]storage.CreateVolumesResult, len(params))
   169  	instanceMap := map[instance.Id]*ociInstance{}
   170  	for i, volume := range params {
   171  		if credErr != nil {
   172  			results[i].Error = errors.Trace(credErr)
   173  			continue
   174  		}
   175  		vol, err := v.createVolume(ctx, volume, instanceMap)
   176  		if err != nil {
   177  			if isAuthFailure(err, ctx) {
   178  				credErr = err
   179  				common.HandleCredentialError(err, ctx)
   180  			}
   181  			results[i].Error = errors.Trace(err)
   182  			continue
   183  		}
   184  		results[i].Volume = vol
   185  	}
   186  	return results, nil
   187  }
   188  
   189  func (v *volumeSource) allVolumes() (map[string]ociCore.Volume, error) {
   190  	result := map[string]ociCore.Volume{}
   191  	request := ociCore.ListVolumesRequest{
   192  		CompartmentId: v.env.ecfg().compartmentID(),
   193  	}
   194  	response, err := v.storageAPI.ListVolumes(context.Background(), request)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	for _, val := range response.Items {
   200  		if t, ok := val.FreeformTags[tags.JujuModel]; !ok {
   201  			continue
   202  		} else {
   203  			if t != "" && t != v.modelUUID {
   204  				continue
   205  			}
   206  		}
   207  		result[*val.Id] = val
   208  	}
   209  	return result, nil
   210  }
   211  
   212  func (v *volumeSource) ListVolumes(ctx envcontext.ProviderCallContext) ([]string, error) {
   213  	ids := []string{}
   214  	volumes, err := v.allVolumes()
   215  	if err != nil {
   216  		common.HandleCredentialError(err, ctx)
   217  		return nil, errors.Trace(err)
   218  	}
   219  
   220  	for k := range volumes {
   221  		ids = append(ids, k)
   222  	}
   223  	return ids, nil
   224  }
   225  
   226  func (v *volumeSource) DescribeVolumes(ctx envcontext.ProviderCallContext, volIds []string) ([]storage.DescribeVolumesResult, error) {
   227  	result := make([]storage.DescribeVolumesResult, len(volIds), len(volIds))
   228  
   229  	allVolumes, err := v.allVolumes()
   230  	if err != nil {
   231  		common.HandleCredentialError(err, ctx)
   232  		return nil, errors.Trace(err)
   233  	}
   234  
   235  	for i, val := range volIds {
   236  		if volume, ok := allVolumes[val]; ok {
   237  			volumeInfo := makeVolumeInfo(volume)
   238  			result[i].VolumeInfo = &volumeInfo
   239  		} else {
   240  			result[i].Error = errors.NotFoundf("%s", volume)
   241  		}
   242  	}
   243  	return result, nil
   244  }
   245  
   246  func (v *volumeSource) DestroyVolumes(ctx envcontext.ProviderCallContext, volIds []string) ([]error, error) {
   247  	volumes, err := v.allVolumes()
   248  	if err != nil {
   249  		common.HandleCredentialError(err, ctx)
   250  		return nil, errors.Trace(err)
   251  	}
   252  
   253  	var credErr error
   254  	errs := make([]error, len(volIds))
   255  
   256  	for idx, volId := range volIds {
   257  		if credErr != nil {
   258  			errs[idx] = errors.Trace(credErr)
   259  			continue
   260  		}
   261  		volumeDetails, ok := volumes[volId]
   262  		if !ok {
   263  			errs[idx] = errors.NotFoundf("no such volume %s", volId)
   264  			continue
   265  		}
   266  		request := ociCore.DeleteVolumeRequest{
   267  			VolumeId: volumeDetails.Id,
   268  		}
   269  
   270  		response, err := v.storageAPI.DeleteVolume(context.Background(), request)
   271  		if err != nil && !v.env.isNotFound(response.RawResponse) {
   272  			if isAuthFailure(err, ctx) {
   273  				common.HandleCredentialError(err, ctx)
   274  				credErr = err
   275  			}
   276  			errs[idx] = errors.Trace(err)
   277  			continue
   278  		}
   279  		err = v.env.waitForResourceStatus(
   280  			v.getVolumeStatus, volumeDetails.Id,
   281  			string(ociCore.VolumeLifecycleStateTerminated),
   282  			5*time.Minute)
   283  		if err != nil && !errors.IsNotFound(err) {
   284  			if isAuthFailure(err, ctx) {
   285  				common.HandleCredentialError(err, ctx)
   286  				credErr = err
   287  			}
   288  			errs[idx] = errors.Trace(err)
   289  		} else {
   290  			errs[idx] = nil
   291  		}
   292  	}
   293  	return errs, nil
   294  }
   295  
   296  func (v *volumeSource) ReleaseVolumes(ctx envcontext.ProviderCallContext, volIds []string) ([]error, error) {
   297  	volumes, err := v.allVolumes()
   298  	if err != nil {
   299  		return nil, errors.Trace(err)
   300  	}
   301  
   302  	var credErr error
   303  	errs := make([]error, len(volIds))
   304  	tagsToRemove := []string{
   305  		tags.JujuModel,
   306  		tags.JujuController,
   307  	}
   308  	for idx, volId := range volIds {
   309  		if credErr != nil {
   310  			errs[idx] = errors.Trace(credErr)
   311  			continue
   312  		}
   313  		volumeDetails, ok := volumes[volId]
   314  		if !ok {
   315  			errs[idx] = errors.NotFoundf("no such volume %s", volId)
   316  			continue
   317  		}
   318  		currentTags := volumeDetails.FreeformTags
   319  		needsUpdate := false
   320  		for _, tag := range tagsToRemove {
   321  			if _, ok := currentTags[tag]; ok {
   322  				needsUpdate = true
   323  				currentTags[tag] = ""
   324  			}
   325  		}
   326  		if needsUpdate {
   327  			requestDetails := ociCore.UpdateVolumeDetails{
   328  				FreeformTags: currentTags,
   329  			}
   330  			request := ociCore.UpdateVolumeRequest{
   331  				UpdateVolumeDetails: requestDetails,
   332  				VolumeId:            volumeDetails.Id,
   333  			}
   334  
   335  			_, err := v.storageAPI.UpdateVolume(context.Background(), request)
   336  			if err != nil {
   337  				if isAuthFailure(err, ctx) {
   338  					common.HandleCredentialError(err, ctx)
   339  					credErr = err
   340  				}
   341  				errs[idx] = errors.Trace(err)
   342  			} else {
   343  				errs[idx] = nil
   344  			}
   345  		}
   346  	}
   347  	return errs, nil
   348  }
   349  
   350  func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   351  	size := mibToGib(params.Size)
   352  	if size < minVolumeSizeInGB || size > maxVolumeSizeInGB {
   353  		return errors.Errorf(
   354  			"invalid volume size %d. Valid range is %d - %d (GiB)", size, minVolumeSizeInGB, maxVolumeSizeInGB)
   355  	}
   356  	return nil
   357  }
   358  
   359  func (v *volumeSource) volumeAttachments(instanceId instance.Id) ([]ociCore.IScsiVolumeAttachment, error) {
   360  	instId := string(instanceId)
   361  	request := ociCore.ListVolumeAttachmentsRequest{
   362  		CompartmentId: v.env.ecfg().compartmentID(),
   363  		InstanceId:    &instId,
   364  	}
   365  	result, err := v.computeAPI.ListVolumeAttachments(context.Background(), request)
   366  	if err != nil {
   367  		return nil, errors.Trace(err)
   368  	}
   369  	ret := make([]ociCore.IScsiVolumeAttachment, len(result.Items))
   370  
   371  	for idx, att := range result.Items {
   372  		// The oracle oci client will return a VolumeAttachment type, which is an
   373  		// interface. This is due to the fact that they will at some point support
   374  		// different attachment types. For the moment, there is only iSCSI, as stated
   375  		// in the documentation, at the time of this writing:
   376  		// https://docs.us-phoenix-1.oraclecloud.com/api/#/en/iaas/20160918/requests/AttachVolumeDetails
   377  		//
   378  		// So we need to cast it back to IScsiVolumeAttachment{} to be able to access
   379  		// the connection info we need, and possibly chap secrets to be able to connect
   380  		// to the volume.
   381  		baseType, ok := att.(ociCore.IScsiVolumeAttachment)
   382  		if !ok {
   383  			return nil, errors.Errorf("invalid attachment type. Expected iscsi")
   384  		}
   385  
   386  		if baseType.LifecycleState == ociCore.VolumeAttachmentLifecycleStateDetached {
   387  			continue
   388  		}
   389  		ret[idx] = baseType
   390  	}
   391  	return ret, nil
   392  }
   393  
   394  func makeVolumeAttachmentResult(attachment ociCore.IScsiVolumeAttachment, param storage.VolumeAttachmentParams) (storage.AttachVolumesResult, error) {
   395  	if attachment.Port == nil || attachment.Iqn == nil {
   396  		return storage.AttachVolumesResult{}, errors.Errorf("invalid attachment info")
   397  	}
   398  	port := strconv.Itoa(*attachment.Port)
   399  	planInfo := &storage.VolumeAttachmentPlanInfo{
   400  		DeviceType: storage.DeviceTypeISCSI,
   401  		DeviceAttributes: map[string]string{
   402  			"iqn":     *attachment.Iqn,
   403  			"address": *attachment.Ipv4,
   404  			"port":    port,
   405  		},
   406  	}
   407  	if attachment.ChapSecret != nil && attachment.ChapUsername != nil {
   408  		planInfo.DeviceAttributes["chap-user"] = *attachment.ChapUsername
   409  		planInfo.DeviceAttributes["chap-secret"] = *attachment.ChapSecret
   410  	}
   411  	result := storage.AttachVolumesResult{
   412  		VolumeAttachment: &storage.VolumeAttachment{
   413  			param.Volume,
   414  			param.Machine,
   415  			storage.VolumeAttachmentInfo{
   416  				PlanInfo: planInfo,
   417  			},
   418  		},
   419  	}
   420  	return result, nil
   421  }
   422  
   423  func (v *volumeSource) attachVolume(ctx envcontext.ProviderCallContext, param storage.VolumeAttachmentParams) (_ storage.AttachVolumesResult, err error) {
   424  	var details ociCore.AttachVolumeResponse
   425  	defer func() {
   426  		volAttach := details.VolumeAttachment
   427  		if volAttach != nil && err != nil && volAttach.GetId() != nil {
   428  			req := ociCore.DetachVolumeRequest{
   429  				VolumeAttachmentId: volAttach.GetId(),
   430  			}
   431  			_, nestedErr := v.computeAPI.DetachVolume(context.Background(), req)
   432  			if nestedErr != nil {
   433  				logger.Warningf("failed to cleanup volume attachment: %v", volAttach.GetId())
   434  				return
   435  			}
   436  			nestedErr = v.env.waitForResourceStatus(
   437  				v.getAttachmentStatus, volAttach.GetId(),
   438  				string(ociCore.VolumeAttachmentLifecycleStateDetached),
   439  				5*time.Minute)
   440  			if nestedErr != nil && !errors.IsNotFound(nestedErr) {
   441  				logger.Warningf("failed to cleanup volume attachment: %v", volAttach.GetId())
   442  				return
   443  			}
   444  		}
   445  	}()
   446  
   447  	instances, err := v.env.getOciInstances(ctx, param.InstanceId)
   448  	if err != nil {
   449  		common.HandleCredentialError(err, ctx)
   450  		return storage.AttachVolumesResult{}, errors.Trace(err)
   451  	}
   452  	if len(instances) != 1 {
   453  		return storage.AttachVolumesResult{}, errors.Errorf("expected 1 instance, got %d", len(instances))
   454  	}
   455  	instance := instances[0]
   456  	if instance.raw.LifecycleState == ociCore.InstanceLifecycleStateTerminated || instance.raw.LifecycleState == ociCore.InstanceLifecycleStateTerminating {
   457  		return storage.AttachVolumesResult{}, errors.Errorf("invalid instance state for volume attachment: %s", instance.raw.LifecycleState)
   458  	}
   459  
   460  	if err := instance.waitForMachineStatus(
   461  		ociCore.InstanceLifecycleStateRunning,
   462  		5*time.Minute); err != nil {
   463  		return storage.AttachVolumesResult{}, errors.Trace(err)
   464  	}
   465  
   466  	volumeAttachments, err := v.volumeAttachments(param.InstanceId)
   467  	if err != nil {
   468  		common.HandleCredentialError(err, ctx)
   469  		return storage.AttachVolumesResult{}, errors.Trace(err)
   470  	}
   471  
   472  	for _, val := range volumeAttachments {
   473  		if val.VolumeId == nil || val.InstanceId == nil {
   474  			continue
   475  		}
   476  		if *val.VolumeId == param.VolumeId && *val.InstanceId == string(param.InstanceId) {
   477  			// Volume already attached. Return info.
   478  			return makeVolumeAttachmentResult(val, param)
   479  		}
   480  	}
   481  
   482  	instID := string(param.InstanceId)
   483  	useChap := true
   484  	displayName := fmt.Sprintf("%s_%s", instID, param.VolumeId)
   485  	attachDetails := ociCore.AttachIScsiVolumeDetails{
   486  		InstanceId:  &instID,
   487  		VolumeId:    &param.VolumeId,
   488  		UseChap:     &useChap,
   489  		DisplayName: &displayName,
   490  	}
   491  	request := ociCore.AttachVolumeRequest{
   492  		AttachVolumeDetails: attachDetails,
   493  	}
   494  
   495  	details, err = v.computeAPI.AttachVolume(context.Background(), request)
   496  	if err != nil {
   497  		common.HandleCredentialError(err, ctx)
   498  		return storage.AttachVolumesResult{}, errors.Trace(err)
   499  	}
   500  
   501  	err = v.env.waitForResourceStatus(
   502  		v.getAttachmentStatus, details.VolumeAttachment.GetId(),
   503  		string(ociCore.VolumeAttachmentLifecycleStateAttached),
   504  		5*time.Minute)
   505  	if err != nil {
   506  		common.HandleCredentialError(err, ctx)
   507  		return storage.AttachVolumesResult{}, errors.Trace(err)
   508  	}
   509  
   510  	detailsReq := ociCore.GetVolumeAttachmentRequest{
   511  		VolumeAttachmentId: details.VolumeAttachment.GetId(),
   512  	}
   513  
   514  	response, err := v.computeAPI.GetVolumeAttachment(context.Background(), detailsReq)
   515  	if err != nil {
   516  		common.HandleCredentialError(err, ctx)
   517  		return storage.AttachVolumesResult{}, errors.Trace(err)
   518  	}
   519  
   520  	baseType, ok := response.VolumeAttachment.(ociCore.IScsiVolumeAttachment)
   521  	if !ok {
   522  		return storage.AttachVolumesResult{}, errors.Errorf("invalid attachment type. Expected iscsi")
   523  	}
   524  
   525  	return makeVolumeAttachmentResult(baseType, param)
   526  }
   527  
   528  func (v *volumeSource) getAttachmentStatus(resourceID *string) (string, error) {
   529  	request := ociCore.GetVolumeAttachmentRequest{
   530  		VolumeAttachmentId: resourceID,
   531  	}
   532  
   533  	response, err := v.computeAPI.GetVolumeAttachment(context.Background(), request)
   534  	if err != nil {
   535  		if v.env.isNotFound(response.RawResponse) {
   536  			return "", errors.NotFoundf("volume attachment not found: %s", *resourceID)
   537  		} else {
   538  			return "", err
   539  		}
   540  	}
   541  	return string(response.VolumeAttachment.GetLifecycleState()), nil
   542  }
   543  
   544  func (v *volumeSource) AttachVolumes(ctx envcontext.ProviderCallContext, params []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   545  	var credErr error
   546  
   547  	instanceIds := []instance.Id{}
   548  	for _, val := range params {
   549  		instanceIds = append(instanceIds, val.InstanceId)
   550  	}
   551  	if len(instanceIds) == 0 {
   552  		return []storage.AttachVolumesResult{}, nil
   553  	}
   554  	instancesAsMap, err := v.env.getOciInstancesAsMap(ctx, instanceIds...)
   555  	if err != nil {
   556  		if isAuthFailure(err, ctx) {
   557  			common.HandleCredentialError(err, ctx)
   558  			credErr = err
   559  		}
   560  		return []storage.AttachVolumesResult{}, errors.Trace(err)
   561  	}
   562  
   563  	ret := make([]storage.AttachVolumesResult, len(params))
   564  	for idx, volParam := range params {
   565  		if credErr != nil {
   566  			ret[idx].Error = errors.Trace(credErr)
   567  			continue
   568  		}
   569  		_, ok := instancesAsMap[volParam.InstanceId]
   570  		if !ok {
   571  			// this really should not happen, given how getOciInstancesAsMap()
   572  			// works
   573  			ret[idx].Error = errors.NotFoundf("instance %q was not found", volParam.InstanceId)
   574  			continue
   575  		}
   576  
   577  		result, err := v.attachVolume(ctx, volParam)
   578  		if err != nil {
   579  			if isAuthFailure(err, ctx) {
   580  				common.HandleCredentialError(err, ctx)
   581  				credErr = err
   582  			}
   583  			ret[idx].Error = errors.Trace(err)
   584  		} else {
   585  			ret[idx] = result
   586  		}
   587  	}
   588  	return ret, nil
   589  }
   590  
   591  func (v *volumeSource) DetachVolumes(ctx envcontext.ProviderCallContext, params []storage.VolumeAttachmentParams) ([]error, error) {
   592  	var credErr error
   593  	ret := make([]error, len(params))
   594  	instanceAttachmentMap := map[instance.Id][]ociCore.IScsiVolumeAttachment{}
   595  
   596  	for idx, param := range params {
   597  		if credErr != nil {
   598  			ret[idx] = errors.Trace(credErr)
   599  			continue
   600  		}
   601  
   602  		instAtt, ok := instanceAttachmentMap[param.InstanceId]
   603  		if !ok {
   604  			currentAttachments, err := v.volumeAttachments(param.InstanceId)
   605  			if err != nil {
   606  				if isAuthFailure(err, ctx) {
   607  					credErr = err
   608  					common.HandleCredentialError(err, ctx)
   609  				}
   610  				ret[idx] = errors.Trace(err)
   611  				continue
   612  			}
   613  			instAtt = currentAttachments
   614  			instanceAttachmentMap[param.InstanceId] = instAtt
   615  		}
   616  		for _, attachment := range instAtt {
   617  			if credErr != nil {
   618  				ret[idx] = errors.Trace(credErr)
   619  				continue
   620  			}
   621  			logger.Tracef("volume ID is: %v", attachment.VolumeId)
   622  			if attachment.VolumeId != nil && param.VolumeId == *attachment.VolumeId && attachment.LifecycleState != ociCore.VolumeAttachmentLifecycleStateDetached {
   623  				if attachment.LifecycleState != ociCore.VolumeAttachmentLifecycleStateDetaching {
   624  					request := ociCore.DetachVolumeRequest{
   625  						VolumeAttachmentId: attachment.Id,
   626  					}
   627  
   628  					_, err := v.computeAPI.DetachVolume(context.Background(), request)
   629  					if err != nil {
   630  						if isAuthFailure(err, ctx) {
   631  							credErr = err
   632  							common.HandleCredentialError(err, ctx)
   633  						}
   634  						ret[idx] = errors.Trace(err)
   635  						break
   636  					}
   637  				}
   638  				err := v.env.waitForResourceStatus(
   639  					v.getAttachmentStatus, attachment.Id,
   640  					string(ociCore.VolumeAttachmentLifecycleStateDetached),
   641  					5*time.Minute)
   642  				if err != nil && !errors.IsNotFound(err) {
   643  					if isAuthFailure(err, ctx) {
   644  						credErr = err
   645  						common.HandleCredentialError(err, ctx)
   646  					}
   647  					ret[idx] = errors.Trace(err)
   648  					logger.Warningf("failed to detach volume: %s", *attachment.Id)
   649  				} else {
   650  					ret[idx] = nil
   651  				}
   652  			}
   653  		}
   654  	}
   655  	return ret, nil
   656  }