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