github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/openstack/cinder_test.go (about) 1 package openstack_test 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/juju/errors" 8 "github.com/juju/names" 9 gitjujutesting "github.com/juju/testing" 10 jc "github.com/juju/testing/checkers" 11 "github.com/juju/utils" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/goose.v1/cinder" 14 "gopkg.in/goose.v1/identity" 15 "gopkg.in/goose.v1/nova" 16 17 "github.com/juju/juju/environs/tags" 18 "github.com/juju/juju/instance" 19 "github.com/juju/juju/provider/openstack" 20 "github.com/juju/juju/storage" 21 "github.com/juju/juju/testing" 22 ) 23 24 const ( 25 mockVolId = "0" 26 mockVolSize = 1024 * 2 27 mockVolName = "123" 28 mockServerId = "mock-server-id" 29 mockVolJson = `{"volume":{"id": "` + mockVolId + `", "size":1,"name":"` + mockVolName + `"}}` 30 ) 31 32 var ( 33 mockVolumeTag = names.NewVolumeTag(mockVolName) 34 mockMachineTag = names.NewMachineTag("456") 35 ) 36 37 var _ = gc.Suite(&cinderVolumeSourceSuite{}) 38 39 type cinderVolumeSourceSuite struct { 40 testing.BaseSuite 41 } 42 43 func init() { 44 // Override attempt strategy to speed things up. 45 openstack.CinderAttempt.Delay = 0 46 } 47 48 func (s *cinderVolumeSourceSuite) TestAttachVolumes(c *gc.C) { 49 mockAdapter := &mockAdapter{ 50 attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) { 51 c.Check(volId, gc.Equals, mockVolId) 52 c.Check(serverId, gc.Equals, mockServerId) 53 return &nova.VolumeAttachment{ 54 Id: volId, 55 VolumeId: volId, 56 ServerId: serverId, 57 Device: "/dev/sda", 58 }, nil 59 }, 60 } 61 62 volSource := openstack.NewCinderVolumeSource(mockAdapter) 63 results, err := volSource.AttachVolumes([]storage.VolumeAttachmentParams{{ 64 Volume: mockVolumeTag, 65 VolumeId: mockVolId, 66 AttachmentParams: storage.AttachmentParams{ 67 Provider: openstack.CinderProviderType, 68 Machine: mockMachineTag, 69 InstanceId: instance.Id(mockServerId), 70 }}, 71 }) 72 c.Assert(err, jc.ErrorIsNil) 73 c.Check(results, jc.DeepEquals, []storage.AttachVolumesResult{{ 74 VolumeAttachment: &storage.VolumeAttachment{ 75 mockVolumeTag, 76 mockMachineTag, 77 storage.VolumeAttachmentInfo{ 78 DeviceName: "sda", 79 }, 80 }, 81 }}) 82 } 83 84 func (s *cinderVolumeSourceSuite) TestCreateVolume(c *gc.C) { 85 const ( 86 requestedSize = 2 * 1024 87 providedSize = 3 * 1024 88 ) 89 90 s.PatchValue(openstack.CinderAttempt, utils.AttemptStrategy{Min: 3}) 91 92 var getVolumeCalls int 93 mockAdapter := &mockAdapter{ 94 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 95 c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{ 96 Size: requestedSize / 1024, 97 Name: "juju-testenv-volume-123", 98 }) 99 return &cinder.Volume{ 100 ID: mockVolId, 101 }, nil 102 }, 103 getVolume: func(volumeId string) (*cinder.Volume, error) { 104 var status string 105 getVolumeCalls++ 106 if getVolumeCalls > 1 { 107 status = "available" 108 } 109 return &cinder.Volume{ 110 ID: volumeId, 111 Size: providedSize / 1024, 112 Status: status, 113 }, nil 114 }, 115 attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) { 116 c.Check(volId, gc.Equals, mockVolId) 117 c.Check(serverId, gc.Equals, mockServerId) 118 return &nova.VolumeAttachment{ 119 Id: volId, 120 VolumeId: volId, 121 ServerId: serverId, 122 Device: "/dev/sda", 123 }, nil 124 }, 125 } 126 127 volSource := openstack.NewCinderVolumeSource(mockAdapter) 128 results, err := volSource.CreateVolumes([]storage.VolumeParams{{ 129 Provider: openstack.CinderProviderType, 130 Tag: mockVolumeTag, 131 Size: requestedSize, 132 Attachment: &storage.VolumeAttachmentParams{ 133 AttachmentParams: storage.AttachmentParams{ 134 Provider: openstack.CinderProviderType, 135 Machine: mockMachineTag, 136 InstanceId: instance.Id(mockServerId), 137 }, 138 }, 139 }}) 140 c.Assert(err, jc.ErrorIsNil) 141 c.Assert(results, gc.HasLen, 1) 142 c.Assert(results[0].Error, jc.ErrorIsNil) 143 144 c.Check(results[0].Volume, jc.DeepEquals, &storage.Volume{ 145 mockVolumeTag, 146 storage.VolumeInfo{ 147 VolumeId: mockVolId, 148 Size: providedSize, 149 Persistent: true, 150 }, 151 }) 152 153 // should have been 2 calls to GetVolume: twice initially 154 // to wait until the volume became available. 155 c.Check(getVolumeCalls, gc.Equals, 2) 156 } 157 158 func (s *cinderVolumeSourceSuite) TestResourceTags(c *gc.C) { 159 var created bool 160 mockAdapter := &mockAdapter{ 161 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 162 created = true 163 c.Assert(args, jc.DeepEquals, cinder.CreateVolumeVolumeParams{ 164 Size: 1, 165 Name: "juju-testenv-volume-123", 166 Metadata: map[string]string{ 167 "ResourceTag1": "Value1", 168 "ResourceTag2": "Value2", 169 }, 170 }) 171 return &cinder.Volume{ID: mockVolId}, nil 172 }, 173 getVolume: func(volumeId string) (*cinder.Volume, error) { 174 return &cinder.Volume{ 175 ID: volumeId, 176 Size: 1, 177 Status: "available", 178 }, nil 179 }, 180 attachVolume: func(serverId, volId, mountPoint string) (*nova.VolumeAttachment, error) { 181 return &nova.VolumeAttachment{ 182 Id: volId, 183 VolumeId: volId, 184 ServerId: serverId, 185 Device: "/dev/sda", 186 }, nil 187 }, 188 } 189 190 volSource := openstack.NewCinderVolumeSource(mockAdapter) 191 _, err := volSource.CreateVolumes([]storage.VolumeParams{{ 192 Provider: openstack.CinderProviderType, 193 Tag: mockVolumeTag, 194 Size: 1024, 195 ResourceTags: map[string]string{ 196 "ResourceTag1": "Value1", 197 "ResourceTag2": "Value2", 198 }, 199 Attachment: &storage.VolumeAttachmentParams{ 200 AttachmentParams: storage.AttachmentParams{ 201 Provider: openstack.CinderProviderType, 202 Machine: mockMachineTag, 203 InstanceId: instance.Id(mockServerId), 204 }, 205 }, 206 }}) 207 c.Assert(err, jc.ErrorIsNil) 208 c.Assert(created, jc.IsTrue) 209 } 210 211 func (s *cinderVolumeSourceSuite) TestListVolumes(c *gc.C) { 212 mockAdapter := &mockAdapter{ 213 getVolumesDetail: func() ([]cinder.Volume, error) { 214 return []cinder.Volume{{ 215 ID: "volume-1", 216 }, { 217 ID: "volume-2", 218 Metadata: map[string]string{ 219 tags.JujuModel: "something-else", 220 }, 221 }, { 222 ID: "volume-3", 223 Metadata: map[string]string{ 224 tags.JujuModel: testing.ModelTag.Id(), 225 }, 226 }}, nil 227 }, 228 } 229 volSource := openstack.NewCinderVolumeSource(mockAdapter) 230 volumeIds, err := volSource.ListVolumes() 231 c.Assert(err, jc.ErrorIsNil) 232 c.Check(volumeIds, jc.DeepEquals, []string{"volume-3"}) 233 } 234 235 func (s *cinderVolumeSourceSuite) TestDescribeVolumes(c *gc.C) { 236 mockAdapter := &mockAdapter{ 237 getVolumesDetail: func() ([]cinder.Volume, error) { 238 return []cinder.Volume{{ 239 ID: mockVolId, 240 Size: mockVolSize / 1024, 241 }}, nil 242 }, 243 } 244 volSource := openstack.NewCinderVolumeSource(mockAdapter) 245 volumes, err := volSource.DescribeVolumes([]string{mockVolId}) 246 c.Assert(err, jc.ErrorIsNil) 247 c.Check(volumes, jc.DeepEquals, []storage.DescribeVolumesResult{{ 248 VolumeInfo: &storage.VolumeInfo{ 249 VolumeId: mockVolId, 250 Size: mockVolSize, 251 Persistent: true, 252 }, 253 }}) 254 } 255 256 func (s *cinderVolumeSourceSuite) TestDestroyVolumes(c *gc.C) { 257 mockAdapter := &mockAdapter{} 258 volSource := openstack.NewCinderVolumeSource(mockAdapter) 259 errs, err := volSource.DestroyVolumes([]string{mockVolId}) 260 c.Assert(err, jc.ErrorIsNil) 261 c.Assert(errs, jc.DeepEquals, []error{nil}) 262 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{ 263 {"GetVolume", []interface{}{mockVolId}}, 264 {"DeleteVolume", []interface{}{mockVolId}}, 265 }) 266 } 267 268 func (s *cinderVolumeSourceSuite) TestDestroyVolumesAttached(c *gc.C) { 269 statuses := []string{"in-use", "detaching", "available"} 270 271 mockAdapter := &mockAdapter{ 272 getVolume: func(volId string) (*cinder.Volume, error) { 273 c.Assert(statuses, gc.Not(gc.HasLen), 0) 274 status := statuses[0] 275 statuses = statuses[1:] 276 return &cinder.Volume{ 277 ID: volId, 278 Status: status, 279 }, nil 280 }, 281 } 282 283 volSource := openstack.NewCinderVolumeSource(mockAdapter) 284 errs, err := volSource.DestroyVolumes([]string{mockVolId}) 285 c.Assert(err, jc.ErrorIsNil) 286 c.Assert(errs, gc.HasLen, 1) 287 c.Assert(errs[0], jc.ErrorIsNil) 288 c.Assert(statuses, gc.HasLen, 0) 289 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{ 290 "GetVolume", []interface{}{mockVolId}, 291 }, { 292 "GetVolume", []interface{}{mockVolId}, 293 }, { 294 "GetVolume", []interface{}{mockVolId}, 295 }, { 296 "DeleteVolume", []interface{}{mockVolId}, 297 }}) 298 } 299 300 func (s *cinderVolumeSourceSuite) TestDetachVolumes(c *gc.C) { 301 const mockServerId2 = mockServerId + "2" 302 303 var numListCalls, numDetachCalls int 304 mockAdapter := &mockAdapter{ 305 listVolumeAttachments: func(serverId string) ([]nova.VolumeAttachment, error) { 306 numListCalls++ 307 if serverId == mockServerId2 { 308 // no attachments 309 return nil, nil 310 } 311 c.Check(serverId, gc.Equals, mockServerId) 312 return []nova.VolumeAttachment{{ 313 Id: mockVolId, 314 VolumeId: mockVolId, 315 ServerId: mockServerId, 316 Device: "/dev/sda", 317 }}, nil 318 }, 319 detachVolume: func(serverId, volId string) error { 320 numDetachCalls++ 321 c.Check(serverId, gc.Equals, mockServerId) 322 c.Check(volId, gc.Equals, mockVolId) 323 return nil 324 }, 325 } 326 327 volSource := openstack.NewCinderVolumeSource(mockAdapter) 328 errs, err := volSource.DetachVolumes([]storage.VolumeAttachmentParams{{ 329 Volume: names.NewVolumeTag("123"), 330 VolumeId: mockVolId, 331 AttachmentParams: storage.AttachmentParams{ 332 Machine: names.NewMachineTag("0"), 333 InstanceId: mockServerId, 334 }, 335 }, { 336 Volume: names.NewVolumeTag("42"), 337 VolumeId: "42", 338 AttachmentParams: storage.AttachmentParams{ 339 Machine: names.NewMachineTag("0"), 340 InstanceId: mockServerId2, 341 }, 342 }}) 343 c.Assert(err, jc.ErrorIsNil) 344 c.Assert(errs, jc.DeepEquals, []error{nil, nil}) 345 // DetachVolume should only be called for existing attachments. 346 mockAdapter.CheckCalls(c, []gitjujutesting.StubCall{{ 347 "ListVolumeAttachments", []interface{}{mockServerId}, 348 }, { 349 "DetachVolume", []interface{}{mockServerId, mockVolId}, 350 }, { 351 "ListVolumeAttachments", []interface{}{mockServerId2}, 352 }}) 353 } 354 355 func (s *cinderVolumeSourceSuite) TestCreateVolumeCleanupDestroys(c *gc.C) { 356 var numCreateCalls, numDestroyCalls, numGetCalls int 357 mockAdapter := &mockAdapter{ 358 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 359 numCreateCalls++ 360 if numCreateCalls == 3 { 361 return nil, errors.New("no volume for you") 362 } 363 return &cinder.Volume{ 364 ID: fmt.Sprint(numCreateCalls), 365 Status: "", 366 }, nil 367 }, 368 deleteVolume: func(volId string) error { 369 numDestroyCalls++ 370 c.Assert(volId, gc.Equals, "2") 371 return errors.New("destroy fails") 372 }, 373 getVolume: func(volumeId string) (*cinder.Volume, error) { 374 numGetCalls++ 375 if numGetCalls == 2 { 376 return nil, errors.New("no volume details for you") 377 } 378 return &cinder.Volume{ 379 ID: "4", 380 Size: mockVolSize / 1024, 381 Status: "available", 382 }, nil 383 }, 384 } 385 386 volSource := openstack.NewCinderVolumeSource(mockAdapter) 387 volumeParams := []storage.VolumeParams{{ 388 Provider: openstack.CinderProviderType, 389 Tag: names.NewVolumeTag("0"), 390 Size: mockVolSize, 391 Attachment: &storage.VolumeAttachmentParams{ 392 AttachmentParams: storage.AttachmentParams{ 393 Provider: openstack.CinderProviderType, 394 Machine: mockMachineTag, 395 InstanceId: instance.Id(mockServerId), 396 }, 397 }, 398 }, { 399 Provider: openstack.CinderProviderType, 400 Tag: names.NewVolumeTag("1"), 401 Size: mockVolSize, 402 Attachment: &storage.VolumeAttachmentParams{ 403 AttachmentParams: storage.AttachmentParams{ 404 Provider: openstack.CinderProviderType, 405 Machine: mockMachineTag, 406 InstanceId: instance.Id(mockServerId), 407 }, 408 }, 409 }, { 410 Provider: openstack.CinderProviderType, 411 Tag: names.NewVolumeTag("2"), 412 Size: mockVolSize, 413 Attachment: &storage.VolumeAttachmentParams{ 414 AttachmentParams: storage.AttachmentParams{ 415 Provider: openstack.CinderProviderType, 416 Machine: mockMachineTag, 417 InstanceId: instance.Id(mockServerId), 418 }, 419 }, 420 }} 421 results, err := volSource.CreateVolumes(volumeParams) 422 c.Assert(err, jc.ErrorIsNil) 423 c.Assert(results, gc.HasLen, 3) 424 c.Assert(results[0].Error, jc.ErrorIsNil) 425 c.Assert(results[1].Error, gc.ErrorMatches, "waiting for volume to be provisioned: getting volume: no volume details for you") 426 c.Assert(results[2].Error, gc.ErrorMatches, "no volume for you") 427 c.Assert(numCreateCalls, gc.Equals, 3) 428 c.Assert(numGetCalls, gc.Equals, 2) 429 c.Assert(numDestroyCalls, gc.Equals, 1) 430 } 431 432 type mockAdapter struct { 433 gitjujutesting.Stub 434 getVolume func(string) (*cinder.Volume, error) 435 getVolumesDetail func() ([]cinder.Volume, error) 436 deleteVolume func(string) error 437 createVolume func(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) 438 attachVolume func(string, string, string) (*nova.VolumeAttachment, error) 439 volumeStatusNotifier func(string, string, int, time.Duration) <-chan error 440 detachVolume func(string, string) error 441 listVolumeAttachments func(string) ([]nova.VolumeAttachment, error) 442 } 443 444 func (ma *mockAdapter) GetVolume(volumeId string) (*cinder.Volume, error) { 445 ma.MethodCall(ma, "GetVolume", volumeId) 446 if ma.getVolume != nil { 447 return ma.getVolume(volumeId) 448 } 449 return &cinder.Volume{ 450 ID: volumeId, 451 Status: "available", 452 }, nil 453 } 454 455 func (ma *mockAdapter) GetVolumesDetail() ([]cinder.Volume, error) { 456 ma.MethodCall(ma, "GetVolumesDetail") 457 if ma.getVolumesDetail != nil { 458 return ma.getVolumesDetail() 459 } 460 return nil, nil 461 } 462 463 func (ma *mockAdapter) DeleteVolume(volId string) error { 464 ma.MethodCall(ma, "DeleteVolume", volId) 465 if ma.deleteVolume != nil { 466 return ma.deleteVolume(volId) 467 } 468 return nil 469 } 470 471 func (ma *mockAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 472 ma.MethodCall(ma, "CreateVolume", args) 473 if ma.createVolume != nil { 474 return ma.createVolume(args) 475 } 476 return nil, errors.NotImplementedf("CreateVolume") 477 } 478 479 func (ma *mockAdapter) AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error) { 480 ma.MethodCall(ma, "AttachVolume", serverId, volumeId, mountPoint) 481 if ma.attachVolume != nil { 482 return ma.attachVolume(serverId, volumeId, mountPoint) 483 } 484 return nil, errors.NotImplementedf("AttachVolume") 485 } 486 487 func (ma *mockAdapter) DetachVolume(serverId, attachmentId string) error { 488 ma.MethodCall(ma, "DetachVolume", serverId, attachmentId) 489 if ma.detachVolume != nil { 490 return ma.detachVolume(serverId, attachmentId) 491 } 492 return nil 493 } 494 495 func (ma *mockAdapter) ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error) { 496 ma.MethodCall(ma, "ListVolumeAttachments", serverId) 497 if ma.listVolumeAttachments != nil { 498 return ma.listVolumeAttachments(serverId) 499 } 500 return nil, nil 501 } 502 503 type testEndpointResolver struct { 504 regionEndpoints map[string]identity.ServiceURLs 505 } 506 507 func (r testEndpointResolver) EndpointsForRegion(region string) identity.ServiceURLs { 508 return r.regionEndpoints[region] 509 } 510 511 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointVolume(c *gc.C) { 512 client := testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{ 513 "west": map[string]string{"volume": "http://cinder.testing/v1"}, 514 }} 515 url, err := openstack.GetVolumeEndpointURL(client, "west") 516 c.Assert(err, jc.ErrorIsNil) 517 c.Assert(url.String(), gc.Equals, "http://cinder.testing/v1") 518 } 519 520 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointVolumeV2(c *gc.C) { 521 client := testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{ 522 "west": map[string]string{"volumev2": "http://cinder.testing/v2"}, 523 }} 524 url, err := openstack.GetVolumeEndpointURL(client, "west") 525 c.Assert(err, jc.ErrorIsNil) 526 c.Assert(url.String(), gc.Equals, "http://cinder.testing/v2") 527 } 528 529 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointPreferV2(c *gc.C) { 530 client := testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{ 531 "south": map[string]string{ 532 "volume": "http://cinder.testing/v1", 533 "volumev2": "http://cinder.testing/v2", 534 }, 535 }} 536 url, err := openstack.GetVolumeEndpointURL(client, "south") 537 c.Assert(err, jc.ErrorIsNil) 538 c.Assert(url.String(), gc.Equals, "http://cinder.testing/v2") 539 } 540 541 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointMissing(c *gc.C) { 542 client := testEndpointResolver{} 543 url, err := openstack.GetVolumeEndpointURL(client, "east") 544 c.Assert(err, gc.ErrorMatches, `endpoint "volume" not found for "east" region`) 545 c.Assert(url, gc.IsNil) 546 } 547 548 func (s *cinderVolumeSourceSuite) TestGetVolumeEndpointBadURL(c *gc.C) { 549 client := testEndpointResolver{regionEndpoints: map[string]identity.ServiceURLs{ 550 "north": map[string]string{"volumev2": "some %4"}, 551 }} 552 url, err := openstack.GetVolumeEndpointURL(client, "north") 553 c.Assert(err, gc.ErrorMatches, `parse some %4: .*`) 554 c.Assert(url, gc.IsNil) 555 }