github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/storageprovisioner/storageprovisioner_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storageprovisioner_test 5 6 import ( 7 "errors" 8 "time" 9 10 "github.com/juju/names" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/environs/config" 16 "github.com/juju/juju/instance" 17 "github.com/juju/juju/storage" 18 "github.com/juju/juju/storage/provider/registry" 19 coretesting "github.com/juju/juju/testing" 20 "github.com/juju/juju/worker" 21 "github.com/juju/juju/worker/storageprovisioner" 22 ) 23 24 type storageProvisionerSuite struct { 25 coretesting.BaseSuite 26 provider *dummyProvider 27 managedFilesystemSource *mockManagedFilesystemSource 28 } 29 30 var _ = gc.Suite(&storageProvisionerSuite{}) 31 32 func (s *storageProvisionerSuite) SetUpTest(c *gc.C) { 33 s.BaseSuite.SetUpTest(c) 34 s.provider = &dummyProvider{dynamic: true} 35 registry.RegisterProvider("dummy", s.provider) 36 s.AddCleanup(func(*gc.C) { 37 registry.RegisterProvider("dummy", nil) 38 }) 39 40 s.managedFilesystemSource = nil 41 s.PatchValue( 42 storageprovisioner.NewManagedFilesystemSource, 43 func( 44 blockDevices map[names.VolumeTag]storage.BlockDevice, 45 filesystems map[names.FilesystemTag]storage.Filesystem, 46 ) storage.FilesystemSource { 47 s.managedFilesystemSource = &mockManagedFilesystemSource{ 48 blockDevices: blockDevices, 49 filesystems: filesystems, 50 } 51 return s.managedFilesystemSource 52 }, 53 ) 54 } 55 56 func (s *storageProvisionerSuite) TestStartStop(c *gc.C) { 57 worker := storageprovisioner.NewStorageProvisioner( 58 coretesting.EnvironmentTag, 59 "dir", 60 newMockVolumeAccessor(), 61 newMockFilesystemAccessor(), 62 &mockLifecycleManager{}, 63 newMockEnvironAccessor(c), 64 newMockMachineAccessor(c), 65 ) 66 worker.Kill() 67 c.Assert(worker.Wait(), gc.IsNil) 68 } 69 70 func (s *storageProvisionerSuite) TestVolumeAdded(c *gc.C) { 71 expectedVolumes := []params.Volume{{ 72 VolumeTag: "volume-1", 73 Info: params.VolumeInfo{ 74 VolumeId: "id-1", 75 HardwareId: "serial-1", 76 Size: 1024, 77 Persistent: true, 78 }, 79 }, { 80 VolumeTag: "volume-2", 81 Info: params.VolumeInfo{ 82 VolumeId: "id-2", 83 HardwareId: "serial-2", 84 Size: 1024, 85 }, 86 }} 87 expectedVolumeAttachments := []params.VolumeAttachment{{ 88 VolumeTag: "volume-1", 89 MachineTag: "machine-1", 90 Info: params.VolumeAttachmentInfo{ 91 DeviceName: "/dev/sda1", 92 ReadOnly: true, 93 }, 94 }, { 95 VolumeTag: "volume-2", 96 MachineTag: "machine-1", 97 Info: params.VolumeAttachmentInfo{ 98 DeviceName: "/dev/sda2", 99 }, 100 }} 101 102 volumeInfoSet := make(chan interface{}) 103 volumeAccessor := newMockVolumeAccessor() 104 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 105 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 106 defer close(volumeInfoSet) 107 c.Assert(volumes, jc.SameContents, expectedVolumes) 108 return nil, nil 109 } 110 111 volumeAttachmentInfoSet := make(chan interface{}) 112 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 113 defer close(volumeAttachmentInfoSet) 114 c.Assert(volumeAttachments, jc.SameContents, expectedVolumeAttachments) 115 return nil, nil 116 } 117 118 args := &workerArgs{volumes: volumeAccessor} 119 worker := newStorageProvisioner(c, args) 120 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 121 defer worker.Kill() 122 123 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 124 MachineTag: "machine-1", AttachmentTag: "volume-1", 125 }, { 126 MachineTag: "machine-1", AttachmentTag: "volume-2", 127 }} 128 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set") 129 130 // The worker should create volumes according to ids "1" and "2". 131 volumeAccessor.volumesWatcher.changes <- []string{"1", "2"} 132 // ... but not until the environment config is available. 133 assertNoEvent(c, volumeInfoSet, "volume info set") 134 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 135 args.environ.watcher.changes <- struct{}{} 136 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 137 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 138 } 139 140 func (s *storageProvisionerSuite) TestFilesystemAdded(c *gc.C) { 141 expectedFilesystems := []params.Filesystem{{ 142 FilesystemTag: "filesystem-1", 143 Info: params.FilesystemInfo{ 144 FilesystemId: "id-1", 145 Size: 1024, 146 }, 147 }, { 148 FilesystemTag: "filesystem-2", 149 Info: params.FilesystemInfo{ 150 FilesystemId: "id-2", 151 Size: 1024, 152 }, 153 }} 154 155 filesystemInfoSet := make(chan interface{}) 156 filesystemAccessor := newMockFilesystemAccessor() 157 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 158 defer close(filesystemInfoSet) 159 c.Assert(filesystems, jc.SameContents, expectedFilesystems) 160 return nil, nil 161 } 162 163 args := &workerArgs{filesystems: filesystemAccessor} 164 worker := newStorageProvisioner(c, args) 165 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 166 defer worker.Kill() 167 168 // The worker should create filesystems according to ids "1" and "2". 169 filesystemAccessor.filesystemsWatcher.changes <- []string{"1", "2"} 170 // ... but not until the environment config is available. 171 assertNoEvent(c, filesystemInfoSet, "filesystem info set") 172 args.environ.watcher.changes <- struct{}{} 173 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 174 } 175 176 func (s *storageProvisionerSuite) TestVolumeNeedsInstance(c *gc.C) { 177 volumeInfoSet := make(chan interface{}) 178 volumeAccessor := newMockVolumeAccessor() 179 volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) { 180 defer close(volumeInfoSet) 181 return nil, nil 182 } 183 volumeAccessor.setVolumeAttachmentInfo = func([]params.VolumeAttachment) ([]params.ErrorResult, error) { 184 return nil, nil 185 } 186 187 args := &workerArgs{volumes: volumeAccessor} 188 worker := newStorageProvisioner(c, args) 189 defer worker.Wait() 190 defer worker.Kill() 191 192 volumeAccessor.volumesWatcher.changes <- []string{needsInstanceVolumeId} 193 args.environ.watcher.changes <- struct{}{} 194 assertNoEvent(c, volumeInfoSet, "volume info set") 195 args.machines.instanceIds[names.NewMachineTag("1")] = "inst-id" 196 args.machines.watcher.changes <- struct{}{} 197 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 198 } 199 200 func (s *storageProvisionerSuite) TestVolumeNonDynamic(c *gc.C) { 201 volumeInfoSet := make(chan interface{}) 202 volumeAccessor := newMockVolumeAccessor() 203 volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) { 204 defer close(volumeInfoSet) 205 return nil, nil 206 } 207 208 args := &workerArgs{volumes: volumeAccessor} 209 worker := newStorageProvisioner(c, args) 210 defer worker.Wait() 211 defer worker.Kill() 212 213 // Volumes for non-dynamic providers should not be created. 214 s.provider.dynamic = false 215 args.environ.watcher.changes <- struct{}{} 216 volumeAccessor.volumesWatcher.changes <- []string{"1"} 217 assertNoEvent(c, volumeInfoSet, "volume info set") 218 } 219 220 func (s *storageProvisionerSuite) TestVolumeAttachmentAdded(c *gc.C) { 221 // We should get two volume attachments: 222 // - volume-1 to machine-1, because the volume and 223 // machine are provisioned, but the attachment is not. 224 // - volume-1 to machine-0, because the volume, 225 // machine, and attachment are provisioned, but 226 // in a previous session, so a reattachment is 227 // requested. 228 expectedVolumeAttachments := []params.VolumeAttachment{{ 229 VolumeTag: "volume-1", 230 MachineTag: "machine-1", 231 Info: params.VolumeAttachmentInfo{ 232 DeviceName: "/dev/sda1", 233 ReadOnly: true, 234 }, 235 }, { 236 VolumeTag: "volume-1", 237 MachineTag: "machine-0", 238 Info: params.VolumeAttachmentInfo{ 239 DeviceName: "/dev/sda1", 240 ReadOnly: true, 241 }, 242 }} 243 244 var allVolumeAttachments []params.VolumeAttachment 245 volumeAttachmentInfoSet := make(chan interface{}) 246 volumeAccessor := newMockVolumeAccessor() 247 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 248 allVolumeAttachments = append(allVolumeAttachments, volumeAttachments...) 249 volumeAttachmentInfoSet <- nil 250 return make([]params.ErrorResult, len(volumeAttachments)), nil 251 } 252 253 // volume-1, machine-0, and machine-1 are provisioned. 254 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 255 VolumeTag: "volume-1", 256 Info: params.VolumeInfo{ 257 VolumeId: "vol-123", 258 }, 259 } 260 volumeAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 261 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 262 263 // machine-0/volume-1 attachment is already created. 264 // We should see a reattachment. 265 alreadyAttached := params.MachineStorageId{ 266 MachineTag: "machine-0", 267 AttachmentTag: "volume-1", 268 } 269 volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{ 270 MachineTag: "machine-0", 271 VolumeTag: "volume-1", 272 } 273 274 args := &workerArgs{volumes: volumeAccessor} 275 worker := newStorageProvisioner(c, args) 276 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 277 defer worker.Kill() 278 279 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 280 MachineTag: "machine-1", AttachmentTag: "volume-1", 281 }, { 282 MachineTag: "machine-1", AttachmentTag: "volume-2", 283 }, { 284 MachineTag: "machine-2", AttachmentTag: "volume-1", 285 }, { 286 MachineTag: "machine-0", AttachmentTag: "volume-1", 287 }} 288 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 289 volumeAccessor.volumesWatcher.changes <- []string{"1"} 290 args.environ.watcher.changes <- struct{}{} 291 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 292 c.Assert(allVolumeAttachments, jc.SameContents, expectedVolumeAttachments) 293 294 // Reattachment should only happen once per session. 295 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{alreadyAttached} 296 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 297 } 298 299 func (s *storageProvisionerSuite) TestFilesystemAttachmentAdded(c *gc.C) { 300 // We should only get a single filesystem attachment, because it is the 301 // only combination where both machine and filesystem are already 302 // provisioned, and the attachmenti s not. 303 // We should get two filesystem attachments: 304 // - filesystem-1 to machine-1, because the filesystem and 305 // machine are provisioned, but the attachment is not. 306 // - filesystem-1 to machine-0, because the filesystem, 307 // machine, and attachment are provisioned, but in a 308 // previous session, so a reattachment is requested. 309 expectedFilesystemAttachments := []params.FilesystemAttachment{{ 310 FilesystemTag: "filesystem-1", 311 MachineTag: "machine-1", 312 Info: params.FilesystemAttachmentInfo{ 313 MountPoint: "/srv/fs-123", 314 }, 315 }, { 316 FilesystemTag: "filesystem-1", 317 MachineTag: "machine-0", 318 Info: params.FilesystemAttachmentInfo{ 319 MountPoint: "/srv/fs-123", 320 }, 321 }} 322 323 var allFilesystemAttachments []params.FilesystemAttachment 324 filesystemAttachmentInfoSet := make(chan interface{}) 325 filesystemAccessor := newMockFilesystemAccessor() 326 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 327 allFilesystemAttachments = append(allFilesystemAttachments, filesystemAttachments...) 328 filesystemAttachmentInfoSet <- nil 329 return make([]params.ErrorResult, len(filesystemAttachments)), nil 330 } 331 332 // filesystem-1 and machine-1 are provisioned. 333 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 334 FilesystemTag: "filesystem-1", 335 Info: params.FilesystemInfo{ 336 FilesystemId: "fs-123", 337 }, 338 } 339 filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 340 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 341 342 // machine-0/filesystem-1 attachment is already created. 343 // We should see a reattachment. 344 alreadyAttached := params.MachineStorageId{ 345 MachineTag: "machine-0", 346 AttachmentTag: "filesystem-1", 347 } 348 filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{ 349 MachineTag: "machine-0", 350 FilesystemTag: "filesystem-1", 351 } 352 353 args := &workerArgs{filesystems: filesystemAccessor} 354 worker := newStorageProvisioner(c, args) 355 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 356 defer worker.Kill() 357 358 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 359 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 360 }, { 361 MachineTag: "machine-1", AttachmentTag: "filesystem-2", 362 }, { 363 MachineTag: "machine-2", AttachmentTag: "filesystem-1", 364 }, { 365 MachineTag: "machine-0", AttachmentTag: "filesystem-1", 366 }} 367 // ... but not until the environment config is available. 368 assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set") 369 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 370 args.environ.watcher.changes <- struct{}{} 371 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 372 c.Assert(allFilesystemAttachments, jc.SameContents, expectedFilesystemAttachments) 373 374 // Reattachment should only happen once per session. 375 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{alreadyAttached} 376 assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set") 377 } 378 379 func (s *storageProvisionerSuite) TestCreateVolumeBackedFilesystem(c *gc.C) { 380 filesystemInfoSet := make(chan interface{}) 381 filesystemAccessor := newMockFilesystemAccessor() 382 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 383 filesystemInfoSet <- filesystems 384 return nil, nil 385 } 386 387 args := &workerArgs{ 388 scope: names.NewMachineTag("0"), 389 filesystems: filesystemAccessor, 390 } 391 worker := newStorageProvisioner(c, args) 392 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 393 defer worker.Kill() 394 395 args.volumes.blockDevices[params.MachineStorageId{ 396 MachineTag: "machine-0", 397 AttachmentTag: "volume-0-0", 398 }] = storage.BlockDevice{ 399 DeviceName: "xvdf1", 400 Size: 123, 401 } 402 filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0", "0/1"} 403 assertNoEvent(c, filesystemInfoSet, "filesystem info set") 404 args.environ.watcher.changes <- struct{}{} 405 406 // Only the block device for volume 0/0 is attached at the moment, 407 // so only the corresponding filesystem will be created. 408 filesystemInfo := waitChannel( 409 c, filesystemInfoSet, 410 "waiting for filesystem info to be set", 411 ).([]params.Filesystem) 412 c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{ 413 FilesystemTag: "filesystem-0-0", 414 Info: params.FilesystemInfo{ 415 FilesystemId: "xvdf1", 416 Size: 123, 417 }, 418 }}) 419 420 // If we now attach the block device for volume 0/1 and trigger the 421 // notification, then the storage provisioner will wake up and create 422 // the filesystem. 423 args.volumes.blockDevices[params.MachineStorageId{ 424 MachineTag: "machine-0", 425 AttachmentTag: "volume-0-1", 426 }] = storage.BlockDevice{ 427 DeviceName: "xvdf2", 428 Size: 246, 429 } 430 args.volumes.blockDevicesWatcher.changes <- struct{}{} 431 filesystemInfo = waitChannel( 432 c, filesystemInfoSet, 433 "waiting for filesystem info to be set", 434 ).([]params.Filesystem) 435 c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{ 436 FilesystemTag: "filesystem-0-1", 437 Info: params.FilesystemInfo{ 438 FilesystemId: "xvdf2", 439 Size: 246, 440 }, 441 }}) 442 } 443 444 func (s *storageProvisionerSuite) TestAttachVolumeBackedFilesystem(c *gc.C) { 445 infoSet := make(chan interface{}) 446 filesystemAccessor := newMockFilesystemAccessor() 447 filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 448 infoSet <- attachments 449 return nil, nil 450 } 451 452 args := &workerArgs{ 453 scope: names.NewMachineTag("0"), 454 filesystems: filesystemAccessor, 455 } 456 worker := newStorageProvisioner(c, args) 457 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 458 defer worker.Kill() 459 460 filesystemAccessor.provisionedFilesystems["filesystem-0-0"] = params.Filesystem{ 461 FilesystemTag: "filesystem-0-0", 462 VolumeTag: "volume-0-0", 463 Info: params.FilesystemInfo{ 464 FilesystemId: "whatever", 465 Size: 123, 466 }, 467 } 468 filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 469 470 args.volumes.blockDevices[params.MachineStorageId{ 471 MachineTag: "machine-0", 472 AttachmentTag: "volume-0-0", 473 }] = storage.BlockDevice{ 474 DeviceName: "xvdf1", 475 Size: 123, 476 } 477 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 478 MachineTag: "machine-0", 479 AttachmentTag: "filesystem-0-0", 480 }} 481 assertNoEvent(c, infoSet, "filesystem attachment info set") 482 args.environ.watcher.changes <- struct{}{} 483 filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0"} 484 485 info := waitChannel( 486 c, infoSet, "waiting for filesystem attachment info to be set", 487 ).([]params.FilesystemAttachment) 488 c.Assert(info, jc.DeepEquals, []params.FilesystemAttachment{{ 489 FilesystemTag: "filesystem-0-0", 490 MachineTag: "machine-0", 491 Info: params.FilesystemAttachmentInfo{ 492 MountPoint: "/mnt/xvdf1", 493 ReadOnly: true, 494 }, 495 }}) 496 } 497 498 func (s *storageProvisionerSuite) TestUpdateEnvironConfig(c *gc.C) { 499 volumeAccessor := newMockVolumeAccessor() 500 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 501 s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) { 502 c.Assert(envConfig, gc.NotNil) 503 c.Assert(sourceConfig, gc.NotNil) 504 c.Assert(envConfig.AllAttrs()["foo"], gc.Equals, "bar") 505 return nil, errors.New("zinga") 506 } 507 508 args := &workerArgs{volumes: volumeAccessor} 509 worker := newStorageProvisioner(c, args) 510 defer worker.Wait() 511 defer worker.Kill() 512 513 newConfig, err := args.environ.cfg.Apply(map[string]interface{}{"foo": "bar"}) 514 c.Assert(err, jc.ErrorIsNil) 515 516 args.environ.watcher.changes <- struct{}{} 517 args.environ.setConfig(newConfig) 518 args.environ.watcher.changes <- struct{}{} 519 args.volumes.volumesWatcher.changes <- []string{"1", "2"} 520 521 err = worker.Wait() 522 c.Assert(err, gc.ErrorMatches, `processing pending volumes: creating volumes: getting volume source: getting storage source "dummy": zinga`) 523 } 524 525 func (s *storageProvisionerSuite) TestResourceTags(c *gc.C) { 526 volumeInfoSet := make(chan interface{}) 527 volumeAccessor := newMockVolumeAccessor() 528 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 529 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 530 defer close(volumeInfoSet) 531 return nil, nil 532 } 533 534 filesystemInfoSet := make(chan interface{}) 535 filesystemAccessor := newMockFilesystemAccessor() 536 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 537 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 538 defer close(filesystemInfoSet) 539 return nil, nil 540 } 541 542 var volumeSource dummyVolumeSource 543 s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) { 544 return &volumeSource, nil 545 } 546 547 var filesystemSource dummyFilesystemSource 548 s.provider.filesystemSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.FilesystemSource, error) { 549 return &filesystemSource, nil 550 } 551 552 args := &workerArgs{ 553 volumes: volumeAccessor, 554 filesystems: filesystemAccessor, 555 } 556 worker := newStorageProvisioner(c, args) 557 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 558 defer worker.Kill() 559 560 volumeAccessor.volumesWatcher.changes <- []string{"1"} 561 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 562 args.environ.watcher.changes <- struct{}{} 563 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 564 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 565 c.Assert(volumeSource.createVolumesArgs, jc.DeepEquals, [][]storage.VolumeParams{{{ 566 Tag: names.NewVolumeTag("1"), 567 Size: 1024, 568 Provider: "dummy", 569 Attributes: map[string]interface{}{"persistent": true}, 570 ResourceTags: map[string]string{"very": "fancy"}, 571 Attachment: &storage.VolumeAttachmentParams{ 572 Volume: names.NewVolumeTag("1"), 573 AttachmentParams: storage.AttachmentParams{ 574 Machine: names.NewMachineTag("1"), 575 Provider: "dummy", 576 InstanceId: "already-provisioned-1", 577 ReadOnly: true, 578 }, 579 }, 580 }}}) 581 c.Assert(filesystemSource.createFilesystemsArgs, jc.DeepEquals, [][]storage.FilesystemParams{{{ 582 Tag: names.NewFilesystemTag("1"), 583 Size: 1024, 584 Provider: "dummy", 585 ResourceTags: map[string]string{"very": "fancy"}, 586 }}}) 587 } 588 589 func (s *storageProvisionerSuite) TestSetVolumeInfoErrorStopsWorker(c *gc.C) { 590 volumeAccessor := newMockVolumeAccessor() 591 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 592 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 593 return []params.ErrorResult{{Error: ¶ms.Error{Message: "message", Code: "code"}}}, nil 594 } 595 596 args := &workerArgs{volumes: volumeAccessor} 597 worker := newStorageProvisioner(c, args) 598 defer worker.Wait() 599 defer worker.Kill() 600 601 done := make(chan interface{}) 602 go func() { 603 defer close(done) 604 err := worker.Wait() 605 c.Assert(err, gc.ErrorMatches, "processing pending volumes: publishing volume 1 to state: message") 606 }() 607 608 args.volumes.volumesWatcher.changes <- []string{"1"} 609 args.environ.watcher.changes <- struct{}{} 610 waitChannel(c, done, "waiting for worker to exit") 611 } 612 613 func (s *storageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) { 614 removed := make(chan interface{}) 615 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 616 defer close(removed) 617 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 618 MachineTag: "machine-0", 619 AttachmentTag: "volume-0", 620 }}) 621 return make([]params.ErrorResult, len(ids)), nil 622 } 623 624 args := &workerArgs{ 625 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 626 } 627 worker := newStorageProvisioner(c, args) 628 defer worker.Wait() 629 defer worker.Kill() 630 631 args.volumes.attachmentsWatcher.changes <- []params.MachineStorageId{{ 632 MachineTag: "machine-0", AttachmentTag: "volume-0", 633 }} 634 args.environ.watcher.changes <- struct{}{} 635 waitChannel(c, removed, "waiting for attachment to be removed") 636 } 637 638 func (s *storageProvisionerSuite) TestDetachVolumes(c *gc.C) { 639 var attached bool 640 volumeAttachmentInfoSet := make(chan interface{}) 641 volumeAccessor := newMockVolumeAccessor() 642 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 643 close(volumeAttachmentInfoSet) 644 attached = true 645 for _, a := range volumeAttachments { 646 id := params.MachineStorageId{ 647 MachineTag: a.MachineTag, 648 AttachmentTag: a.VolumeTag, 649 } 650 volumeAccessor.provisionedAttachments[id] = a 651 } 652 return make([]params.ErrorResult, len(volumeAttachments)), nil 653 } 654 655 expectedAttachmentIds := []params.MachineStorageId{{ 656 MachineTag: "machine-1", AttachmentTag: "volume-1", 657 }} 658 659 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 660 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 661 life := params.Alive 662 if attached { 663 life = params.Dying 664 } 665 return []params.LifeResult{{Life: life}}, nil 666 } 667 668 detached := make(chan interface{}) 669 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) error { 670 c.Assert(args, gc.HasLen, 1) 671 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 672 c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 673 defer close(detached) 674 return nil 675 } 676 677 removed := make(chan interface{}) 678 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 679 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 680 close(removed) 681 return make([]params.ErrorResult, len(ids)), nil 682 } 683 684 // volume-1 and machine-1 are provisioned. 685 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 686 VolumeTag: "volume-1", 687 Info: params.VolumeInfo{ 688 VolumeId: "vol-123", 689 }, 690 } 691 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 692 693 args := &workerArgs{ 694 volumes: volumeAccessor, 695 life: &mockLifecycleManager{ 696 attachmentLife: attachmentLife, 697 removeAttachments: removeAttachments, 698 }, 699 } 700 worker := newStorageProvisioner(c, args) 701 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 702 defer worker.Kill() 703 704 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 705 MachineTag: "machine-1", AttachmentTag: "volume-1", 706 }} 707 volumeAccessor.volumesWatcher.changes <- []string{"1"} 708 args.environ.watcher.changes <- struct{}{} 709 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 710 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 711 MachineTag: "machine-1", AttachmentTag: "volume-1", 712 }} 713 waitChannel(c, detached, "waiting for volume to be detached") 714 waitChannel(c, removed, "waiting for attachment to be removed") 715 } 716 717 func (s *storageProvisionerSuite) TestDetachFilesystemsUnattached(c *gc.C) { 718 removed := make(chan interface{}) 719 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 720 defer close(removed) 721 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 722 MachineTag: "machine-0", 723 AttachmentTag: "filesystem-0", 724 }}) 725 return make([]params.ErrorResult, len(ids)), nil 726 } 727 728 args := &workerArgs{ 729 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 730 } 731 worker := newStorageProvisioner(c, args) 732 defer worker.Wait() 733 defer worker.Kill() 734 735 args.filesystems.attachmentsWatcher.changes <- []params.MachineStorageId{{ 736 MachineTag: "machine-0", AttachmentTag: "filesystem-0", 737 }} 738 args.environ.watcher.changes <- struct{}{} 739 waitChannel(c, removed, "waiting for attachment to be removed") 740 } 741 742 func (s *storageProvisionerSuite) TestDetachFilesystems(c *gc.C) { 743 var attached bool 744 filesystemAttachmentInfoSet := make(chan interface{}) 745 filesystemAccessor := newMockFilesystemAccessor() 746 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 747 close(filesystemAttachmentInfoSet) 748 attached = true 749 for _, a := range filesystemAttachments { 750 id := params.MachineStorageId{ 751 MachineTag: a.MachineTag, 752 AttachmentTag: a.FilesystemTag, 753 } 754 filesystemAccessor.provisionedAttachments[id] = a 755 } 756 return make([]params.ErrorResult, len(filesystemAttachments)), nil 757 } 758 759 expectedAttachmentIds := []params.MachineStorageId{{ 760 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 761 }} 762 763 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 764 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 765 life := params.Alive 766 if attached { 767 life = params.Dying 768 } 769 return []params.LifeResult{{Life: life}}, nil 770 } 771 772 detached := make(chan interface{}) 773 s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) error { 774 c.Assert(args, gc.HasLen, 1) 775 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 776 c.Assert(args[0].Filesystem.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 777 defer close(detached) 778 return nil 779 } 780 781 removed := make(chan interface{}) 782 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 783 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 784 close(removed) 785 return make([]params.ErrorResult, len(ids)), nil 786 } 787 788 // filesystem-1 and machine-1 are provisioned. 789 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 790 FilesystemTag: "filesystem-1", 791 Info: params.FilesystemInfo{ 792 FilesystemId: "fs-id", 793 }, 794 } 795 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 796 797 args := &workerArgs{ 798 filesystems: filesystemAccessor, 799 life: &mockLifecycleManager{ 800 attachmentLife: attachmentLife, 801 removeAttachments: removeAttachments, 802 }, 803 } 804 worker := newStorageProvisioner(c, args) 805 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 806 defer worker.Kill() 807 808 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 809 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 810 }} 811 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 812 args.environ.watcher.changes <- struct{}{} 813 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 814 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 815 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 816 }} 817 waitChannel(c, detached, "waiting for filesystem to be detached") 818 waitChannel(c, removed, "waiting for attachment to be removed") 819 } 820 821 func (s *storageProvisionerSuite) TestDestroyVolumes(c *gc.C) { 822 provisionedVolume := names.NewVolumeTag("1") 823 unprovisionedVolume := names.NewVolumeTag("2") 824 825 volumeAccessor := newMockVolumeAccessor() 826 volumeAccessor.provisionVolume(provisionedVolume) 827 828 life := func(tags []names.Tag) ([]params.LifeResult, error) { 829 results := make([]params.LifeResult, len(tags)) 830 for i := range results { 831 results[i].Life = params.Dead 832 } 833 return results, nil 834 } 835 836 destroyedChan := make(chan interface{}, 1) 837 s.provider.destroyVolumesFunc = func(volumeIds []string) []error { 838 destroyedChan <- volumeIds 839 return make([]error, len(volumeIds)) 840 } 841 842 removedChan := make(chan interface{}, 1) 843 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 844 removedChan <- tags 845 return make([]params.ErrorResult, len(tags)), nil 846 } 847 848 args := &workerArgs{ 849 volumes: volumeAccessor, 850 life: &mockLifecycleManager{ 851 life: life, 852 remove: remove, 853 }, 854 } 855 worker := newStorageProvisioner(c, args) 856 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 857 defer worker.Kill() 858 859 volumeAccessor.volumesWatcher.changes <- []string{ 860 provisionedVolume.Id(), 861 unprovisionedVolume.Id(), 862 } 863 args.environ.watcher.changes <- struct{}{} 864 865 // Both volumes should be removed; the provisioned one 866 // should be deprovisioned first. 867 868 destroyed := waitChannel(c, destroyedChan, "waiting for volume to be deprovisioned") 869 assertNoEvent(c, destroyedChan, "volumes deprovisioned") 870 c.Assert(destroyed, jc.DeepEquals, []string{"vol-1"}) 871 872 var removed []names.Tag 873 for len(removed) < 2 { 874 tags := waitChannel(c, removedChan, "waiting for volumes to be removed").([]names.Tag) 875 removed = append(removed, tags...) 876 } 877 c.Assert(removed, jc.SameContents, []names.Tag{provisionedVolume, unprovisionedVolume}) 878 assertNoEvent(c, removedChan, "volumes removed") 879 } 880 881 func (s *storageProvisionerSuite) TestDestroyFilesystems(c *gc.C) { 882 provisionedFilesystem := names.NewFilesystemTag("1") 883 unprovisionedFilesystem := names.NewFilesystemTag("2") 884 885 filesystemAccessor := newMockFilesystemAccessor() 886 filesystemAccessor.provisionFilesystem(provisionedFilesystem) 887 888 life := func(tags []names.Tag) ([]params.LifeResult, error) { 889 results := make([]params.LifeResult, len(tags)) 890 for i := range results { 891 results[i].Life = params.Dead 892 } 893 return results, nil 894 } 895 896 removedChan := make(chan interface{}, 1) 897 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 898 removedChan <- tags 899 return make([]params.ErrorResult, len(tags)), nil 900 } 901 902 args := &workerArgs{ 903 filesystems: filesystemAccessor, 904 life: &mockLifecycleManager{ 905 life: life, 906 remove: remove, 907 }, 908 } 909 worker := newStorageProvisioner(c, args) 910 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 911 defer worker.Kill() 912 913 filesystemAccessor.filesystemsWatcher.changes <- []string{ 914 provisionedFilesystem.Id(), 915 unprovisionedFilesystem.Id(), 916 } 917 args.environ.watcher.changes <- struct{}{} 918 919 // Both filesystems should be removed; the provisioned one 920 // *should* be deprovisioned first, but we don't currently 921 // have the ability to do so via the storage provider API. 922 923 var removed []names.Tag 924 for len(removed) < 2 { 925 tags := waitChannel(c, removedChan, "waiting for filesystems to be removed").([]names.Tag) 926 removed = append(removed, tags...) 927 } 928 c.Assert(removed, jc.SameContents, []names.Tag{provisionedFilesystem, unprovisionedFilesystem}) 929 assertNoEvent(c, removedChan, "filesystems removed") 930 } 931 932 func newStorageProvisioner(c *gc.C, args *workerArgs) worker.Worker { 933 if args == nil { 934 args = &workerArgs{} 935 } 936 if args.scope == nil { 937 args.scope = coretesting.EnvironmentTag 938 } 939 if args.volumes == nil { 940 args.volumes = newMockVolumeAccessor() 941 } 942 if args.filesystems == nil { 943 args.filesystems = newMockFilesystemAccessor() 944 } 945 if args.life == nil { 946 args.life = &mockLifecycleManager{} 947 } 948 if args.environ == nil { 949 args.environ = newMockEnvironAccessor(c) 950 } 951 if args.machines == nil { 952 args.machines = newMockMachineAccessor(c) 953 } 954 return storageprovisioner.NewStorageProvisioner( 955 args.scope, 956 "storage-dir", 957 args.volumes, 958 args.filesystems, 959 args.life, 960 args.environ, 961 args.machines, 962 ) 963 } 964 965 type workerArgs struct { 966 scope names.Tag 967 volumes *mockVolumeAccessor 968 filesystems *mockFilesystemAccessor 969 life *mockLifecycleManager 970 environ *mockEnvironAccessor 971 machines *mockMachineAccessor 972 } 973 974 func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} { 975 select { 976 case v := <-ch: 977 return v 978 case <-time.After(coretesting.LongWait): 979 c.Fatalf("timed out " + activity) 980 panic("unreachable") 981 } 982 } 983 984 func assertNoEvent(c *gc.C, ch <-chan interface{}, event string) { 985 select { 986 case <-ch: 987 c.Fatalf("unexpected " + event) 988 case <-time.After(coretesting.ShortWait): 989 } 990 } 991 992 // TODO(wallyworld) - test destroying volumes when done