github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 "github.com/juju/utils/clock" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/environs/config" 17 "github.com/juju/juju/instance" 18 "github.com/juju/juju/storage" 19 "github.com/juju/juju/storage/provider/registry" 20 coretesting "github.com/juju/juju/testing" 21 "github.com/juju/juju/worker" 22 "github.com/juju/juju/worker/storageprovisioner" 23 ) 24 25 type storageProvisionerSuite struct { 26 coretesting.BaseSuite 27 provider *dummyProvider 28 managedFilesystemSource *mockManagedFilesystemSource 29 } 30 31 var _ = gc.Suite(&storageProvisionerSuite{}) 32 33 func (s *storageProvisionerSuite) SetUpTest(c *gc.C) { 34 s.BaseSuite.SetUpTest(c) 35 s.provider = &dummyProvider{dynamic: true} 36 registry.RegisterProvider("dummy", s.provider) 37 s.AddCleanup(func(*gc.C) { 38 registry.RegisterProvider("dummy", nil) 39 }) 40 41 s.managedFilesystemSource = nil 42 s.PatchValue( 43 storageprovisioner.NewManagedFilesystemSource, 44 func( 45 blockDevices map[names.VolumeTag]storage.BlockDevice, 46 filesystems map[names.FilesystemTag]storage.Filesystem, 47 ) storage.FilesystemSource { 48 s.managedFilesystemSource = &mockManagedFilesystemSource{ 49 blockDevices: blockDevices, 50 filesystems: filesystems, 51 } 52 return s.managedFilesystemSource 53 }, 54 ) 55 } 56 57 func (s *storageProvisionerSuite) TestStartStop(c *gc.C) { 58 worker := storageprovisioner.NewStorageProvisioner( 59 coretesting.EnvironmentTag, 60 "dir", 61 newMockVolumeAccessor(), 62 newMockFilesystemAccessor(), 63 &mockLifecycleManager{}, 64 newMockEnvironAccessor(c), 65 newMockMachineAccessor(c), 66 &mockStatusSetter{}, 67 &mockClock{}, 68 ) 69 worker.Kill() 70 c.Assert(worker.Wait(), gc.IsNil) 71 } 72 73 func (s *storageProvisionerSuite) TestVolumeAdded(c *gc.C) { 74 expectedVolumes := []params.Volume{{ 75 VolumeTag: "volume-1", 76 Info: params.VolumeInfo{ 77 VolumeId: "id-1", 78 HardwareId: "serial-1", 79 Size: 1024, 80 Persistent: true, 81 }, 82 }, { 83 VolumeTag: "volume-2", 84 Info: params.VolumeInfo{ 85 VolumeId: "id-2", 86 HardwareId: "serial-2", 87 Size: 1024, 88 }, 89 }} 90 expectedVolumeAttachments := []params.VolumeAttachment{{ 91 VolumeTag: "volume-1", 92 MachineTag: "machine-1", 93 Info: params.VolumeAttachmentInfo{ 94 DeviceName: "/dev/sda1", 95 ReadOnly: true, 96 }, 97 }, { 98 VolumeTag: "volume-2", 99 MachineTag: "machine-1", 100 Info: params.VolumeAttachmentInfo{ 101 DeviceName: "/dev/sda2", 102 }, 103 }} 104 105 volumeInfoSet := make(chan interface{}) 106 volumeAccessor := newMockVolumeAccessor() 107 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 108 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 109 defer close(volumeInfoSet) 110 c.Assert(volumes, jc.SameContents, expectedVolumes) 111 return nil, nil 112 } 113 114 volumeAttachmentInfoSet := make(chan interface{}) 115 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 116 defer close(volumeAttachmentInfoSet) 117 c.Assert(volumeAttachments, jc.SameContents, expectedVolumeAttachments) 118 return nil, nil 119 } 120 121 args := &workerArgs{volumes: volumeAccessor} 122 worker := newStorageProvisioner(c, args) 123 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 124 defer worker.Kill() 125 126 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 127 MachineTag: "machine-1", AttachmentTag: "volume-1", 128 }, { 129 MachineTag: "machine-1", AttachmentTag: "volume-2", 130 }} 131 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set") 132 133 // The worker should create volumes according to ids "1" and "2". 134 volumeAccessor.volumesWatcher.changes <- []string{"1", "2"} 135 // ... but not until the environment config is available. 136 assertNoEvent(c, volumeInfoSet, "volume info set") 137 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 138 args.environ.watcher.changes <- struct{}{} 139 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 140 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 141 } 142 143 func (s *storageProvisionerSuite) TestCreateVolumeCreatesAttachment(c *gc.C) { 144 volumeAccessor := newMockVolumeAccessor() 145 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 146 147 volumeAttachmentInfoSet := make(chan interface{}) 148 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 149 defer close(volumeAttachmentInfoSet) 150 return make([]params.ErrorResult, len(volumeAttachments)), nil 151 } 152 153 s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 154 volumeAccessor.provisionedAttachments[params.MachineStorageId{ 155 MachineTag: args[0].Attachment.Machine.String(), 156 AttachmentTag: args[0].Attachment.Volume.String(), 157 }] = params.VolumeAttachment{ 158 VolumeTag: args[0].Attachment.Volume.String(), 159 MachineTag: args[0].Attachment.Machine.String(), 160 } 161 return []storage.CreateVolumesResult{{ 162 Volume: &storage.Volume{ 163 Tag: args[0].Tag, 164 VolumeInfo: storage.VolumeInfo{ 165 VolumeId: "vol-ume", 166 }, 167 }, 168 VolumeAttachment: &storage.VolumeAttachment{ 169 Volume: args[0].Attachment.Volume, 170 Machine: args[0].Attachment.Machine, 171 }, 172 }}, nil 173 } 174 175 attachVolumesCalled := make(chan interface{}) 176 s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 177 defer close(attachVolumesCalled) 178 return nil, errors.New("should not be called") 179 } 180 181 args := &workerArgs{volumes: volumeAccessor} 182 worker := newStorageProvisioner(c, args) 183 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 184 defer worker.Kill() 185 186 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 187 MachineTag: "machine-1", AttachmentTag: "volume-1", 188 }} 189 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set") 190 191 // The worker should create volumes according to ids "1". 192 volumeAccessor.volumesWatcher.changes <- []string{"1"} 193 args.environ.watcher.changes <- struct{}{} 194 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 195 assertNoEvent(c, attachVolumesCalled, "AttachVolumes called") 196 } 197 198 func (s *storageProvisionerSuite) TestCreateVolumeRetry(c *gc.C) { 199 volumeInfoSet := make(chan interface{}) 200 volumeAccessor := newMockVolumeAccessor() 201 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 202 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 203 defer close(volumeInfoSet) 204 return make([]params.ErrorResult, len(volumes)), nil 205 } 206 207 // mockFunc's After will progress the current time by the specified 208 // duration and signal the channel immediately. 209 clock := &mockClock{} 210 var createVolumeTimes []time.Time 211 212 s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 213 createVolumeTimes = append(createVolumeTimes, clock.Now()) 214 if len(createVolumeTimes) < 10 { 215 return []storage.CreateVolumesResult{{Error: errors.New("badness")}}, nil 216 } 217 return []storage.CreateVolumesResult{{ 218 Volume: &storage.Volume{Tag: args[0].Tag}, 219 }}, nil 220 } 221 222 args := &workerArgs{volumes: volumeAccessor, clock: clock} 223 worker := newStorageProvisioner(c, args) 224 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 225 defer worker.Kill() 226 227 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 228 MachineTag: "machine-1", AttachmentTag: "volume-1", 229 }} 230 volumeAccessor.volumesWatcher.changes <- []string{"1"} 231 args.environ.watcher.changes <- struct{}{} 232 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 233 c.Assert(createVolumeTimes, gc.HasLen, 10) 234 235 // The first attempt should have been immediate: T0. 236 c.Assert(createVolumeTimes[0], gc.Equals, time.Time{}) 237 238 delays := make([]time.Duration, len(createVolumeTimes)-1) 239 for i := range createVolumeTimes[1:] { 240 delays[i] = createVolumeTimes[i+1].Sub(createVolumeTimes[i]) 241 } 242 c.Assert(delays, jc.DeepEquals, []time.Duration{ 243 30 * time.Second, 244 1 * time.Minute, 245 2 * time.Minute, 246 4 * time.Minute, 247 8 * time.Minute, 248 16 * time.Minute, 249 30 * time.Minute, // ceiling reached 250 30 * time.Minute, 251 30 * time.Minute, 252 }) 253 254 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 255 {Tag: "volume-1", Status: "pending", Info: "badness"}, 256 {Tag: "volume-1", Status: "pending", Info: "badness"}, 257 {Tag: "volume-1", Status: "pending", Info: "badness"}, 258 {Tag: "volume-1", Status: "pending", Info: "badness"}, 259 {Tag: "volume-1", Status: "pending", Info: "badness"}, 260 {Tag: "volume-1", Status: "pending", Info: "badness"}, 261 {Tag: "volume-1", Status: "pending", Info: "badness"}, 262 {Tag: "volume-1", Status: "pending", Info: "badness"}, 263 {Tag: "volume-1", Status: "pending", Info: "badness"}, 264 {Tag: "volume-1", Status: "attaching", Info: ""}, 265 }) 266 } 267 268 func (s *storageProvisionerSuite) TestAttachVolumeRetry(c *gc.C) { 269 volumeInfoSet := make(chan interface{}) 270 volumeAccessor := newMockVolumeAccessor() 271 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 272 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 273 defer close(volumeInfoSet) 274 return make([]params.ErrorResult, len(volumes)), nil 275 } 276 volumeAttachmentInfoSet := make(chan interface{}) 277 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 278 defer close(volumeAttachmentInfoSet) 279 return make([]params.ErrorResult, len(volumeAttachments)), nil 280 } 281 282 // mockFunc's After will progress the current time by the specified 283 // duration and signal the channel immediately. 284 clock := &mockClock{} 285 var attachVolumeTimes []time.Time 286 287 s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 288 attachVolumeTimes = append(attachVolumeTimes, clock.Now()) 289 if len(attachVolumeTimes) < 10 { 290 return []storage.AttachVolumesResult{{Error: errors.New("badness")}}, nil 291 } 292 return []storage.AttachVolumesResult{{ 293 VolumeAttachment: &storage.VolumeAttachment{ 294 args[0].Volume, 295 args[0].Machine, 296 storage.VolumeAttachmentInfo{ 297 DeviceName: "/dev/sda1", 298 }, 299 }, 300 }}, nil 301 } 302 303 args := &workerArgs{volumes: volumeAccessor, clock: clock} 304 worker := newStorageProvisioner(c, args) 305 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 306 defer worker.Kill() 307 308 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 309 MachineTag: "machine-1", AttachmentTag: "volume-1", 310 }} 311 volumeAccessor.volumesWatcher.changes <- []string{"1"} 312 args.environ.watcher.changes <- struct{}{} 313 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 314 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 315 c.Assert(attachVolumeTimes, gc.HasLen, 10) 316 317 // The first attempt should have been immediate: T0. 318 c.Assert(attachVolumeTimes[0], gc.Equals, time.Time{}) 319 320 delays := make([]time.Duration, len(attachVolumeTimes)-1) 321 for i := range attachVolumeTimes[1:] { 322 delays[i] = attachVolumeTimes[i+1].Sub(attachVolumeTimes[i]) 323 } 324 c.Assert(delays, jc.DeepEquals, []time.Duration{ 325 30 * time.Second, 326 1 * time.Minute, 327 2 * time.Minute, 328 4 * time.Minute, 329 8 * time.Minute, 330 16 * time.Minute, 331 30 * time.Minute, // ceiling reached 332 30 * time.Minute, 333 30 * time.Minute, 334 }) 335 336 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 337 {Tag: "volume-1", Status: "attaching", Info: ""}, // CreateVolumes 338 {Tag: "volume-1", Status: "attaching", Info: "badness"}, // AttachVolumes 339 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 340 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 341 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 342 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 343 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 344 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 345 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 346 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 347 {Tag: "volume-1", Status: "attached", Info: ""}, 348 }) 349 } 350 351 func (s *storageProvisionerSuite) TestFilesystemAdded(c *gc.C) { 352 expectedFilesystems := []params.Filesystem{{ 353 FilesystemTag: "filesystem-1", 354 Info: params.FilesystemInfo{ 355 FilesystemId: "id-1", 356 Size: 1024, 357 }, 358 }, { 359 FilesystemTag: "filesystem-2", 360 Info: params.FilesystemInfo{ 361 FilesystemId: "id-2", 362 Size: 1024, 363 }, 364 }} 365 366 filesystemInfoSet := make(chan interface{}) 367 filesystemAccessor := newMockFilesystemAccessor() 368 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 369 defer close(filesystemInfoSet) 370 c.Assert(filesystems, jc.SameContents, expectedFilesystems) 371 return nil, nil 372 } 373 374 args := &workerArgs{filesystems: filesystemAccessor} 375 worker := newStorageProvisioner(c, args) 376 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 377 defer worker.Kill() 378 379 // The worker should create filesystems according to ids "1" and "2". 380 filesystemAccessor.filesystemsWatcher.changes <- []string{"1", "2"} 381 // ... but not until the environment config is available. 382 assertNoEvent(c, filesystemInfoSet, "filesystem info set") 383 args.environ.watcher.changes <- struct{}{} 384 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 385 } 386 387 func (s *storageProvisionerSuite) TestVolumeNeedsInstance(c *gc.C) { 388 volumeInfoSet := make(chan interface{}) 389 volumeAccessor := newMockVolumeAccessor() 390 volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) { 391 defer close(volumeInfoSet) 392 return nil, nil 393 } 394 volumeAccessor.setVolumeAttachmentInfo = func([]params.VolumeAttachment) ([]params.ErrorResult, error) { 395 return nil, nil 396 } 397 398 args := &workerArgs{volumes: volumeAccessor} 399 worker := newStorageProvisioner(c, args) 400 defer worker.Wait() 401 defer worker.Kill() 402 403 volumeAccessor.volumesWatcher.changes <- []string{needsInstanceVolumeId} 404 args.environ.watcher.changes <- struct{}{} 405 assertNoEvent(c, volumeInfoSet, "volume info set") 406 args.machines.instanceIds[names.NewMachineTag("1")] = "inst-id" 407 args.machines.watcher.changes <- struct{}{} 408 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 409 } 410 411 func (s *storageProvisionerSuite) TestVolumeNonDynamic(c *gc.C) { 412 volumeInfoSet := make(chan interface{}) 413 volumeAccessor := newMockVolumeAccessor() 414 volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) { 415 defer close(volumeInfoSet) 416 return nil, nil 417 } 418 419 args := &workerArgs{volumes: volumeAccessor} 420 worker := newStorageProvisioner(c, args) 421 defer worker.Wait() 422 defer worker.Kill() 423 424 // Volumes for non-dynamic providers should not be created. 425 s.provider.dynamic = false 426 args.environ.watcher.changes <- struct{}{} 427 volumeAccessor.volumesWatcher.changes <- []string{"1"} 428 assertNoEvent(c, volumeInfoSet, "volume info set") 429 } 430 431 func (s *storageProvisionerSuite) TestVolumeAttachmentAdded(c *gc.C) { 432 // We should get two volume attachments: 433 // - volume-1 to machine-1, because the volume and 434 // machine are provisioned, but the attachment is not. 435 // - volume-1 to machine-0, because the volume, 436 // machine, and attachment are provisioned, but 437 // in a previous session, so a reattachment is 438 // requested. 439 expectedVolumeAttachments := []params.VolumeAttachment{{ 440 VolumeTag: "volume-1", 441 MachineTag: "machine-1", 442 Info: params.VolumeAttachmentInfo{ 443 DeviceName: "/dev/sda1", 444 ReadOnly: true, 445 }, 446 }, { 447 VolumeTag: "volume-1", 448 MachineTag: "machine-0", 449 Info: params.VolumeAttachmentInfo{ 450 DeviceName: "/dev/sda1", 451 ReadOnly: true, 452 }, 453 }} 454 455 var allVolumeAttachments []params.VolumeAttachment 456 volumeAttachmentInfoSet := make(chan interface{}) 457 volumeAccessor := newMockVolumeAccessor() 458 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 459 allVolumeAttachments = append(allVolumeAttachments, volumeAttachments...) 460 volumeAttachmentInfoSet <- nil 461 return make([]params.ErrorResult, len(volumeAttachments)), nil 462 } 463 464 // volume-1, machine-0, and machine-1 are provisioned. 465 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 466 VolumeTag: "volume-1", 467 Info: params.VolumeInfo{ 468 VolumeId: "vol-123", 469 }, 470 } 471 volumeAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 472 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 473 474 // machine-0/volume-1 attachment is already created. 475 // We should see a reattachment. 476 alreadyAttached := params.MachineStorageId{ 477 MachineTag: "machine-0", 478 AttachmentTag: "volume-1", 479 } 480 volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{ 481 MachineTag: "machine-0", 482 VolumeTag: "volume-1", 483 } 484 485 args := &workerArgs{volumes: volumeAccessor} 486 worker := newStorageProvisioner(c, args) 487 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 488 defer worker.Kill() 489 490 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 491 MachineTag: "machine-1", AttachmentTag: "volume-1", 492 }, { 493 MachineTag: "machine-1", AttachmentTag: "volume-2", 494 }, { 495 MachineTag: "machine-2", AttachmentTag: "volume-1", 496 }, { 497 MachineTag: "machine-0", AttachmentTag: "volume-1", 498 }} 499 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 500 volumeAccessor.volumesWatcher.changes <- []string{"1"} 501 args.environ.watcher.changes <- struct{}{} 502 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 503 c.Assert(allVolumeAttachments, jc.SameContents, expectedVolumeAttachments) 504 505 // Reattachment should only happen once per session. 506 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{alreadyAttached} 507 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 508 } 509 510 func (s *storageProvisionerSuite) TestFilesystemAttachmentAdded(c *gc.C) { 511 // We should only get a single filesystem attachment, because it is the 512 // only combination where both machine and filesystem are already 513 // provisioned, and the attachmenti s not. 514 // We should get two filesystem attachments: 515 // - filesystem-1 to machine-1, because the filesystem and 516 // machine are provisioned, but the attachment is not. 517 // - filesystem-1 to machine-0, because the filesystem, 518 // machine, and attachment are provisioned, but in a 519 // previous session, so a reattachment is requested. 520 expectedFilesystemAttachments := []params.FilesystemAttachment{{ 521 FilesystemTag: "filesystem-1", 522 MachineTag: "machine-1", 523 Info: params.FilesystemAttachmentInfo{ 524 MountPoint: "/srv/fs-123", 525 }, 526 }, { 527 FilesystemTag: "filesystem-1", 528 MachineTag: "machine-0", 529 Info: params.FilesystemAttachmentInfo{ 530 MountPoint: "/srv/fs-123", 531 }, 532 }} 533 534 var allFilesystemAttachments []params.FilesystemAttachment 535 filesystemAttachmentInfoSet := make(chan interface{}) 536 filesystemAccessor := newMockFilesystemAccessor() 537 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 538 allFilesystemAttachments = append(allFilesystemAttachments, filesystemAttachments...) 539 filesystemAttachmentInfoSet <- nil 540 return make([]params.ErrorResult, len(filesystemAttachments)), nil 541 } 542 543 // filesystem-1 and machine-1 are provisioned. 544 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 545 FilesystemTag: "filesystem-1", 546 Info: params.FilesystemInfo{ 547 FilesystemId: "fs-123", 548 }, 549 } 550 filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 551 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 552 553 // machine-0/filesystem-1 attachment is already created. 554 // We should see a reattachment. 555 alreadyAttached := params.MachineStorageId{ 556 MachineTag: "machine-0", 557 AttachmentTag: "filesystem-1", 558 } 559 filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{ 560 MachineTag: "machine-0", 561 FilesystemTag: "filesystem-1", 562 } 563 564 args := &workerArgs{filesystems: filesystemAccessor} 565 worker := newStorageProvisioner(c, args) 566 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 567 defer worker.Kill() 568 569 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 570 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 571 }, { 572 MachineTag: "machine-1", AttachmentTag: "filesystem-2", 573 }, { 574 MachineTag: "machine-2", AttachmentTag: "filesystem-1", 575 }, { 576 MachineTag: "machine-0", AttachmentTag: "filesystem-1", 577 }} 578 // ... but not until the environment config is available. 579 assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set") 580 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 581 args.environ.watcher.changes <- struct{}{} 582 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 583 c.Assert(allFilesystemAttachments, jc.SameContents, expectedFilesystemAttachments) 584 585 // Reattachment should only happen once per session. 586 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{alreadyAttached} 587 assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set") 588 } 589 590 func (s *storageProvisionerSuite) TestCreateVolumeBackedFilesystem(c *gc.C) { 591 filesystemInfoSet := make(chan interface{}) 592 filesystemAccessor := newMockFilesystemAccessor() 593 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 594 filesystemInfoSet <- filesystems 595 return nil, nil 596 } 597 598 args := &workerArgs{ 599 scope: names.NewMachineTag("0"), 600 filesystems: filesystemAccessor, 601 } 602 worker := newStorageProvisioner(c, args) 603 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 604 defer worker.Kill() 605 606 args.volumes.blockDevices[params.MachineStorageId{ 607 MachineTag: "machine-0", 608 AttachmentTag: "volume-0-0", 609 }] = storage.BlockDevice{ 610 DeviceName: "xvdf1", 611 Size: 123, 612 } 613 filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0", "0/1"} 614 assertNoEvent(c, filesystemInfoSet, "filesystem info set") 615 args.environ.watcher.changes <- struct{}{} 616 617 // Only the block device for volume 0/0 is attached at the moment, 618 // so only the corresponding filesystem will be created. 619 filesystemInfo := waitChannel( 620 c, filesystemInfoSet, 621 "waiting for filesystem info to be set", 622 ).([]params.Filesystem) 623 c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{ 624 FilesystemTag: "filesystem-0-0", 625 Info: params.FilesystemInfo{ 626 FilesystemId: "xvdf1", 627 Size: 123, 628 }, 629 }}) 630 631 // If we now attach the block device for volume 0/1 and trigger the 632 // notification, then the storage provisioner will wake up and create 633 // the filesystem. 634 args.volumes.blockDevices[params.MachineStorageId{ 635 MachineTag: "machine-0", 636 AttachmentTag: "volume-0-1", 637 }] = storage.BlockDevice{ 638 DeviceName: "xvdf2", 639 Size: 246, 640 } 641 args.volumes.blockDevicesWatcher.changes <- struct{}{} 642 filesystemInfo = waitChannel( 643 c, filesystemInfoSet, 644 "waiting for filesystem info to be set", 645 ).([]params.Filesystem) 646 c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{ 647 FilesystemTag: "filesystem-0-1", 648 Info: params.FilesystemInfo{ 649 FilesystemId: "xvdf2", 650 Size: 246, 651 }, 652 }}) 653 } 654 655 func (s *storageProvisionerSuite) TestAttachVolumeBackedFilesystem(c *gc.C) { 656 infoSet := make(chan interface{}) 657 filesystemAccessor := newMockFilesystemAccessor() 658 filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 659 infoSet <- attachments 660 return nil, nil 661 } 662 663 args := &workerArgs{ 664 scope: names.NewMachineTag("0"), 665 filesystems: filesystemAccessor, 666 } 667 worker := newStorageProvisioner(c, args) 668 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 669 defer worker.Kill() 670 671 filesystemAccessor.provisionedFilesystems["filesystem-0-0"] = params.Filesystem{ 672 FilesystemTag: "filesystem-0-0", 673 VolumeTag: "volume-0-0", 674 Info: params.FilesystemInfo{ 675 FilesystemId: "whatever", 676 Size: 123, 677 }, 678 } 679 filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 680 681 args.volumes.blockDevices[params.MachineStorageId{ 682 MachineTag: "machine-0", 683 AttachmentTag: "volume-0-0", 684 }] = storage.BlockDevice{ 685 DeviceName: "xvdf1", 686 Size: 123, 687 } 688 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 689 MachineTag: "machine-0", 690 AttachmentTag: "filesystem-0-0", 691 }} 692 assertNoEvent(c, infoSet, "filesystem attachment info set") 693 args.environ.watcher.changes <- struct{}{} 694 filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0"} 695 696 info := waitChannel( 697 c, infoSet, "waiting for filesystem attachment info to be set", 698 ).([]params.FilesystemAttachment) 699 c.Assert(info, jc.DeepEquals, []params.FilesystemAttachment{{ 700 FilesystemTag: "filesystem-0-0", 701 MachineTag: "machine-0", 702 Info: params.FilesystemAttachmentInfo{ 703 MountPoint: "/mnt/xvdf1", 704 ReadOnly: true, 705 }, 706 }}) 707 } 708 709 func (s *storageProvisionerSuite) TestUpdateEnvironConfig(c *gc.C) { 710 volumeAccessor := newMockVolumeAccessor() 711 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 712 s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) { 713 c.Assert(envConfig, gc.NotNil) 714 c.Assert(sourceConfig, gc.NotNil) 715 c.Assert(envConfig.AllAttrs()["foo"], gc.Equals, "bar") 716 return nil, errors.New("zinga") 717 } 718 719 args := &workerArgs{volumes: volumeAccessor} 720 worker := newStorageProvisioner(c, args) 721 defer worker.Wait() 722 defer worker.Kill() 723 724 newConfig, err := args.environ.cfg.Apply(map[string]interface{}{"foo": "bar"}) 725 c.Assert(err, jc.ErrorIsNil) 726 727 args.environ.watcher.changes <- struct{}{} 728 args.environ.setConfig(newConfig) 729 args.environ.watcher.changes <- struct{}{} 730 args.volumes.volumesWatcher.changes <- []string{"1", "2"} 731 732 err = worker.Wait() 733 c.Assert(err, gc.ErrorMatches, `creating volumes: getting volume source: getting storage source "dummy": zinga`) 734 } 735 736 func (s *storageProvisionerSuite) TestResourceTags(c *gc.C) { 737 volumeInfoSet := make(chan interface{}) 738 volumeAccessor := newMockVolumeAccessor() 739 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 740 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 741 defer close(volumeInfoSet) 742 return nil, nil 743 } 744 745 filesystemInfoSet := make(chan interface{}) 746 filesystemAccessor := newMockFilesystemAccessor() 747 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 748 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 749 defer close(filesystemInfoSet) 750 return nil, nil 751 } 752 753 var volumeSource dummyVolumeSource 754 s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) { 755 return &volumeSource, nil 756 } 757 758 var filesystemSource dummyFilesystemSource 759 s.provider.filesystemSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.FilesystemSource, error) { 760 return &filesystemSource, nil 761 } 762 763 args := &workerArgs{ 764 volumes: volumeAccessor, 765 filesystems: filesystemAccessor, 766 } 767 worker := newStorageProvisioner(c, args) 768 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 769 defer worker.Kill() 770 771 volumeAccessor.volumesWatcher.changes <- []string{"1"} 772 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 773 args.environ.watcher.changes <- struct{}{} 774 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 775 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 776 c.Assert(volumeSource.createVolumesArgs, jc.DeepEquals, [][]storage.VolumeParams{{{ 777 Tag: names.NewVolumeTag("1"), 778 Size: 1024, 779 Provider: "dummy", 780 Attributes: map[string]interface{}{"persistent": true}, 781 ResourceTags: map[string]string{"very": "fancy"}, 782 Attachment: &storage.VolumeAttachmentParams{ 783 Volume: names.NewVolumeTag("1"), 784 AttachmentParams: storage.AttachmentParams{ 785 Machine: names.NewMachineTag("1"), 786 Provider: "dummy", 787 InstanceId: "already-provisioned-1", 788 ReadOnly: true, 789 }, 790 }, 791 }}}) 792 c.Assert(filesystemSource.createFilesystemsArgs, jc.DeepEquals, [][]storage.FilesystemParams{{{ 793 Tag: names.NewFilesystemTag("1"), 794 Size: 1024, 795 Provider: "dummy", 796 ResourceTags: map[string]string{"very": "fancy"}, 797 }}}) 798 } 799 800 func (s *storageProvisionerSuite) TestSetVolumeInfoErrorStopsWorker(c *gc.C) { 801 volumeAccessor := newMockVolumeAccessor() 802 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 803 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 804 return nil, errors.New("belly up") 805 } 806 807 args := &workerArgs{volumes: volumeAccessor} 808 worker := newStorageProvisioner(c, args) 809 defer worker.Wait() 810 defer worker.Kill() 811 812 done := make(chan interface{}) 813 go func() { 814 defer close(done) 815 err := worker.Wait() 816 c.Assert(err, gc.ErrorMatches, "creating volumes: publishing volumes to state: belly up") 817 }() 818 819 args.volumes.volumesWatcher.changes <- []string{"1"} 820 args.environ.watcher.changes <- struct{}{} 821 waitChannel(c, done, "waiting for worker to exit") 822 } 823 824 func (s *storageProvisionerSuite) TestSetVolumeInfoErrorResultDoesNotStopWorker(c *gc.C) { 825 volumeAccessor := newMockVolumeAccessor() 826 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 827 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 828 return []params.ErrorResult{{Error: ¶ms.Error{Message: "message", Code: "code"}}}, nil 829 } 830 831 args := &workerArgs{volumes: volumeAccessor} 832 worker := newStorageProvisioner(c, args) 833 defer func() { 834 err := worker.Wait() 835 c.Assert(err, jc.ErrorIsNil) 836 }() 837 defer worker.Kill() 838 839 done := make(chan interface{}) 840 go func() { 841 defer close(done) 842 worker.Wait() 843 }() 844 845 args.volumes.volumesWatcher.changes <- []string{"1"} 846 args.environ.watcher.changes <- struct{}{} 847 assertNoEvent(c, done, "worker exited") 848 } 849 850 func (s *storageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) { 851 removed := make(chan interface{}) 852 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 853 defer close(removed) 854 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 855 MachineTag: "machine-0", 856 AttachmentTag: "volume-0", 857 }}) 858 return make([]params.ErrorResult, len(ids)), nil 859 } 860 861 args := &workerArgs{ 862 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 863 } 864 worker := newStorageProvisioner(c, args) 865 defer worker.Wait() 866 defer worker.Kill() 867 868 args.volumes.attachmentsWatcher.changes <- []params.MachineStorageId{{ 869 MachineTag: "machine-0", AttachmentTag: "volume-0", 870 }} 871 args.environ.watcher.changes <- struct{}{} 872 waitChannel(c, removed, "waiting for attachment to be removed") 873 } 874 875 func (s *storageProvisionerSuite) TestDetachVolumes(c *gc.C) { 876 var attached bool 877 volumeAttachmentInfoSet := make(chan interface{}) 878 volumeAccessor := newMockVolumeAccessor() 879 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 880 close(volumeAttachmentInfoSet) 881 attached = true 882 for _, a := range volumeAttachments { 883 id := params.MachineStorageId{ 884 MachineTag: a.MachineTag, 885 AttachmentTag: a.VolumeTag, 886 } 887 volumeAccessor.provisionedAttachments[id] = a 888 } 889 return make([]params.ErrorResult, len(volumeAttachments)), nil 890 } 891 892 expectedAttachmentIds := []params.MachineStorageId{{ 893 MachineTag: "machine-1", AttachmentTag: "volume-1", 894 }} 895 896 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 897 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 898 life := params.Alive 899 if attached { 900 life = params.Dying 901 } 902 return []params.LifeResult{{Life: life}}, nil 903 } 904 905 detached := make(chan interface{}) 906 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) { 907 c.Assert(args, gc.HasLen, 1) 908 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 909 c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 910 defer close(detached) 911 return make([]error, len(args)), nil 912 } 913 914 removed := make(chan interface{}) 915 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 916 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 917 close(removed) 918 return make([]params.ErrorResult, len(ids)), nil 919 } 920 921 // volume-1 and machine-1 are provisioned. 922 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 923 VolumeTag: "volume-1", 924 Info: params.VolumeInfo{ 925 VolumeId: "vol-123", 926 }, 927 } 928 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 929 930 args := &workerArgs{ 931 volumes: volumeAccessor, 932 life: &mockLifecycleManager{ 933 attachmentLife: attachmentLife, 934 removeAttachments: removeAttachments, 935 }, 936 } 937 worker := newStorageProvisioner(c, args) 938 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 939 defer worker.Kill() 940 941 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 942 MachineTag: "machine-1", AttachmentTag: "volume-1", 943 }} 944 volumeAccessor.volumesWatcher.changes <- []string{"1"} 945 args.environ.watcher.changes <- struct{}{} 946 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 947 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 948 MachineTag: "machine-1", AttachmentTag: "volume-1", 949 }} 950 waitChannel(c, detached, "waiting for volume to be detached") 951 waitChannel(c, removed, "waiting for attachment to be removed") 952 } 953 954 func (s *storageProvisionerSuite) TestDetachVolumesRetry(c *gc.C) { 955 machine := names.NewMachineTag("1") 956 volume := names.NewVolumeTag("1") 957 attachmentId := params.MachineStorageId{ 958 MachineTag: machine.String(), AttachmentTag: volume.String(), 959 } 960 volumeAccessor := newMockVolumeAccessor() 961 volumeAccessor.provisionedAttachments[attachmentId] = params.VolumeAttachment{ 962 MachineTag: machine.String(), 963 VolumeTag: volume.String(), 964 } 965 volumeAccessor.provisionedVolumes[volume.String()] = params.Volume{ 966 VolumeTag: volume.String(), 967 Info: params.VolumeInfo{ 968 VolumeId: "vol-123", 969 }, 970 } 971 volumeAccessor.provisionedMachines[machine.String()] = instance.Id("already-provisioned-1") 972 973 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 974 return []params.LifeResult{{Life: params.Dying}}, nil 975 } 976 977 // mockFunc's After will progress the current time by the specified 978 // duration and signal the channel immediately. 979 clock := &mockClock{} 980 var detachVolumeTimes []time.Time 981 982 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) { 983 detachVolumeTimes = append(detachVolumeTimes, clock.Now()) 984 if len(detachVolumeTimes) < 10 { 985 return []error{errors.New("badness")}, nil 986 } 987 return []error{nil}, nil 988 } 989 990 removed := make(chan interface{}) 991 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 992 close(removed) 993 return make([]params.ErrorResult, len(ids)), nil 994 } 995 996 args := &workerArgs{ 997 volumes: volumeAccessor, 998 clock: clock, 999 life: &mockLifecycleManager{ 1000 attachmentLife: attachmentLife, 1001 removeAttachments: removeAttachments, 1002 }, 1003 } 1004 worker := newStorageProvisioner(c, args) 1005 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1006 defer worker.Kill() 1007 1008 volumeAccessor.volumesWatcher.changes <- []string{volume.Id()} 1009 args.environ.watcher.changes <- struct{}{} 1010 volumeAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{attachmentId} 1011 waitChannel(c, removed, "waiting for attachment to be removed") 1012 c.Assert(detachVolumeTimes, gc.HasLen, 10) 1013 1014 // The first attempt should have been immediate: T0. 1015 c.Assert(detachVolumeTimes[0], gc.Equals, time.Time{}) 1016 1017 delays := make([]time.Duration, len(detachVolumeTimes)-1) 1018 for i := range detachVolumeTimes[1:] { 1019 delays[i] = detachVolumeTimes[i+1].Sub(detachVolumeTimes[i]) 1020 } 1021 c.Assert(delays, jc.DeepEquals, []time.Duration{ 1022 30 * time.Second, 1023 1 * time.Minute, 1024 2 * time.Minute, 1025 4 * time.Minute, 1026 8 * time.Minute, 1027 16 * time.Minute, 1028 30 * time.Minute, // ceiling reached 1029 30 * time.Minute, 1030 30 * time.Minute, 1031 }) 1032 1033 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 1034 {Tag: "volume-1", Status: "detaching", Info: "badness"}, // DetachVolumes 1035 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1036 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1037 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1038 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1039 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1040 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1041 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1042 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1043 {Tag: "volume-1", Status: "detached", Info: ""}, 1044 }) 1045 } 1046 1047 func (s *storageProvisionerSuite) TestDetachFilesystemsUnattached(c *gc.C) { 1048 removed := make(chan interface{}) 1049 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1050 defer close(removed) 1051 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 1052 MachineTag: "machine-0", 1053 AttachmentTag: "filesystem-0", 1054 }}) 1055 return make([]params.ErrorResult, len(ids)), nil 1056 } 1057 1058 args := &workerArgs{ 1059 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 1060 } 1061 worker := newStorageProvisioner(c, args) 1062 defer worker.Wait() 1063 defer worker.Kill() 1064 1065 args.filesystems.attachmentsWatcher.changes <- []params.MachineStorageId{{ 1066 MachineTag: "machine-0", AttachmentTag: "filesystem-0", 1067 }} 1068 args.environ.watcher.changes <- struct{}{} 1069 waitChannel(c, removed, "waiting for attachment to be removed") 1070 } 1071 1072 func (s *storageProvisionerSuite) TestDetachFilesystems(c *gc.C) { 1073 var attached bool 1074 filesystemAttachmentInfoSet := make(chan interface{}) 1075 filesystemAccessor := newMockFilesystemAccessor() 1076 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 1077 close(filesystemAttachmentInfoSet) 1078 attached = true 1079 for _, a := range filesystemAttachments { 1080 id := params.MachineStorageId{ 1081 MachineTag: a.MachineTag, 1082 AttachmentTag: a.FilesystemTag, 1083 } 1084 filesystemAccessor.provisionedAttachments[id] = a 1085 } 1086 return make([]params.ErrorResult, len(filesystemAttachments)), nil 1087 } 1088 1089 expectedAttachmentIds := []params.MachineStorageId{{ 1090 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1091 }} 1092 1093 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1094 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1095 life := params.Alive 1096 if attached { 1097 life = params.Dying 1098 } 1099 return []params.LifeResult{{Life: life}}, nil 1100 } 1101 1102 detached := make(chan interface{}) 1103 s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]error, error) { 1104 c.Assert(args, gc.HasLen, 1) 1105 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 1106 c.Assert(args[0].Filesystem.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 1107 defer close(detached) 1108 return make([]error, len(args)), nil 1109 } 1110 1111 removed := make(chan interface{}) 1112 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1113 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1114 close(removed) 1115 return make([]params.ErrorResult, len(ids)), nil 1116 } 1117 1118 // filesystem-1 and machine-1 are provisioned. 1119 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 1120 FilesystemTag: "filesystem-1", 1121 Info: params.FilesystemInfo{ 1122 FilesystemId: "fs-id", 1123 }, 1124 } 1125 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 1126 1127 args := &workerArgs{ 1128 filesystems: filesystemAccessor, 1129 life: &mockLifecycleManager{ 1130 attachmentLife: attachmentLife, 1131 removeAttachments: removeAttachments, 1132 }, 1133 } 1134 worker := newStorageProvisioner(c, args) 1135 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1136 defer worker.Kill() 1137 1138 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 1139 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1140 }} 1141 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 1142 args.environ.watcher.changes <- struct{}{} 1143 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 1144 filesystemAccessor.attachmentsWatcher.changes <- []params.MachineStorageId{{ 1145 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1146 }} 1147 waitChannel(c, detached, "waiting for filesystem to be detached") 1148 waitChannel(c, removed, "waiting for attachment to be removed") 1149 } 1150 1151 func (s *storageProvisionerSuite) TestDestroyVolumes(c *gc.C) { 1152 provisionedVolume := names.NewVolumeTag("1") 1153 unprovisionedVolume := names.NewVolumeTag("2") 1154 1155 volumeAccessor := newMockVolumeAccessor() 1156 volumeAccessor.provisionVolume(provisionedVolume) 1157 1158 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1159 results := make([]params.LifeResult, len(tags)) 1160 for i := range results { 1161 results[i].Life = params.Dead 1162 } 1163 return results, nil 1164 } 1165 1166 destroyedChan := make(chan interface{}, 1) 1167 s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) { 1168 destroyedChan <- volumeIds 1169 return make([]error, len(volumeIds)), nil 1170 } 1171 1172 removedChan := make(chan interface{}, 1) 1173 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1174 removedChan <- tags 1175 return make([]params.ErrorResult, len(tags)), nil 1176 } 1177 1178 args := &workerArgs{ 1179 volumes: volumeAccessor, 1180 life: &mockLifecycleManager{ 1181 life: life, 1182 remove: remove, 1183 }, 1184 } 1185 worker := newStorageProvisioner(c, args) 1186 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1187 defer worker.Kill() 1188 1189 volumeAccessor.volumesWatcher.changes <- []string{ 1190 provisionedVolume.Id(), 1191 unprovisionedVolume.Id(), 1192 } 1193 args.environ.watcher.changes <- struct{}{} 1194 1195 // Both volumes should be removed; the provisioned one 1196 // should be deprovisioned first. 1197 1198 destroyed := waitChannel(c, destroyedChan, "waiting for volume to be deprovisioned") 1199 assertNoEvent(c, destroyedChan, "volumes deprovisioned") 1200 c.Assert(destroyed, jc.DeepEquals, []string{"vol-1"}) 1201 1202 var removed []names.Tag 1203 for len(removed) < 2 { 1204 tags := waitChannel(c, removedChan, "waiting for volumes to be removed").([]names.Tag) 1205 removed = append(removed, tags...) 1206 } 1207 c.Assert(removed, jc.SameContents, []names.Tag{provisionedVolume, unprovisionedVolume}) 1208 assertNoEvent(c, removedChan, "volumes removed") 1209 } 1210 1211 func (s *storageProvisionerSuite) TestDestroyVolumesRetry(c *gc.C) { 1212 volume := names.NewVolumeTag("1") 1213 volumeAccessor := newMockVolumeAccessor() 1214 volumeAccessor.provisionVolume(volume) 1215 1216 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1217 return []params.LifeResult{{Life: params.Dead}}, nil 1218 } 1219 1220 // mockFunc's After will progress the current time by the specified 1221 // duration and signal the channel immediately. 1222 clock := &mockClock{} 1223 var destroyVolumeTimes []time.Time 1224 1225 s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) { 1226 destroyVolumeTimes = append(destroyVolumeTimes, clock.Now()) 1227 if len(destroyVolumeTimes) < 10 { 1228 return []error{errors.New("badness")}, nil 1229 } 1230 return []error{nil}, nil 1231 } 1232 1233 removedChan := make(chan interface{}, 1) 1234 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1235 removedChan <- tags 1236 return make([]params.ErrorResult, len(tags)), nil 1237 } 1238 1239 args := &workerArgs{ 1240 volumes: volumeAccessor, 1241 clock: clock, 1242 life: &mockLifecycleManager{ 1243 life: life, 1244 remove: remove, 1245 }, 1246 } 1247 worker := newStorageProvisioner(c, args) 1248 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1249 defer worker.Kill() 1250 1251 volumeAccessor.volumesWatcher.changes <- []string{volume.Id()} 1252 args.environ.watcher.changes <- struct{}{} 1253 waitChannel(c, removedChan, "waiting for volume to be removed") 1254 c.Assert(destroyVolumeTimes, gc.HasLen, 10) 1255 1256 // The first attempt should have been immediate: T0. 1257 c.Assert(destroyVolumeTimes[0], gc.Equals, time.Time{}) 1258 1259 delays := make([]time.Duration, len(destroyVolumeTimes)-1) 1260 for i := range destroyVolumeTimes[1:] { 1261 delays[i] = destroyVolumeTimes[i+1].Sub(destroyVolumeTimes[i]) 1262 } 1263 c.Assert(delays, jc.DeepEquals, []time.Duration{ 1264 30 * time.Second, 1265 1 * time.Minute, 1266 2 * time.Minute, 1267 4 * time.Minute, 1268 8 * time.Minute, 1269 16 * time.Minute, 1270 30 * time.Minute, // ceiling reached 1271 30 * time.Minute, 1272 30 * time.Minute, 1273 }) 1274 1275 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 1276 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1277 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1278 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1279 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1280 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1281 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1282 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1283 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1284 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1285 }) 1286 } 1287 1288 func (s *storageProvisionerSuite) TestDestroyFilesystems(c *gc.C) { 1289 provisionedFilesystem := names.NewFilesystemTag("1") 1290 unprovisionedFilesystem := names.NewFilesystemTag("2") 1291 1292 filesystemAccessor := newMockFilesystemAccessor() 1293 filesystemAccessor.provisionFilesystem(provisionedFilesystem) 1294 1295 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1296 results := make([]params.LifeResult, len(tags)) 1297 for i := range results { 1298 results[i].Life = params.Dead 1299 } 1300 return results, nil 1301 } 1302 1303 removedChan := make(chan interface{}, 1) 1304 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1305 removedChan <- tags 1306 return make([]params.ErrorResult, len(tags)), nil 1307 } 1308 1309 args := &workerArgs{ 1310 filesystems: filesystemAccessor, 1311 life: &mockLifecycleManager{ 1312 life: life, 1313 remove: remove, 1314 }, 1315 } 1316 worker := newStorageProvisioner(c, args) 1317 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1318 defer worker.Kill() 1319 1320 filesystemAccessor.filesystemsWatcher.changes <- []string{ 1321 provisionedFilesystem.Id(), 1322 unprovisionedFilesystem.Id(), 1323 } 1324 args.environ.watcher.changes <- struct{}{} 1325 1326 // Both filesystems should be removed; the provisioned one 1327 // *should* be deprovisioned first, but we don't currently 1328 // have the ability to do so via the storage provider API. 1329 1330 var removed []names.Tag 1331 for len(removed) < 2 { 1332 tags := waitChannel(c, removedChan, "waiting for filesystems to be removed").([]names.Tag) 1333 removed = append(removed, tags...) 1334 } 1335 c.Assert(removed, jc.SameContents, []names.Tag{provisionedFilesystem, unprovisionedFilesystem}) 1336 assertNoEvent(c, removedChan, "filesystems removed") 1337 } 1338 1339 func newStorageProvisioner(c *gc.C, args *workerArgs) worker.Worker { 1340 if args == nil { 1341 args = &workerArgs{} 1342 } 1343 if args.scope == nil { 1344 args.scope = coretesting.EnvironmentTag 1345 } 1346 if args.volumes == nil { 1347 args.volumes = newMockVolumeAccessor() 1348 } 1349 if args.filesystems == nil { 1350 args.filesystems = newMockFilesystemAccessor() 1351 } 1352 if args.life == nil { 1353 args.life = &mockLifecycleManager{} 1354 } 1355 if args.environ == nil { 1356 args.environ = newMockEnvironAccessor(c) 1357 } 1358 if args.machines == nil { 1359 args.machines = newMockMachineAccessor(c) 1360 } 1361 if args.clock == nil { 1362 args.clock = &mockClock{} 1363 } 1364 if args.statusSetter == nil { 1365 args.statusSetter = &mockStatusSetter{} 1366 } 1367 return storageprovisioner.NewStorageProvisioner( 1368 args.scope, 1369 "storage-dir", 1370 args.volumes, 1371 args.filesystems, 1372 args.life, 1373 args.environ, 1374 args.machines, 1375 args.statusSetter, 1376 args.clock, 1377 ) 1378 } 1379 1380 type workerArgs struct { 1381 scope names.Tag 1382 volumes *mockVolumeAccessor 1383 filesystems *mockFilesystemAccessor 1384 life *mockLifecycleManager 1385 environ *mockEnvironAccessor 1386 machines *mockMachineAccessor 1387 clock clock.Clock 1388 statusSetter *mockStatusSetter 1389 } 1390 1391 func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} { 1392 select { 1393 case v := <-ch: 1394 return v 1395 case <-time.After(coretesting.LongWait): 1396 c.Fatalf("timed out " + activity) 1397 panic("unreachable") 1398 } 1399 } 1400 1401 func assertNoEvent(c *gc.C, ch <-chan interface{}, event string) { 1402 select { 1403 case <-ch: 1404 c.Fatalf("unexpected " + event) 1405 case <-time.After(coretesting.ShortWait): 1406 } 1407 }