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