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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package oci_test
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/http"
    10  
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/core/instance"
    17  	envcontext "github.com/juju/juju/environs/context"
    18  	"github.com/juju/juju/environs/tags"
    19  	"github.com/juju/juju/provider/oci"
    20  	"github.com/juju/juju/storage"
    21  
    22  	ociCore "github.com/oracle/oci-go-sdk/core"
    23  )
    24  
    25  type storageVolumeSuite struct {
    26  	commonSuite
    27  
    28  	provider   storage.Provider
    29  	environCtx envcontext.ProviderCallContext
    30  }
    31  
    32  var _ = gc.Suite(&storageVolumeSuite{})
    33  
    34  func (s *storageVolumeSuite) SetUpTest(c *gc.C) {
    35  	s.commonSuite.SetUpTest(c)
    36  
    37  	s.environCtx = envcontext.NewCloudCallContext()
    38  	var err error
    39  	s.provider, err = s.env.StorageProvider(oci.OciStorageProviderType)
    40  	c.Assert(err, gc.IsNil)
    41  }
    42  
    43  func (s *storageVolumeSuite) newVolumeSource(c *gc.C) storage.VolumeSource {
    44  	cfg, err := storage.NewConfig("iscsi", oci.OciStorageProviderType,
    45  		map[string]interface{}{
    46  			oci.OciVolumeType: oci.IscsiPool,
    47  		})
    48  	c.Assert(err, gc.IsNil)
    49  	c.Assert(cfg, gc.NotNil)
    50  
    51  	source, err := s.provider.VolumeSource(cfg)
    52  	c.Assert(err, gc.IsNil)
    53  	return source
    54  }
    55  
    56  func (s *storageVolumeSuite) setupCreateVolumesExpectations(tag names.VolumeTag, size int) {
    57  	name := tag.String()
    58  	volTags := map[string]string{
    59  		tags.JujuModel: s.env.Config().UUID(),
    60  	}
    61  
    62  	volume := ociCore.Volume{
    63  		AvailabilityDomain: makeStringPointer("fakeZone1"),
    64  		CompartmentId:      &s.testCompartment,
    65  		Id:                 makeStringPointer("fakeVolumeId"),
    66  		LifecycleState:     ociCore.VolumeLifecycleStateProvisioning,
    67  		FreeformTags:       volTags,
    68  		SizeInGBs:          &size,
    69  	}
    70  
    71  	requestDetails := ociCore.CreateVolumeDetails{
    72  		AvailabilityDomain: makeStringPointer("fakeZone1"),
    73  		CompartmentId:      &s.testCompartment,
    74  		DisplayName:        &name,
    75  		SizeInMBs:          &size,
    76  		FreeformTags:       volTags,
    77  	}
    78  
    79  	request := ociCore.CreateVolumeRequest{
    80  		CreateVolumeDetails: requestDetails,
    81  	}
    82  
    83  	response := ociCore.CreateVolumeResponse{
    84  		RawResponse: &http.Response{
    85  			StatusCode: 200,
    86  		},
    87  		Volume: volume,
    88  	}
    89  
    90  	volumeAvailable := volume
    91  	volumeAvailable.LifecycleState = ociCore.VolumeLifecycleStateAvailable
    92  
    93  	getVolumeRequest := ociCore.GetVolumeRequest{VolumeId: volumeAvailable.Id}
    94  	getVolumeResponse := ociCore.GetVolumeResponse{
    95  		Volume: volumeAvailable,
    96  	}
    97  	s.storage.EXPECT().CreateVolume(context.Background(), request).Return(response, nil)
    98  	s.storage.EXPECT().GetVolume(context.Background(), getVolumeRequest).Return(getVolumeResponse, nil).AnyTimes()
    99  
   100  }
   101  
   102  func (s *storageVolumeSuite) TestCreateVolumes(c *gc.C) {
   103  	ctrl := s.patchEnv(c)
   104  	defer ctrl.Finish()
   105  
   106  	source := s.newVolumeSource(c)
   107  	volumeTag := names.NewVolumeTag("1")
   108  	s.setupListInstancesExpectations(s.testInstanceID, ociCore.InstanceLifecycleStateRunning, 0)
   109  	s.setupCreateVolumesExpectations(volumeTag, 61440)
   110  
   111  	results, err := source.CreateVolumes(s.environCtx, []storage.VolumeParams{
   112  		{
   113  			Size:     uint64(61440),
   114  			Tag:      names.NewVolumeTag("1"),
   115  			Provider: oci.OciStorageProviderType,
   116  			Attachment: &storage.VolumeAttachmentParams{
   117  				AttachmentParams: storage.AttachmentParams{
   118  					InstanceId: instance.Id(s.testInstanceID),
   119  				},
   120  			},
   121  		},
   122  	})
   123  	c.Assert(err, gc.IsNil)
   124  	c.Assert(results, gc.HasLen, 1)
   125  	c.Assert(results[0].Error, jc.ErrorIsNil)
   126  }
   127  
   128  func (s *storageVolumeSuite) TestCreateVolumesInvalidSize(c *gc.C) {
   129  	source := s.newVolumeSource(c)
   130  	results, err := source.CreateVolumes(s.environCtx, []storage.VolumeParams{
   131  		{
   132  			Size:     uint64(2048),
   133  			Tag:      names.NewVolumeTag("1"),
   134  			Provider: oci.OciStorageProviderType,
   135  			Attachment: &storage.VolumeAttachmentParams{
   136  				AttachmentParams: storage.AttachmentParams{
   137  					InstanceId: instance.Id(s.testInstanceID),
   138  				},
   139  			},
   140  		},
   141  	})
   142  	c.Assert(err, gc.IsNil)
   143  	c.Assert(results, gc.HasLen, 1)
   144  	c.Check(results[0].Error, gc.ErrorMatches, "invalid volume size 2. Valid range is.*")
   145  }
   146  
   147  func (s *storageVolumeSuite) TestCreateVolumesNilParams(c *gc.C) {
   148  	source := s.newVolumeSource(c)
   149  	results, err := source.CreateVolumes(s.environCtx, nil)
   150  	c.Assert(err, gc.IsNil)
   151  	c.Assert(results, gc.HasLen, 0)
   152  }
   153  
   154  func (s *storageVolumeSuite) setupListVolumesExpectations(size int) map[string]ociCore.Volume {
   155  	volTags := map[string]string{
   156  		tags.JujuModel: s.env.Config().UUID(),
   157  	}
   158  	volumes := []ociCore.Volume{
   159  		{
   160  			AvailabilityDomain: makeStringPointer("fakeZone1"),
   161  			CompartmentId:      &s.testCompartment,
   162  			Id:                 makeStringPointer("fakeVolumeId"),
   163  			LifecycleState:     ociCore.VolumeLifecycleStateAvailable,
   164  			FreeformTags:       volTags,
   165  			SizeInGBs:          &size,
   166  		},
   167  		{
   168  			AvailabilityDomain: makeStringPointer("fakeZone1"),
   169  			CompartmentId:      &s.testCompartment,
   170  			Id:                 makeStringPointer("fakeVolumeId2"),
   171  			LifecycleState:     ociCore.VolumeLifecycleStateAvailable,
   172  			FreeformTags:       volTags,
   173  			SizeInGBs:          &size,
   174  		},
   175  	}
   176  
   177  	request := ociCore.ListVolumesRequest{
   178  		CompartmentId: &s.testCompartment,
   179  	}
   180  
   181  	response := ociCore.ListVolumesResponse{
   182  		Items: volumes,
   183  	}
   184  	s.storage.EXPECT().ListVolumes(context.Background(), request).Return(response, nil).AnyTimes()
   185  	asMap := map[string]ociCore.Volume{}
   186  	for _, vol := range volumes {
   187  		asMap[*vol.Id] = vol
   188  	}
   189  	return asMap
   190  }
   191  
   192  func (s *storageVolumeSuite) TestListVolumes(c *gc.C) {
   193  	ctrl := s.patchEnv(c)
   194  	defer ctrl.Finish()
   195  
   196  	s.setupListVolumesExpectations(60)
   197  
   198  	source := s.newVolumeSource(c)
   199  
   200  	volumes, err := source.ListVolumes(s.environCtx)
   201  	c.Assert(err, gc.IsNil)
   202  	c.Assert(len(volumes), gc.Equals, 2)
   203  	c.Assert(volumes, jc.SameContents, []string{"fakeVolumeId", "fakeVolumeId2"})
   204  }
   205  
   206  func (s *storageVolumeSuite) TestDescribeVolumes(c *gc.C) {
   207  	ctrl := s.patchEnv(c)
   208  	defer ctrl.Finish()
   209  
   210  	s.setupListVolumesExpectations(60)
   211  
   212  	source := s.newVolumeSource(c)
   213  
   214  	results, err := source.DescribeVolumes(s.environCtx, []string{"fakeVolumeId"})
   215  	c.Assert(err, gc.IsNil)
   216  	c.Assert(len(results), gc.Equals, 1)
   217  	c.Assert(results[0].VolumeInfo.VolumeId, gc.Equals, "fakeVolumeId")
   218  	c.Assert(results[0].VolumeInfo.Size, gc.Equals, uint64(60*1024))
   219  	c.Assert(results[0].VolumeInfo.Persistent, gc.Equals, true)
   220  
   221  	results, err = source.DescribeVolumes(s.environCtx, []string{"fakeVolumeId", "fakeVolumeId2"})
   222  	c.Assert(err, gc.IsNil)
   223  	c.Assert(len(results), gc.Equals, 2)
   224  
   225  	results, err = source.DescribeVolumes(s.environCtx, []string{"IDontExist", "fakeVolumeId2"})
   226  	c.Assert(err, gc.IsNil)
   227  	c.Assert(len(results), gc.Equals, 2)
   228  	c.Assert(results[0].Error, gc.NotNil)
   229  	c.Assert(results[1].Error, gc.IsNil)
   230  }
   231  
   232  func (s *storageVolumeSuite) TestValidateVolumeParams(c *gc.C) {
   233  	source := s.newVolumeSource(c)
   234  	params := storage.VolumeParams{
   235  		Size:     uint64(2048),
   236  		Tag:      names.NewVolumeTag("1"),
   237  		Provider: oci.OciStorageProviderType,
   238  		Attachment: &storage.VolumeAttachmentParams{
   239  			AttachmentParams: storage.AttachmentParams{
   240  				InstanceId: instance.Id(s.testInstanceID),
   241  			},
   242  		},
   243  	}
   244  
   245  	err := source.ValidateVolumeParams(params)
   246  	c.Assert(err, gc.ErrorMatches, "invalid volume size 2. Valid range is.*")
   247  
   248  	params.Size = 61440
   249  	err = source.ValidateVolumeParams(params)
   250  	c.Assert(err, gc.IsNil)
   251  }
   252  
   253  func (s *storageVolumeSuite) setupDeleteVolumesExpectations(size int, id string) {
   254  	volumes := s.setupListVolumesExpectations(size)
   255  
   256  	request := ociCore.DeleteVolumeRequest{
   257  		VolumeId: &id,
   258  	}
   259  	terminatedVol := volumes[id]
   260  	terminatedVol.LifecycleState = ociCore.VolumeLifecycleStateTerminated
   261  	response := ociCore.DeleteVolumeResponse{
   262  		RawResponse: &http.Response{
   263  			StatusCode: 200,
   264  		},
   265  	}
   266  	s.storage.EXPECT().DeleteVolume(context.Background(), request).Return(response, nil).AnyTimes()
   267  
   268  	getVolumeRequest := ociCore.GetVolumeRequest{VolumeId: terminatedVol.Id}
   269  	getVolumeResponse := ociCore.GetVolumeResponse{
   270  		Volume: terminatedVol,
   271  	}
   272  	s.storage.EXPECT().GetVolume(context.Background(), getVolumeRequest).Return(getVolumeResponse, nil).AnyTimes()
   273  }
   274  
   275  func (s *storageVolumeSuite) TestDestroyVolumes(c *gc.C) {
   276  	ctrl := s.patchEnv(c)
   277  	defer ctrl.Finish()
   278  
   279  	s.setupDeleteVolumesExpectations(60, "fakeVolumeId")
   280  
   281  	source := s.newVolumeSource(c)
   282  
   283  	results, err := source.DestroyVolumes(s.environCtx, []string{"fakeVolumeId"})
   284  	c.Assert(err, gc.IsNil)
   285  	c.Assert(len(results), gc.Equals, 1)
   286  	c.Assert(results[0], gc.IsNil)
   287  
   288  	results, err = source.DestroyVolumes(s.environCtx, []string{"bogusId"})
   289  	c.Assert(err, gc.IsNil)
   290  	c.Assert(len(results), gc.Equals, 1)
   291  	c.Assert(results[0], gc.ErrorMatches, "no such volume.*")
   292  }
   293  
   294  func (s *storageVolumeSuite) setupUpdateVolumesExpectations(id string) {
   295  	volumes := s.setupListVolumesExpectations(60)
   296  	vol := volumes[id]
   297  	volTags := map[string]string{
   298  		tags.JujuModel: "",
   299  	}
   300  
   301  	requestDetails := ociCore.UpdateVolumeDetails{
   302  		FreeformTags: volTags,
   303  	}
   304  	request := ociCore.UpdateVolumeRequest{
   305  		UpdateVolumeDetails: requestDetails,
   306  		VolumeId:            vol.Id,
   307  	}
   308  	s.storage.EXPECT().UpdateVolume(context.Background(), request).Return(ociCore.UpdateVolumeResponse{}, nil).AnyTimes()
   309  }
   310  
   311  func (s *storageVolumeSuite) TestReleaseVolumes(c *gc.C) {
   312  	ctrl := s.patchEnv(c)
   313  	defer ctrl.Finish()
   314  
   315  	s.setupUpdateVolumesExpectations("fakeVolumeId")
   316  	source := s.newVolumeSource(c)
   317  
   318  	results, err := source.ReleaseVolumes(s.environCtx, []string{"fakeVolumeId"})
   319  	c.Assert(err, gc.IsNil)
   320  	c.Assert(len(results), gc.Equals, 1)
   321  	c.Assert(results[0], gc.IsNil)
   322  
   323  	results, err = source.ReleaseVolumes(s.environCtx, []string{"IAmNotHereWhatIsHereIsntHereJustThereButWithoutTheT"})
   324  	c.Assert(err, gc.IsNil)
   325  	c.Assert(len(results), gc.Equals, 1)
   326  	c.Assert(results[0], gc.ErrorMatches, "no such volume.*")
   327  }
   328  
   329  func (s *storageVolumeSuite) setupGetInstanceExpectations(instance string, state ociCore.InstanceLifecycleStateEnum) {
   330  	requestMachine1, responseMachine1 := makeGetInstanceRequestResponse(
   331  		ociCore.Instance{
   332  			AvailabilityDomain: makeStringPointer("fakeZone1"),
   333  			CompartmentId:      &s.testCompartment,
   334  			Id:                 makeStringPointer(instance),
   335  			LifecycleState:     state,
   336  			Region:             makeStringPointer("us-phoenix-1"),
   337  			Shape:              makeStringPointer("VM.Standard1.1"),
   338  			DisplayName:        makeStringPointer("fakeName"),
   339  			FreeformTags:       s.tags,
   340  		},
   341  	)
   342  	s.compute.EXPECT().GetInstance(
   343  		context.Background(), requestMachine1).Return(
   344  		responseMachine1, nil).AnyTimes()
   345  }
   346  
   347  func (s *storageVolumeSuite) makeListVolumeAttachmentExpectations(instance string, volumeId string, returnEmpty bool, times int) {
   348  	request := ociCore.ListVolumeAttachmentsRequest{
   349  		CompartmentId: &s.testCompartment,
   350  		InstanceId:    &instance,
   351  	}
   352  	port := 3260
   353  	response := ociCore.ListVolumeAttachmentsResponse{}
   354  
   355  	if returnEmpty == false {
   356  		response.Items = []ociCore.VolumeAttachment{
   357  			ociCore.IScsiVolumeAttachment{
   358  				AvailabilityDomain: makeStringPointer("fakeZone1"),
   359  				InstanceId:         &instance,
   360  				CompartmentId:      &s.testCompartment,
   361  				Iqn:                makeStringPointer("bogus"),
   362  				Id:                 makeStringPointer("fakeVolumeAttachment1"),
   363  				VolumeId:           &volumeId,
   364  				Ipv4:               makeStringPointer("192.168.1.1"),
   365  				Port:               &port,
   366  				DisplayName:        makeStringPointer("fakeVolumeAttachment"),
   367  				ChapSecret:         makeStringPointer("superSecretPassword"),
   368  				ChapUsername:       makeStringPointer("JohnDoe"),
   369  				LifecycleState:     ociCore.VolumeAttachmentLifecycleStateAttached,
   370  			},
   371  		}
   372  	}
   373  	expect := s.compute.EXPECT().ListVolumeAttachments(context.Background(), request).Return(response, nil)
   374  	if times == 0 {
   375  		expect.AnyTimes()
   376  	} else {
   377  		expect.Times(times)
   378  	}
   379  }
   380  
   381  func (s *storageVolumeSuite) TestAttachVolumeWithExistingAttachment(c *gc.C) {
   382  	ctrl := s.patchEnv(c)
   383  	defer ctrl.Finish()
   384  
   385  	volumeId := "fakeVolumeId"
   386  	s.setupListInstancesExpectations(s.testInstanceID, ociCore.InstanceLifecycleStateRunning, 0)
   387  	s.setupGetInstanceExpectations(s.testInstanceID, ociCore.InstanceLifecycleStateRunning)
   388  	s.makeListVolumeAttachmentExpectations(s.testInstanceID, volumeId, false, 0)
   389  
   390  	source := s.newVolumeSource(c)
   391  
   392  	result, err := source.AttachVolumes(s.environCtx, []storage.VolumeAttachmentParams{
   393  		{
   394  			AttachmentParams: storage.AttachmentParams{
   395  				Provider:   oci.OciStorageProviderType,
   396  				InstanceId: instance.Id(s.testInstanceID),
   397  				ReadOnly:   false,
   398  				Machine:    names.NewMachineTag("1"),
   399  			},
   400  			VolumeId: volumeId,
   401  			Volume:   names.NewVolumeTag("1"),
   402  		},
   403  	})
   404  	c.Assert(err, gc.IsNil)
   405  	c.Assert(len(result), gc.Equals, 1)
   406  	c.Assert(result[0].Error, gc.IsNil)
   407  	planInfo := result[0].VolumeAttachment.VolumeAttachmentInfo.PlanInfo
   408  	c.Assert(planInfo.DeviceAttributes["iqn"], gc.Equals, "bogus")
   409  	c.Assert(planInfo.DeviceAttributes["address"], gc.Equals, "192.168.1.1")
   410  	c.Assert(planInfo.DeviceAttributes["port"], gc.Equals, "3260")
   411  	c.Assert(planInfo.DeviceAttributes["chap-user"], gc.Equals, "JohnDoe")
   412  	c.Assert(planInfo.DeviceAttributes["chap-secret"], gc.Equals, "superSecretPassword")
   413  
   414  }
   415  
   416  func (s *storageVolumeSuite) TestAttachVolumeWithInvalidInstanceState(c *gc.C) {
   417  	ctrl := s.patchEnv(c)
   418  	defer ctrl.Finish()
   419  
   420  	volumeId := "fakeVolumeId"
   421  	s.setupListInstancesExpectations(s.testInstanceID, ociCore.InstanceLifecycleStateTerminated, 0)
   422  
   423  	source := s.newVolumeSource(c)
   424  
   425  	result, err := source.AttachVolumes(s.environCtx, []storage.VolumeAttachmentParams{
   426  		{
   427  			AttachmentParams: storage.AttachmentParams{
   428  				Provider:   oci.OciStorageProviderType,
   429  				InstanceId: instance.Id(s.testInstanceID),
   430  				ReadOnly:   false,
   431  				Machine:    names.NewMachineTag("1"),
   432  			},
   433  			VolumeId: volumeId,
   434  			Volume:   names.NewVolumeTag("1"),
   435  		},
   436  	})
   437  	c.Assert(err, gc.IsNil)
   438  	c.Assert(len(result), gc.Equals, 1)
   439  	c.Assert(result[0].Error, gc.ErrorMatches, "invalid instance state for volume attachment:.*")
   440  }
   441  
   442  func (s *storageVolumeSuite) setupAttachNewVolumeExpectations(instance, volumeId, attachmentId string) {
   443  	useChap := true
   444  	displayName := fmt.Sprintf("%s_%s", instance, volumeId)
   445  	attachDetails := ociCore.AttachIScsiVolumeDetails{
   446  		InstanceId:  &instance,
   447  		VolumeId:    &volumeId,
   448  		UseChap:     &useChap,
   449  		DisplayName: &displayName,
   450  	}
   451  	request := ociCore.AttachVolumeRequest{
   452  		AttachVolumeDetails: attachDetails,
   453  	}
   454  
   455  	attachment := s.getVolumeAttachmentTemplate(instance, volumeId, attachmentId)
   456  	attachment.LifecycleState = ociCore.VolumeAttachmentLifecycleStateAttaching
   457  	response := ociCore.AttachVolumeResponse{
   458  		RawResponse: &http.Response{
   459  			StatusCode: 200,
   460  		},
   461  		VolumeAttachment: attachment,
   462  	}
   463  	s.compute.EXPECT().AttachVolume(context.Background(), request).Return(response, nil)
   464  
   465  }
   466  
   467  func (s *storageVolumeSuite) getVolumeAttachmentTemplate(instance, volume, attachment string) ociCore.IScsiVolumeAttachment {
   468  	port := 3260
   469  	return ociCore.IScsiVolumeAttachment{
   470  		AvailabilityDomain: makeStringPointer("fakeZone1"),
   471  		InstanceId:         &instance,
   472  		CompartmentId:      &s.testCompartment,
   473  		Iqn:                makeStringPointer("bogus"),
   474  		Id:                 &attachment,
   475  		VolumeId:           &volume,
   476  		Ipv4:               makeStringPointer("192.168.1.1"),
   477  		Port:               &port,
   478  		DisplayName:        makeStringPointer("fakeVolumeAttachment"),
   479  		ChapSecret:         makeStringPointer("superSecretPassword"),
   480  		ChapUsername:       makeStringPointer("JohnDoe"),
   481  		LifecycleState:     ociCore.VolumeAttachmentLifecycleStateAttaching,
   482  	}
   483  }
   484  
   485  func (s *storageVolumeSuite) setupGetVolumeAttachmentExpectations(
   486  	instance, volumeId, attachmentId string, state ociCore.VolumeAttachmentLifecycleStateEnum) {
   487  	request := ociCore.GetVolumeAttachmentRequest{
   488  		VolumeAttachmentId: &attachmentId,
   489  	}
   490  	attachment := s.getVolumeAttachmentTemplate(instance, volumeId, attachmentId)
   491  	attachment.LifecycleState = state
   492  	response := ociCore.GetVolumeAttachmentResponse{
   493  		VolumeAttachment: attachment,
   494  	}
   495  	s.compute.EXPECT().GetVolumeAttachment(context.Background(), request).Return(response, nil).AnyTimes()
   496  }
   497  
   498  func (s *storageVolumeSuite) TestAttachVolume(c *gc.C) {
   499  	ctrl := s.patchEnv(c)
   500  	defer ctrl.Finish()
   501  
   502  	volumeId := "fakeVolumeId"
   503  	attachId := "fakeVolumeAttachmentId"
   504  	s.setupListInstancesExpectations(s.testInstanceID, ociCore.InstanceLifecycleStateRunning, 0)
   505  	s.setupGetInstanceExpectations(s.testInstanceID, ociCore.InstanceLifecycleStateRunning)
   506  	s.makeListVolumeAttachmentExpectations(s.testInstanceID, volumeId, true, 1)
   507  	s.setupAttachNewVolumeExpectations(s.testInstanceID, volumeId, attachId)
   508  	s.setupGetVolumeAttachmentExpectations(
   509  		s.testInstanceID, volumeId, attachId,
   510  		ociCore.VolumeAttachmentLifecycleStateAttached)
   511  
   512  	source := s.newVolumeSource(c)
   513  
   514  	result, err := source.AttachVolumes(s.environCtx, []storage.VolumeAttachmentParams{
   515  		{
   516  			AttachmentParams: storage.AttachmentParams{
   517  				Provider:   oci.OciStorageProviderType,
   518  				InstanceId: instance.Id(s.testInstanceID),
   519  				ReadOnly:   false,
   520  				Machine:    names.NewMachineTag("1"),
   521  			},
   522  			VolumeId: volumeId,
   523  			Volume:   names.NewVolumeTag("1"),
   524  		},
   525  	})
   526  	c.Assert(err, gc.IsNil)
   527  	c.Assert(len(result), gc.Equals, 1)
   528  	c.Assert(result[0].Error, gc.IsNil)
   529  }
   530  
   531  func (s *storageVolumeSuite) setupDetachVolumesExpectations(attachmentId string) {
   532  	request := ociCore.DetachVolumeRequest{
   533  		VolumeAttachmentId: &attachmentId,
   534  	}
   535  	response := ociCore.DetachVolumeResponse{
   536  		RawResponse: &http.Response{
   537  			StatusCode: 200,
   538  		},
   539  	}
   540  	s.compute.EXPECT().DetachVolume(context.Background(), request).Return(response, nil).AnyTimes()
   541  }
   542  
   543  func (s *storageVolumeSuite) TestDetachVolume(c *gc.C) {
   544  	ctrl := s.patchEnv(c)
   545  	defer ctrl.Finish()
   546  
   547  	volumeId := "fakeVolumeId"
   548  	attachId := "fakeVolumeAttachment1"
   549  	s.setupListInstancesExpectations(s.testInstanceID, ociCore.InstanceLifecycleStateRunning, 0)
   550  	s.makeListVolumeAttachmentExpectations(s.testInstanceID, volumeId, false, 1)
   551  	s.setupDetachVolumesExpectations(attachId)
   552  	s.setupGetVolumeAttachmentExpectations(
   553  		s.testInstanceID, volumeId, attachId,
   554  		ociCore.VolumeAttachmentLifecycleStateDetached)
   555  
   556  	source := s.newVolumeSource(c)
   557  
   558  	result, err := source.DetachVolumes(s.environCtx, []storage.VolumeAttachmentParams{
   559  		{
   560  			AttachmentParams: storage.AttachmentParams{
   561  				Provider:   oci.OciStorageProviderType,
   562  				InstanceId: instance.Id(s.testInstanceID),
   563  				ReadOnly:   false,
   564  				Machine:    names.NewMachineTag("1"),
   565  			},
   566  			VolumeId: volumeId,
   567  			Volume:   names.NewVolumeTag("1"),
   568  		},
   569  	})
   570  
   571  	c.Assert(err, gc.IsNil)
   572  	c.Assert(len(result), gc.Equals, 1)
   573  }