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 }