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