github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/openstack/cinder_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package openstack_test 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 gitjujutesting "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils" 14 gc "gopkg.in/check.v1" 15 "gopkg.in/goose.v2/cinder" 16 gooseerrors "gopkg.in/goose.v2/errors" 17 "gopkg.in/goose.v2/identity" 18 "gopkg.in/goose.v2/nova" 19 "gopkg.in/juju/names.v2" 20 21 "github.com/juju/juju/core/instance" 22 "github.com/juju/juju/environs/context" 23 "github.com/juju/juju/environs/tags" 24 "github.com/juju/juju/provider/openstack" 25 "github.com/juju/juju/storage" 26 "github.com/juju/juju/testing" 27 ) 28 29 const ( 30 mockVolId = "0" 31 mockVolSize = 1024 * 2 32 mockVolName = "123" 33 mockServerId = "mock-server-id" 34 mockVolJson = `{"volume":{"id": "` + mockVolId + `", "size":1,"name":"` + mockVolName + `"}}` 35 ) 36 37 var ( 38 mockVolumeTag = names.NewVolumeTag(mockVolName) 39 mockMachineTag = names.NewMachineTag("456") 40 ) 41 42 var _ = gc.Suite(&cinderVolumeSourceSuite{}) 43 44 type cinderVolumeSourceSuite struct { 45 testing.BaseSuite 46 47 callCtx *context.CloudCallContext 48 invalidCredential bool 49 } 50 51 func (s *cinderVolumeSourceSuite) SetUpTest(c *gc.C) { 52 s.BaseSuite.SetUpTest(c) 53 s.callCtx = &context.CloudCallContext{ 54 InvalidateCredentialFunc: func(string) error { 55 s.invalidCredential = true 56 return nil 57 }, 58 } 59 } 60 61 func (s *cinderVolumeSourceSuite) TearDownTest(c *gc.C) { 62 s.invalidCredential = false 63 s.BaseSuite.TearDownTest(c) 64 } 65 66 func init() { 67 // Override attempt strategy to speed things up. 68 openstack.CinderAttempt.Delay = 0 69 } 70 71 func toStringPtr(s string) *string { 72 return &s 73 } 74 75 func (s *cinderVolumeSourceSuite) TestAttachVolumes(c *gc.C) { 76 mockAdapter := &mockAdapter{ 77 attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) { 78 c.Check(volId, gc.Equals, mockVolId) 79 c.Check(serverId, gc.Equals, mockServerId) 80 return &nova.VolumeAttachment{ 81 Id: volId, 82 VolumeId: volId, 83 ServerId: serverId, 84 Device: toStringPtr("/dev/sda"), 85 }, nil 86 }, 87 } 88 89 volSource := openstack.NewCinderVolumeSource(mockAdapter) 90 results, err := volSource.AttachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{ 91 Volume: mockVolumeTag, 92 VolumeId: mockVolId, 93 AttachmentParams: storage.AttachmentParams{ 94 Provider: openstack.CinderProviderType, 95 Machine: mockMachineTag, 96 InstanceId: instance.Id(mockServerId), 97 }}, 98 }) 99 c.Assert(err, jc.ErrorIsNil) 100 c.Check(results, jc.DeepEquals, []storage.AttachVolumesResult{{ 101 VolumeAttachment: &storage.VolumeAttachment{ 102 mockVolumeTag, 103 mockMachineTag, 104 storage.VolumeAttachmentInfo{ 105 DeviceName: "sda", 106 }, 107 }, 108 }}) 109 } 110 111 var testUnauthorisedGooseError = gooseerrors.NewUnauthorisedf(nil, "", "invalid auth") 112 113 func (s *cinderVolumeSourceSuite) TestAttachVolumesInvalidCredential(c *gc.C) { 114 c.Assert(s.invalidCredential, jc.IsFalse) 115 mockAdapter := &mockAdapter{ 116 attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) { 117 return &nova.VolumeAttachment{}, testUnauthorisedGooseError 118 }, 119 } 120 121 volSource := openstack.NewCinderVolumeSource(mockAdapter) 122 _, err := volSource.AttachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{ 123 Volume: mockVolumeTag, 124 VolumeId: mockVolId, 125 AttachmentParams: storage.AttachmentParams{ 126 Provider: openstack.CinderProviderType, 127 Machine: mockMachineTag, 128 InstanceId: instance.Id(mockServerId), 129 }}, 130 }) 131 c.Assert(err, jc.ErrorIsNil) 132 c.Assert(s.invalidCredential, jc.IsTrue) 133 } 134 135 func (s *cinderVolumeSourceSuite) TestAttachVolumesNoDevice(c *gc.C) { 136 mockAdapter := &mockAdapter{ 137 attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) { 138 return &nova.VolumeAttachment{ 139 Id: volId, 140 VolumeId: volId, 141 ServerId: serverId, 142 Device: nil, 143 }, nil 144 }, 145 } 146 147 volSource := openstack.NewCinderVolumeSource(mockAdapter) 148 results, err := volSource.AttachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{ 149 Volume: mockVolumeTag, 150 VolumeId: mockVolId, 151 AttachmentParams: storage.AttachmentParams{ 152 Provider: openstack.CinderProviderType, 153 Machine: mockMachineTag, 154 InstanceId: instance.Id(mockServerId), 155 }}, 156 }) 157 c.Assert(err, jc.ErrorIsNil) 158 c.Assert(results, gc.HasLen, 1) 159 c.Assert(results[0].Error, gc.ErrorMatches, "device not assigned to volume attachment") 160 } 161 162 func (s *cinderVolumeSourceSuite) TestCreateVolume(c *gc.C) { 163 const ( 164 requestedSize = 2 * 1024 165 providedSize = 3 * 1024 166 ) 167 168 s.PatchValue(openstack.CinderAttempt, utils.AttemptStrategy{Min: 3}) 169 170 var getVolumeCalls int 171 mockAdapter := &mockAdapter{ 172 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 173 c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{ 174 Size: requestedSize / 1024, 175 Name: "juju-testmodel-volume-123", 176 }) 177 return &cinder.Volume{ 178 ID: mockVolId, 179 }, nil 180 }, 181 getVolume: func(volumeId string) (*cinder.Volume, error) { 182 var status string 183 getVolumeCalls++ 184 if getVolumeCalls > 1 { 185 status = "available" 186 } 187 return &cinder.Volume{ 188 ID: volumeId, 189 Size: providedSize / 1024, 190 Status: status, 191 }, nil 192 }, 193 attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) { 194 c.Check(volId, gc.Equals, mockVolId) 195 c.Check(serverId, gc.Equals, mockServerId) 196 return &nova.VolumeAttachment{ 197 Id: volId, 198 VolumeId: volId, 199 ServerId: serverId, 200 Device: toStringPtr("/dev/sda"), 201 }, nil 202 }, 203 } 204 205 volSource := openstack.NewCinderVolumeSource(mockAdapter) 206 results, err := volSource.CreateVolumes(s.callCtx, []storage.VolumeParams{{ 207 Provider: openstack.CinderProviderType, 208 Tag: mockVolumeTag, 209 Size: requestedSize, 210 Attachment: &storage.VolumeAttachmentParams{ 211 AttachmentParams: storage.AttachmentParams{ 212 Provider: openstack.CinderProviderType, 213 Machine: mockMachineTag, 214 InstanceId: instance.Id(mockServerId), 215 }, 216 }, 217 }}) 218 c.Assert(err, jc.ErrorIsNil) 219 c.Assert(results, gc.HasLen, 1) 220 c.Assert(results[0].Error, jc.ErrorIsNil) 221 222 c.Check(results[0].Volume, jc.DeepEquals, &storage.Volume{ 223 mockVolumeTag, 224 storage.VolumeInfo{ 225 VolumeId: mockVolId, 226 Size: providedSize, 227 Persistent: true, 228 }, 229 }) 230 231 // should have been 2 calls to GetVolume: twice initially 232 // to wait until the volume became available. 233 c.Check(getVolumeCalls, gc.Equals, 2) 234 } 235 236 func (s *cinderVolumeSourceSuite) TestCreateVolumeVolumeType(c *gc.C) { 237 var created bool 238 mockAdapter := &mockAdapter{ 239 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 240 created = true 241 c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{ 242 Size: 1, 243 Name: "juju-testmodel-volume-123", 244 VolumeType: "SSD", 245 }) 246 return &cinder.Volume{ID: mockVolId}, nil 247 }, 248 getVolume: func(volumeId string) (*cinder.Volume, error) { 249 return &cinder.Volume{ 250 ID: volumeId, 251 Size: 1, 252 Status: "available", 253 }, nil 254 }, 255 } 256 257 volSource := openstack.NewCinderVolumeSource(mockAdapter) 258 _, err := volSource.CreateVolumes(s.callCtx, []storage.VolumeParams{{ 259 Provider: openstack.CinderProviderType, 260 Tag: mockVolumeTag, 261 Size: 1024, 262 Attributes: map[string]interface{}{ 263 "volume-type": "SSD", 264 }, 265 }}) 266 c.Assert(err, jc.ErrorIsNil) 267 c.Assert(created, jc.IsTrue) 268 } 269 270 func (s *cinderVolumeSourceSuite) TestCreateVolumeInvalidCredential(c *gc.C) { 271 c.Assert(s.invalidCredential, jc.IsFalse) 272 mockAdapter := &mockAdapter{ 273 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 274 return &cinder.Volume{}, testUnauthorisedGooseError 275 }, 276 getVolume: func(volumeId string) (*cinder.Volume, error) { 277 return &cinder.Volume{}, testUnauthorisedGooseError 278 }, 279 } 280 281 volSource := openstack.NewCinderVolumeSource(mockAdapter) 282 _, err := volSource.CreateVolumes(s.callCtx, []storage.VolumeParams{{ 283 Provider: openstack.CinderProviderType, 284 Tag: mockVolumeTag, 285 Size: 1024, 286 Attributes: map[string]interface{}{ 287 "volume-type": "SSD", 288 }, 289 }}) 290 c.Assert(err, jc.ErrorIsNil) 291 c.Assert(s.invalidCredential, jc.IsTrue) 292 } 293 294 func (s *cinderVolumeSourceSuite) TestResourceTags(c *gc.C) { 295 var created bool 296 mockAdapter := &mockAdapter{ 297 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 298 created = true 299 c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{ 300 Size: 1, 301 Name: "juju-testmodel-volume-123", 302 Metadata: map[string]string{ 303 "ResourceTag1": "Value1", 304 "ResourceTag2": "Value2", 305 }, 306 }) 307 return &cinder.Volume{ID: mockVolId}, nil 308 }, 309 getVolume: func(volumeId string) (*cinder.Volume, error) { 310 return &cinder.Volume{ 311 ID: volumeId, 312 Size: 1, 313 Status: "available", 314 }, nil 315 }, 316 attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) { 317 return &nova.VolumeAttachment{ 318 Id: volId, 319 VolumeId: volId, 320 ServerId: serverId, 321 Device: toStringPtr("/dev/sda"), 322 }, nil 323 }, 324 } 325 326 volSource := openstack.NewCinderVolumeSource(mockAdapter) 327 _, err := volSource.CreateVolumes(s.callCtx, []storage.VolumeParams{{ 328 Provider: openstack.CinderProviderType, 329 Tag: mockVolumeTag, 330 Size: 1024, 331 ResourceTags: map[string]string{ 332 "ResourceTag1": "Value1", 333 "ResourceTag2": "Value2", 334 }, 335 Attachment: &storage.VolumeAttachmentParams{ 336 AttachmentParams: storage.AttachmentParams{ 337 Provider: openstack.CinderProviderType, 338 Machine: mockMachineTag, 339 InstanceId: instance.Id(mockServerId), 340 }, 341 }, 342 }}) 343 c.Assert(err, jc.ErrorIsNil) 344 c.Assert(created, jc.IsTrue) 345 } 346 347 func (s *cinderVolumeSourceSuite) TestListVolumes(c *gc.C) { 348 mockAdapter := &mockAdapter{ 349 getVolumesDetail: func() ([]cinder.Volume, error) { 350 return []cinder.Volume{{ 351 ID: "volume-1", 352 }, { 353 ID: "volume-2", 354 Metadata: map[string]string{ 355 tags.JujuModel: "something-else", 356 }, 357 }, { 358 ID: "volume-3", 359 Metadata: map[string]string{ 360 tags.JujuModel: testing.ModelTag.Id(), 361 }, 362 }}, nil 363 }, 364 } 365 volSource := openstack.NewCinderVolumeSource(mockAdapter) 366 volumeIds, err := volSource.ListVolumes(s.callCtx) 367 c.Assert(err, jc.ErrorIsNil) 368 c.Check(volumeIds, jc.DeepEquals, []string{"volume-3"}) 369 } 370 371 func (s *cinderVolumeSourceSuite) TestListVolumesInvalidCredential(c *gc.C) { 372 c.Assert(s.invalidCredential, jc.IsFalse) 373 mockAdapter := &mockAdapter{ 374 getVolumesDetail: func() ([]cinder.Volume, error) { 375 return []cinder.Volume{}, testUnauthorisedGooseError 376 }, 377 } 378 volSource := openstack.NewCinderVolumeSource(mockAdapter) 379 _, err := volSource.ListVolumes(s.callCtx) 380 c.Assert(err, gc.ErrorMatches, "invalid auth") 381 c.Assert(s.invalidCredential, jc.IsTrue) 382 } 383 384 func (s *cinderVolumeSourceSuite) TestDescribeVolumes(c *gc.C) { 385 mockAdapter := &mockAdapter{ 386 getVolumesDetail: func() ([]cinder.Volume, error) { 387 return []cinder.Volume{{ 388 ID: mockVolId, 389 Size: mockVolSize / 1024, 390 }}, nil 391 }, 392 } 393 volSource := openstack.NewCinderVolumeSource(mockAdapter) 394 volumes, err := volSource.DescribeVolumes(s.callCtx, []string{mockVolId}) 395 c.Assert(err, jc.ErrorIsNil) 396 c.Check(volumes, jc.DeepEquals, []storage.DescribeVolumesResult{{ 397 VolumeInfo: &storage.VolumeInfo{ 398 VolumeId: mockVolId, 399 Size: mockVolSize, 400 Persistent: true, 401 }, 402 }}) 403 } 404 405 func (s *cinderVolumeSourceSuite) TestDescribeVolumesInvalidCredential(c *gc.C) { 406 c.Assert(s.invalidCredential, jc.IsFalse) 407 mockAdapter := &mockAdapter{ 408 getVolumesDetail: func() ([]cinder.Volume, error) { 409 return []cinder.Volume{}, testUnauthorisedGooseError 410 }, 411 } 412 volSource := openstack.NewCinderVolumeSource(mockAdapter) 413 _, err := volSource.DescribeVolumes(s.callCtx, []string{mockVolId}) 414 c.Assert(err, gc.ErrorMatches, "invalid auth") 415 c.Assert(s.invalidCredential, jc.IsTrue) 416 } 417 418 func (s *cinderVolumeSourceSuite) TestDestroyVolumes(c *gc.C) { 419 mockAdapter := &mockAdapter{} 420 volSource := openstack.NewCinderVolumeSource(mockAdapter) 421 errs, err := volSource.DestroyVolumes(s.callCtx, []string{mockVolId}) 422 c.Assert(err, jc.ErrorIsNil) 423 c.Assert(errs, jc.DeepEquals, []error{nil}) 424 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{ 425 {"GetVolume", []interface{}{mockVolId}}, 426 {"DeleteVolume", []interface{}{mockVolId}}, 427 }) 428 } 429 430 func (s *cinderVolumeSourceSuite) TestDestroyVolumesNotFound(c *gc.C) { 431 mockAdapter := &mockAdapter{ 432 getVolume: func(volId string) (*cinder.Volume, error) { 433 return nil, errors.NotFoundf("volume %q", volId) 434 }, 435 } 436 volSource := openstack.NewCinderVolumeSource(mockAdapter) 437 errs, err := volSource.DestroyVolumes(s.callCtx, []string{mockVolId}) 438 c.Assert(err, jc.ErrorIsNil) 439 c.Assert(errs, jc.DeepEquals, []error{nil}) 440 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{ 441 {"GetVolume", []interface{}{mockVolId}}, 442 }) 443 } 444 445 func (s *cinderVolumeSourceSuite) TestDestroyVolumesAttached(c *gc.C) { 446 statuses := []string{"in-use", "detaching", "available"} 447 448 mockAdapter := &mockAdapter{ 449 getVolume: func(volId string) (*cinder.Volume, error) { 450 c.Assert(statuses, gc.Not(gc.HasLen), 0) 451 status := statuses[0] 452 statuses = statuses[1:] 453 return &cinder.Volume{ 454 ID: volId, 455 Status: status, 456 }, nil 457 }, 458 } 459 460 volSource := openstack.NewCinderVolumeSource(mockAdapter) 461 errs, err := volSource.DestroyVolumes(s.callCtx, []string{mockVolId}) 462 c.Assert(err, jc.ErrorIsNil) 463 c.Assert(errs, gc.HasLen, 1) 464 c.Assert(errs[0], jc.ErrorIsNil) 465 c.Assert(statuses, gc.HasLen, 0) 466 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{ 467 "GetVolume", []interface{}{mockVolId}, 468 }, { 469 "GetVolume", []interface{}{mockVolId}, 470 }, { 471 "GetVolume", []interface{}{mockVolId}, 472 }, { 473 "DeleteVolume", []interface{}{mockVolId}, 474 }}) 475 } 476 477 func (s *cinderVolumeSourceSuite) TestDestroyVolumesInvalidCredential(c *gc.C) { 478 c.Assert(s.invalidCredential, jc.IsFalse) 479 mockAdapter := &mockAdapter{ 480 getVolume: func(volId string) (*cinder.Volume, error) { 481 return &cinder.Volume{}, testUnauthorisedGooseError 482 }, 483 } 484 485 volSource := openstack.NewCinderVolumeSource(mockAdapter) 486 errs, err := volSource.DestroyVolumes(s.callCtx, []string{mockVolId}) 487 c.Assert(err, jc.ErrorIsNil) 488 c.Assert(errs, gc.HasLen, 1) 489 c.Assert(errs[0], gc.ErrorMatches, "getting volume: invalid auth") 490 c.Assert(s.invalidCredential, jc.IsTrue) 491 mockAdapter.CheckCallNames(c, "GetVolume") 492 } 493 494 func (s *cinderVolumeSourceSuite) TestReleaseVolumes(c *gc.C) { 495 mockAdapter := &mockAdapter{} 496 volSource := openstack.NewCinderVolumeSource(mockAdapter) 497 errs, err := volSource.ReleaseVolumes(s.callCtx, []string{mockVolId}) 498 c.Assert(err, jc.ErrorIsNil) 499 c.Assert(errs, jc.DeepEquals, []error{nil}) 500 metadata := map[string]string{ 501 "juju-controller-uuid": "", 502 "juju-model-uuid": "", 503 } 504 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{ 505 {"GetVolume", []interface{}{mockVolId}}, 506 {"SetVolumeMetadata", []interface{}{mockVolId, metadata}}, 507 }) 508 } 509 510 func (s *cinderVolumeSourceSuite) TestReleaseVolumesAttached(c *gc.C) { 511 mockAdapter := &mockAdapter{ 512 getVolume: func(volId string) (*cinder.Volume, error) { 513 return &cinder.Volume{ 514 ID: volId, 515 Status: "in-use", 516 }, nil 517 }, 518 } 519 520 volSource := openstack.NewCinderVolumeSource(mockAdapter) 521 errs, err := volSource.ReleaseVolumes(s.callCtx, []string{mockVolId}) 522 c.Assert(err, jc.ErrorIsNil) 523 c.Assert(errs, gc.HasLen, 1) 524 c.Assert(errs[0], gc.ErrorMatches, `cannot release volume "0": volume still in-use`) 525 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{ 526 "GetVolume", []interface{}{mockVolId}, 527 }}) 528 } 529 530 func (s *cinderVolumeSourceSuite) TestReleaseVolumesInvalidCredential(c *gc.C) { 531 c.Assert(s.invalidCredential, jc.IsFalse) 532 mockAdapter := &mockAdapter{ 533 getVolume: func(volId string) (*cinder.Volume, error) { 534 return &cinder.Volume{}, testUnauthorisedGooseError 535 }, 536 } 537 538 volSource := openstack.NewCinderVolumeSource(mockAdapter) 539 _, err := volSource.ReleaseVolumes(s.callCtx, []string{mockVolId}) 540 c.Assert(err, jc.ErrorIsNil) 541 c.Assert(s.invalidCredential, jc.IsTrue) 542 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{ 543 "GetVolume", []interface{}{mockVolId}, 544 }}) 545 } 546 547 func (s *cinderVolumeSourceSuite) TestReleaseVolumesDetaching(c *gc.C) { 548 statuses := []string{"detaching", "available"} 549 550 mockAdapter := &mockAdapter{ 551 getVolume: func(volId string) (*cinder.Volume, error) { 552 c.Assert(statuses, gc.Not(gc.HasLen), 0) 553 status := statuses[0] 554 statuses = statuses[1:] 555 return &cinder.Volume{ 556 ID: volId, 557 Status: status, 558 }, nil 559 }, 560 } 561 562 volSource := openstack.NewCinderVolumeSource(mockAdapter) 563 errs, err := volSource.ReleaseVolumes(s.callCtx, []string{mockVolId}) 564 c.Assert(err, jc.ErrorIsNil) 565 c.Assert(errs, gc.HasLen, 1) 566 c.Assert(errs[0], jc.ErrorIsNil) 567 c.Assert(statuses, gc.HasLen, 0) 568 mockAdapter.CheckCallNames(c, "GetVolume", "GetVolume", "SetVolumeMetadata") 569 } 570 571 func (s *cinderVolumeSourceSuite) TestDetachVolumes(c *gc.C) { 572 const mockServerId2 = mockServerId + "2" 573 574 var numDetachCalls int 575 mockAdapter := &mockAdapter{ 576 detachVolume: func(serverId, volId string) error { 577 numDetachCalls++ 578 if volId == "42" { 579 return errors.NotFoundf("attachment") 580 } 581 c.Check(serverId, gc.Equals, mockServerId) 582 c.Check(volId, gc.Equals, mockVolId) 583 return nil 584 }, 585 } 586 587 volSource := openstack.NewCinderVolumeSource(mockAdapter) 588 errs, err := volSource.DetachVolumes(s.callCtx, []storage.VolumeAttachmentParams{{ 589 Volume: names.NewVolumeTag("123"), 590 VolumeId: mockVolId, 591 AttachmentParams: storage.AttachmentParams{ 592 Machine: names.NewMachineTag("0"), 593 InstanceId: mockServerId, 594 }, 595 }, { 596 Volume: names.NewVolumeTag("42"), 597 VolumeId: "42", 598 AttachmentParams: storage.AttachmentParams{ 599 Machine: names.NewMachineTag("0"), 600 InstanceId: mockServerId2, 601 }, 602 }}) 603 c.Assert(err, jc.ErrorIsNil) 604 c.Assert(errs, jc.DeepEquals, []error{nil, nil}) 605 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{ 606 {"DetachVolume", []interface{}{mockServerId, mockVolId}}, 607 {"DetachVolume", []interface{}{mockServerId2, "42"}}, 608 }) 609 } 610 611 func (s *cinderVolumeSourceSuite) TestCreateVolumeCleanupDestroys(c *gc.C) { 612 var numCreateCalls, numDestroyCalls, numGetCalls int 613 mockAdapter := &mockAdapter{ 614 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 615 numCreateCalls++ 616 if numCreateCalls == 3 { 617 return nil, errors.New("no volume for you") 618 } 619 return &cinder.Volume{ 620 ID: fmt.Sprint(numCreateCalls), 621 Status: "", 622 }, nil 623 }, 624 deleteVolume: func(volId string) error { 625 numDestroyCalls++ 626 c.Assert(volId, gc.Equals, "2") 627 return errors.New("destroy fails") 628 }, 629 getVolume: func(volumeId string) (*cinder.Volume, error) { 630 numGetCalls++ 631 if numGetCalls == 2 { 632 return nil, errors.New("no volume details for you") 633 } 634 return &cinder.Volume{ 635 ID: "4", 636 Size: mockVolSize / 1024, 637 Status: "available", 638 }, nil 639 }, 640 } 641 642 volSource := openstack.NewCinderVolumeSource(mockAdapter) 643 volumeParams := []storage.VolumeParams{{ 644 Provider: openstack.CinderProviderType, 645 Tag: names.NewVolumeTag("0"), 646 Size: mockVolSize, 647 Attachment: &storage.VolumeAttachmentParams{ 648 AttachmentParams: storage.AttachmentParams{ 649 Provider: openstack.CinderProviderType, 650 Machine: mockMachineTag, 651 InstanceId: instance.Id(mockServerId), 652 }, 653 }, 654 }, { 655 Provider: openstack.CinderProviderType, 656 Tag: names.NewVolumeTag("1"), 657 Size: mockVolSize, 658 Attachment: &storage.VolumeAttachmentParams{ 659 AttachmentParams: storage.AttachmentParams{ 660 Provider: openstack.CinderProviderType, 661 Machine: mockMachineTag, 662 InstanceId: instance.Id(mockServerId), 663 }, 664 }, 665 }, { 666 Provider: openstack.CinderProviderType, 667 Tag: names.NewVolumeTag("2"), 668 Size: mockVolSize, 669 Attachment: &storage.VolumeAttachmentParams{ 670 AttachmentParams: storage.AttachmentParams{ 671 Provider: openstack.CinderProviderType, 672 Machine: mockMachineTag, 673 InstanceId: instance.Id(mockServerId), 674 }, 675 }, 676 }} 677 results, err := volSource.CreateVolumes(s.callCtx, volumeParams) 678 c.Assert(err, jc.ErrorIsNil) 679 c.Assert(results, gc.HasLen, 3) 680 c.Assert(results[0].Error, jc.ErrorIsNil) 681 c.Assert(results[1].Error, gc.ErrorMatches, "waiting for volume to be provisioned: getting volume: no volume details for you") 682 c.Assert(results[2].Error, gc.ErrorMatches, "no volume for you") 683 c.Assert(numCreateCalls, gc.Equals, 3) 684 c.Assert(numGetCalls, gc.Equals, 2) 685 c.Assert(numDestroyCalls, gc.Equals, 1) 686 } 687 688 func (s *cinderVolumeSourceSuite) TestImportVolume(c *gc.C) { 689 mockAdapter := &mockAdapter{ 690 getVolume: func(volumeId string) (*cinder.Volume, error) { 691 return &cinder.Volume{ 692 ID: volumeId, 693 Size: mockVolSize / 1024, 694 Status: "available", 695 }, nil 696 }, 697 } 698 volSource := openstack.NewCinderVolumeSource(mockAdapter) 699 c.Assert(volSource, gc.Implements, new(storage.VolumeImporter)) 700 701 tags := map[string]string{ 702 "a": "b", 703 "c": "d", 704 } 705 info, err := volSource.(storage.VolumeImporter).ImportVolume(s.callCtx, mockVolId, tags) 706 c.Assert(err, jc.ErrorIsNil) 707 c.Assert(info, jc.DeepEquals, storage.VolumeInfo{ 708 VolumeId: mockVolId, 709 Size: mockVolSize, 710 Persistent: true, 711 }) 712 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{ 713 {"GetVolume", []interface{}{mockVolId}}, 714 {"SetVolumeMetadata", []interface{}{mockVolId, tags}}, 715 }) 716 } 717 718 func (s *cinderVolumeSourceSuite) TestImportVolumeInUse(c *gc.C) { 719 mockAdapter := &mockAdapter{ 720 getVolume: func(volumeId string) (*cinder.Volume, error) { 721 return &cinder.Volume{ 722 ID: volumeId, 723 Status: "in-use", 724 }, nil 725 }, 726 } 727 volSource := openstack.NewCinderVolumeSource(mockAdapter) 728 _, err := volSource.(storage.VolumeImporter).ImportVolume(s.callCtx, mockVolId, nil) 729 c.Assert(err, gc.ErrorMatches, `cannot import volume "0" with status "in-use"`) 730 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{ 731 {"GetVolume", []interface{}{mockVolId}}, 732 }) 733 } 734 735 func (s *cinderVolumeSourceSuite) TestImportVolumeInvalidCredential(c *gc.C) { 736 c.Assert(s.invalidCredential, jc.IsFalse) 737 mockAdapter := &mockAdapter{ 738 getVolume: func(volumeId string) (*cinder.Volume, error) { 739 return &cinder.Volume{}, testUnauthorisedGooseError 740 }, 741 } 742 volSource := openstack.NewCinderVolumeSource(mockAdapter) 743 _, err := volSource.(storage.VolumeImporter).ImportVolume(s.callCtx, mockVolId, nil) 744 c.Assert(err, gc.ErrorMatches, `getting volume: invalid auth`) 745 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{ 746 {"GetVolume", []interface{}{mockVolId}}, 747 }) 748 c.Assert(s.invalidCredential, jc.IsTrue) 749 } 750 751 type mockAdapter struct { 752 gitjujutesting.Stub 753 getVolume func(string) (*cinder.Volume, error) 754 getVolumesDetail func() ([]cinder.Volume, error) 755 deleteVolume func(string) error 756 createVolume func(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) 757 attachVolume func(string, string, string) (*nova.VolumeAttachment, error) 758 volumeStatusNotifier func(string, string, int, time.Duration) <-chan error 759 detachVolume func(string, string) error 760 listVolumeAttachments func(string) ([]nova.VolumeAttachment, error) 761 setVolumeMetadata func(string, map[string]string) (map[string]string, error) 762 } 763 764 func (ma *mockAdapter) GetVolume(volumeId string) (*cinder.Volume, error) { 765 ma.MethodCall(ma, "GetVolume", volumeId) 766 if ma.getVolume != nil { 767 return ma.getVolume(volumeId) 768 } 769 return &cinder.Volume{ 770 ID: volumeId, 771 Status: "available", 772 }, nil 773 } 774 775 func (ma *mockAdapter) GetVolumesDetail() ([]cinder.Volume, error) { 776 ma.MethodCall(ma, "GetVolumesDetail") 777 if ma.getVolumesDetail != nil { 778 return ma.getVolumesDetail() 779 } 780 return nil, nil 781 } 782 783 func (ma *mockAdapter) DeleteVolume(volId string) error { 784 ma.MethodCall(ma, "DeleteVolume", volId) 785 if ma.deleteVolume != nil { 786 return ma.deleteVolume(volId) 787 } 788 return nil 789 } 790 791 func (ma *mockAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 792 ma.MethodCall(ma, "CreateVolume", args) 793 if ma.createVolume != nil { 794 return ma.createVolume(args) 795 } 796 return nil, errors.NotImplementedf("CreateVolume") 797 } 798 799 func (ma *mockAdapter) AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error) { 800 ma.MethodCall(ma, "AttachVolume", serverId, volumeId, mountPoint) 801 if ma.attachVolume != nil { 802 return ma.attachVolume(serverId, volumeId, mountPoint) 803 } 804 return nil, errors.NotImplementedf("AttachVolume") 805 } 806 807 func (ma *mockAdapter) DetachVolume(serverId, attachmentId string) error { 808 ma.MethodCall(ma, "DetachVolume", serverId, attachmentId) 809 if ma.detachVolume != nil { 810 return ma.detachVolume(serverId, attachmentId) 811 } 812 return nil 813 } 814 815 func (ma *mockAdapter) ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error) { 816 ma.MethodCall(ma, "ListVolumeAttachments", serverId) 817 if ma.listVolumeAttachments != nil { 818 return ma.listVolumeAttachments(serverId) 819 } 820 return nil, nil 821 } 822 823 func (ma *mockAdapter) SetVolumeMetadata(volumeId string, metadata map[string]string) (map[string]string, error) { 824 ma.MethodCall(ma, "SetVolumeMetadata", volumeId, metadata) 825 if ma.setVolumeMetadata != nil { 826 return ma.setVolumeMetadata(volumeId, metadata) 827 } 828 return nil, nil 829 } 830 831 type testEndpointResolver struct { 832 authenticated bool 833 regionEndpoints map[string]identity.ServiceURLs 834 } 835 836 func (r *testEndpointResolver) IsAuthenticated() bool { 837 return r.authenticated 838 } 839 840 func (r *testEndpointResolver) Authenticate() error { 841 r.authenticated = true 842 return nil 843 } 844 845 func (r *testEndpointResolver) EndpointsForRegion(region string) identity.ServiceURLs { 846 if !r.authenticated { 847 return identity.ServiceURLs{} 848 } 849 return r.regionEndpoints[region] 850 } 851 852 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointVolume(c *gc.C) { 853 client := &testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{ 854 "west": map[string]string{"volume": "http://cinder.testing/v1"}, 855 }} 856 url, err := openstack.GetVolumeEndpointURL(client, "west") 857 c.Assert(err, jc.ErrorIsNil) 858 c.Assert(url.String(), gc.Equals, "http://cinder.testing/v1") 859 } 860 861 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointVolumeV2(c *gc.C) { 862 client := &testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{ 863 "west": map[string]string{"volumev2": "http://cinder.testing/v2"}, 864 }} 865 url, err := openstack.GetVolumeEndpointURL(client, "west") 866 c.Assert(err, jc.ErrorIsNil) 867 c.Assert(url.String(), gc.Equals, "http://cinder.testing/v2") 868 } 869 870 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointPreferV2(c *gc.C) { 871 client := &testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{ 872 "south": map[string]string{ 873 "volume": "http://cinder.testing/v1", 874 "volumev2": "http://cinder.testing/v2", 875 }, 876 }} 877 url, err := openstack.GetVolumeEndpointURL(client, "south") 878 c.Assert(err, jc.ErrorIsNil) 879 c.Assert(url.String(), gc.Equals, "http://cinder.testing/v2") 880 } 881 882 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointMissing(c *gc.C) { 883 client := &testEndpointResolver{} 884 url, err := openstack.GetVolumeEndpointURL(client, "east") 885 c.Assert(err, gc.ErrorMatches, `endpoint "volume" in region "east" not found`) 886 c.Assert(err, jc.Satisfies, errors.IsNotFound) 887 c.Assert(url, gc.IsNil) 888 } 889 890 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointBadURL(c *gc.C) { 891 client := &testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{ 892 "north": map[string]string{"volumev2": "some %4"}, 893 }} 894 url, err := openstack.GetVolumeEndpointURL(client, "north") 895 c.Assert(err, gc.ErrorMatches, `parse some %4: .*`) 896 c.Assert(url, gc.IsNil) 897 }