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