github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "time" 8 9 "github.com/juju/errors" 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/watcher" 22 "github.com/juju/juju/worker" 23 "github.com/juju/juju/worker/storageprovisioner" 24 ) 25 26 type storageProvisionerSuite struct { 27 coretesting.BaseSuite 28 provider *dummyProvider 29 managedFilesystemSource *mockManagedFilesystemSource 30 } 31 32 var _ = gc.Suite(&storageProvisionerSuite{}) 33 34 func (s *storageProvisionerSuite) SetUpTest(c *gc.C) { 35 s.BaseSuite.SetUpTest(c) 36 s.provider = &dummyProvider{dynamic: true} 37 registry.RegisterProvider("dummy", s.provider) 38 s.AddCleanup(func(*gc.C) { 39 registry.RegisterProvider("dummy", nil) 40 }) 41 42 s.managedFilesystemSource = nil 43 s.PatchValue( 44 storageprovisioner.NewManagedFilesystemSource, 45 func( 46 blockDevices map[names.VolumeTag]storage.BlockDevice, 47 filesystems map[names.FilesystemTag]storage.Filesystem, 48 ) storage.FilesystemSource { 49 s.managedFilesystemSource = &mockManagedFilesystemSource{ 50 blockDevices: blockDevices, 51 filesystems: filesystems, 52 } 53 return s.managedFilesystemSource 54 }, 55 ) 56 } 57 58 func (s *storageProvisionerSuite) TestStartStop(c *gc.C) { 59 worker, err := storageprovisioner.NewStorageProvisioner(storageprovisioner.Config{ 60 Scope: coretesting.ModelTag, 61 Volumes: newMockVolumeAccessor(), 62 Filesystems: newMockFilesystemAccessor(), 63 Life: &mockLifecycleManager{}, 64 Environ: newMockModelAccessor(c), 65 Machines: newMockMachineAccessor(c), 66 Status: &mockStatusSetter{}, 67 Clock: &mockClock{}, 68 }) 69 c.Assert(err, jc.ErrorIsNil) 70 71 worker.Kill() 72 c.Assert(worker.Wait(), gc.IsNil) 73 } 74 75 func (s *storageProvisionerSuite) TestInvalidConfig(c *gc.C) { 76 _, err := storageprovisioner.NewStorageProvisioner(almostValidConfig()) 77 c.Check(err, jc.Satisfies, errors.IsNotValid) 78 } 79 80 func (s *storageProvisionerSuite) TestVolumeAdded(c *gc.C) { 81 expectedVolumes := []params.Volume{{ 82 VolumeTag: "volume-1", 83 Info: params.VolumeInfo{ 84 VolumeId: "id-1", 85 HardwareId: "serial-1", 86 Size: 1024, 87 Persistent: true, 88 }, 89 }, { 90 VolumeTag: "volume-2", 91 Info: params.VolumeInfo{ 92 VolumeId: "id-2", 93 HardwareId: "serial-2", 94 Size: 1024, 95 }, 96 }} 97 expectedVolumeAttachments := []params.VolumeAttachment{{ 98 VolumeTag: "volume-1", 99 MachineTag: "machine-1", 100 Info: params.VolumeAttachmentInfo{ 101 DeviceName: "/dev/sda1", 102 ReadOnly: true, 103 }, 104 }, { 105 VolumeTag: "volume-2", 106 MachineTag: "machine-1", 107 Info: params.VolumeAttachmentInfo{ 108 DeviceName: "/dev/sda2", 109 }, 110 }} 111 112 volumeInfoSet := make(chan interface{}) 113 volumeAccessor := newMockVolumeAccessor() 114 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 115 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 116 defer close(volumeInfoSet) 117 c.Assert(volumes, jc.SameContents, expectedVolumes) 118 return nil, nil 119 } 120 121 volumeAttachmentInfoSet := make(chan interface{}) 122 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 123 defer close(volumeAttachmentInfoSet) 124 c.Assert(volumeAttachments, jc.SameContents, expectedVolumeAttachments) 125 return nil, nil 126 } 127 128 args := &workerArgs{volumes: volumeAccessor} 129 worker := newStorageProvisioner(c, args) 130 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 131 defer worker.Kill() 132 133 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 134 MachineTag: "machine-1", AttachmentTag: "volume-1", 135 }, { 136 MachineTag: "machine-1", AttachmentTag: "volume-2", 137 }} 138 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set") 139 140 // The worker should create volumes according to ids "1" and "2". 141 volumeAccessor.volumesWatcher.changes <- []string{"1", "2"} 142 // ... but not until the environment config is available. 143 assertNoEvent(c, volumeInfoSet, "volume info set") 144 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 145 args.environ.watcher.changes <- struct{}{} 146 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 147 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 148 } 149 150 func (s *storageProvisionerSuite) TestCreateVolumeCreatesAttachment(c *gc.C) { 151 volumeAccessor := newMockVolumeAccessor() 152 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 153 154 volumeAttachmentInfoSet := make(chan interface{}) 155 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 156 defer close(volumeAttachmentInfoSet) 157 return make([]params.ErrorResult, len(volumeAttachments)), nil 158 } 159 160 s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 161 volumeAccessor.provisionedAttachments[params.MachineStorageId{ 162 MachineTag: args[0].Attachment.Machine.String(), 163 AttachmentTag: args[0].Attachment.Volume.String(), 164 }] = params.VolumeAttachment{ 165 VolumeTag: args[0].Attachment.Volume.String(), 166 MachineTag: args[0].Attachment.Machine.String(), 167 } 168 return []storage.CreateVolumesResult{{ 169 Volume: &storage.Volume{ 170 Tag: args[0].Tag, 171 VolumeInfo: storage.VolumeInfo{ 172 VolumeId: "vol-ume", 173 }, 174 }, 175 VolumeAttachment: &storage.VolumeAttachment{ 176 Volume: args[0].Attachment.Volume, 177 Machine: args[0].Attachment.Machine, 178 }, 179 }}, nil 180 } 181 182 attachVolumesCalled := make(chan interface{}) 183 s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 184 defer close(attachVolumesCalled) 185 return nil, errors.New("should not be called") 186 } 187 188 args := &workerArgs{volumes: volumeAccessor} 189 worker := newStorageProvisioner(c, args) 190 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 191 defer worker.Kill() 192 193 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 194 MachineTag: "machine-1", AttachmentTag: "volume-1", 195 }} 196 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set") 197 198 // The worker should create volumes according to ids "1". 199 volumeAccessor.volumesWatcher.changes <- []string{"1"} 200 args.environ.watcher.changes <- struct{}{} 201 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 202 assertNoEvent(c, attachVolumesCalled, "AttachVolumes called") 203 } 204 205 func (s *storageProvisionerSuite) TestCreateVolumeRetry(c *gc.C) { 206 volumeInfoSet := make(chan interface{}) 207 volumeAccessor := newMockVolumeAccessor() 208 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 209 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 210 defer close(volumeInfoSet) 211 return make([]params.ErrorResult, len(volumes)), nil 212 } 213 214 // mockFunc's After will progress the current time by the specified 215 // duration and signal the channel immediately. 216 clock := &mockClock{} 217 var createVolumeTimes []time.Time 218 219 s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 220 createVolumeTimes = append(createVolumeTimes, clock.Now()) 221 if len(createVolumeTimes) < 10 { 222 return []storage.CreateVolumesResult{{Error: errors.New("badness")}}, nil 223 } 224 return []storage.CreateVolumesResult{{ 225 Volume: &storage.Volume{Tag: args[0].Tag}, 226 }}, nil 227 } 228 229 args := &workerArgs{volumes: volumeAccessor, clock: clock} 230 worker := newStorageProvisioner(c, args) 231 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 232 defer worker.Kill() 233 234 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 235 MachineTag: "machine-1", AttachmentTag: "volume-1", 236 }} 237 volumeAccessor.volumesWatcher.changes <- []string{"1"} 238 args.environ.watcher.changes <- struct{}{} 239 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 240 c.Assert(createVolumeTimes, gc.HasLen, 10) 241 242 // The first attempt should have been immediate: T0. 243 c.Assert(createVolumeTimes[0], gc.Equals, time.Time{}) 244 245 delays := make([]time.Duration, len(createVolumeTimes)-1) 246 for i := range createVolumeTimes[1:] { 247 delays[i] = createVolumeTimes[i+1].Sub(createVolumeTimes[i]) 248 } 249 c.Assert(delays, jc.DeepEquals, []time.Duration{ 250 30 * time.Second, 251 1 * time.Minute, 252 2 * time.Minute, 253 4 * time.Minute, 254 8 * time.Minute, 255 16 * time.Minute, 256 30 * time.Minute, // ceiling reached 257 30 * time.Minute, 258 30 * time.Minute, 259 }) 260 261 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 262 {Tag: "volume-1", Status: "pending", Info: "badness"}, 263 {Tag: "volume-1", Status: "pending", Info: "badness"}, 264 {Tag: "volume-1", Status: "pending", Info: "badness"}, 265 {Tag: "volume-1", Status: "pending", Info: "badness"}, 266 {Tag: "volume-1", Status: "pending", Info: "badness"}, 267 {Tag: "volume-1", Status: "pending", Info: "badness"}, 268 {Tag: "volume-1", Status: "pending", Info: "badness"}, 269 {Tag: "volume-1", Status: "pending", Info: "badness"}, 270 {Tag: "volume-1", Status: "pending", Info: "badness"}, 271 {Tag: "volume-1", Status: "attaching", Info: ""}, 272 }) 273 } 274 275 func (s *storageProvisionerSuite) TestCreateFilesystemRetry(c *gc.C) { 276 filesystemInfoSet := make(chan interface{}) 277 filesystemAccessor := newMockFilesystemAccessor() 278 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 279 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 280 defer close(filesystemInfoSet) 281 return make([]params.ErrorResult, len(filesystems)), nil 282 } 283 284 // mockFunc's After will progress the current time by the specified 285 // duration and signal the channel immediately. 286 clock := &mockClock{} 287 var createFilesystemTimes []time.Time 288 289 s.provider.createFilesystemsFunc = func(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) { 290 createFilesystemTimes = append(createFilesystemTimes, clock.Now()) 291 if len(createFilesystemTimes) < 10 { 292 return []storage.CreateFilesystemsResult{{Error: errors.New("badness")}}, nil 293 } 294 return []storage.CreateFilesystemsResult{{ 295 Filesystem: &storage.Filesystem{Tag: args[0].Tag}, 296 }}, nil 297 } 298 299 args := &workerArgs{filesystems: filesystemAccessor, clock: clock} 300 worker := newStorageProvisioner(c, args) 301 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 302 defer worker.Kill() 303 304 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 305 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 306 }} 307 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 308 args.environ.watcher.changes <- struct{}{} 309 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 310 c.Assert(createFilesystemTimes, gc.HasLen, 10) 311 312 // The first attempt should have been immediate: T0. 313 c.Assert(createFilesystemTimes[0], gc.Equals, time.Time{}) 314 315 delays := make([]time.Duration, len(createFilesystemTimes)-1) 316 for i := range createFilesystemTimes[1:] { 317 delays[i] = createFilesystemTimes[i+1].Sub(createFilesystemTimes[i]) 318 } 319 c.Assert(delays, jc.DeepEquals, []time.Duration{ 320 30 * time.Second, 321 1 * time.Minute, 322 2 * time.Minute, 323 4 * time.Minute, 324 8 * time.Minute, 325 16 * time.Minute, 326 30 * time.Minute, // ceiling reached 327 30 * time.Minute, 328 30 * time.Minute, 329 }) 330 331 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 332 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 333 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 334 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 335 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 336 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 337 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 338 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 339 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 340 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 341 {Tag: "filesystem-1", Status: "attaching", Info: ""}, 342 }) 343 } 344 345 func (s *storageProvisionerSuite) TestAttachVolumeRetry(c *gc.C) { 346 volumeInfoSet := make(chan interface{}) 347 volumeAccessor := newMockVolumeAccessor() 348 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 349 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 350 defer close(volumeInfoSet) 351 return make([]params.ErrorResult, len(volumes)), nil 352 } 353 volumeAttachmentInfoSet := make(chan interface{}) 354 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 355 defer close(volumeAttachmentInfoSet) 356 return make([]params.ErrorResult, len(volumeAttachments)), nil 357 } 358 359 // mockFunc's After will progress the current time by the specified 360 // duration and signal the channel immediately. 361 clock := &mockClock{} 362 var attachVolumeTimes []time.Time 363 364 s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 365 attachVolumeTimes = append(attachVolumeTimes, clock.Now()) 366 if len(attachVolumeTimes) < 10 { 367 return []storage.AttachVolumesResult{{Error: errors.New("badness")}}, nil 368 } 369 return []storage.AttachVolumesResult{{ 370 VolumeAttachment: &storage.VolumeAttachment{ 371 args[0].Volume, 372 args[0].Machine, 373 storage.VolumeAttachmentInfo{ 374 DeviceName: "/dev/sda1", 375 }, 376 }, 377 }}, nil 378 } 379 380 args := &workerArgs{volumes: volumeAccessor, clock: clock} 381 worker := newStorageProvisioner(c, args) 382 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 383 defer worker.Kill() 384 385 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 386 MachineTag: "machine-1", AttachmentTag: "volume-1", 387 }} 388 volumeAccessor.volumesWatcher.changes <- []string{"1"} 389 args.environ.watcher.changes <- struct{}{} 390 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 391 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 392 c.Assert(attachVolumeTimes, gc.HasLen, 10) 393 394 // The first attempt should have been immediate: T0. 395 c.Assert(attachVolumeTimes[0], gc.Equals, time.Time{}) 396 397 delays := make([]time.Duration, len(attachVolumeTimes)-1) 398 for i := range attachVolumeTimes[1:] { 399 delays[i] = attachVolumeTimes[i+1].Sub(attachVolumeTimes[i]) 400 } 401 c.Assert(delays, jc.DeepEquals, []time.Duration{ 402 30 * time.Second, 403 1 * time.Minute, 404 2 * time.Minute, 405 4 * time.Minute, 406 8 * time.Minute, 407 16 * time.Minute, 408 30 * time.Minute, // ceiling reached 409 30 * time.Minute, 410 30 * time.Minute, 411 }) 412 413 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 414 {Tag: "volume-1", Status: "attaching", Info: ""}, // CreateVolumes 415 {Tag: "volume-1", Status: "attaching", Info: "badness"}, // AttachVolumes 416 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 417 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 418 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 419 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 420 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 421 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 422 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 423 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 424 {Tag: "volume-1", Status: "attached", Info: ""}, 425 }) 426 } 427 428 func (s *storageProvisionerSuite) TestAttachFilesystemRetry(c *gc.C) { 429 filesystemInfoSet := make(chan interface{}) 430 filesystemAccessor := newMockFilesystemAccessor() 431 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 432 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 433 defer close(filesystemInfoSet) 434 return make([]params.ErrorResult, len(filesystems)), nil 435 } 436 filesystemAttachmentInfoSet := make(chan interface{}) 437 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 438 defer close(filesystemAttachmentInfoSet) 439 return make([]params.ErrorResult, len(filesystemAttachments)), nil 440 } 441 442 // mockFunc's After will progress the current time by the specified 443 // duration and signal the channel immediately. 444 clock := &mockClock{} 445 var attachFilesystemTimes []time.Time 446 447 s.provider.attachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) { 448 attachFilesystemTimes = append(attachFilesystemTimes, clock.Now()) 449 if len(attachFilesystemTimes) < 10 { 450 return []storage.AttachFilesystemsResult{{Error: errors.New("badness")}}, nil 451 } 452 return []storage.AttachFilesystemsResult{{ 453 FilesystemAttachment: &storage.FilesystemAttachment{ 454 args[0].Filesystem, 455 args[0].Machine, 456 storage.FilesystemAttachmentInfo{ 457 Path: "/oh/over/there", 458 }, 459 }, 460 }}, nil 461 } 462 463 args := &workerArgs{filesystems: filesystemAccessor, clock: clock} 464 worker := newStorageProvisioner(c, args) 465 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 466 defer worker.Kill() 467 468 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 469 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 470 }} 471 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 472 args.environ.watcher.changes <- struct{}{} 473 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 474 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 475 c.Assert(attachFilesystemTimes, gc.HasLen, 10) 476 477 // The first attempt should have been immediate: T0. 478 c.Assert(attachFilesystemTimes[0], gc.Equals, time.Time{}) 479 480 delays := make([]time.Duration, len(attachFilesystemTimes)-1) 481 for i := range attachFilesystemTimes[1:] { 482 delays[i] = attachFilesystemTimes[i+1].Sub(attachFilesystemTimes[i]) 483 } 484 c.Assert(delays, jc.DeepEquals, []time.Duration{ 485 30 * time.Second, 486 1 * time.Minute, 487 2 * time.Minute, 488 4 * time.Minute, 489 8 * time.Minute, 490 16 * time.Minute, 491 30 * time.Minute, // ceiling reached 492 30 * time.Minute, 493 30 * time.Minute, 494 }) 495 496 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 497 {Tag: "filesystem-1", Status: "attaching", Info: ""}, // CreateFilesystems 498 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, // AttachFilesystems 499 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 500 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 501 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 502 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 503 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 504 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 505 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 506 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 507 {Tag: "filesystem-1", Status: "attached", Info: ""}, 508 }) 509 } 510 511 func (s *storageProvisionerSuite) TestValidateVolumeParams(c *gc.C) { 512 volumeAccessor := newMockVolumeAccessor() 513 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 514 volumeAccessor.provisionedVolumes["volume-3"] = params.Volume{VolumeTag: "volume-3"} 515 volumeAccessor.provisionedVolumes["volume-4"] = params.Volume{ 516 VolumeTag: "volume-4", 517 Info: params.VolumeInfo{VolumeId: "vol-ume"}, 518 } 519 520 var validateCalls int 521 validated := make(chan interface{}, 1) 522 s.provider.validateVolumeParamsFunc = func(p storage.VolumeParams) error { 523 validateCalls++ 524 validated <- p 525 switch p.Tag.String() { 526 case "volume-1", "volume-3": 527 return errors.New("something is wrong") 528 } 529 return nil 530 } 531 532 life := func(tags []names.Tag) ([]params.LifeResult, error) { 533 results := make([]params.LifeResult, len(tags)) 534 for i := range results { 535 switch tags[i].String() { 536 case "volume-3", "volume-4": 537 results[i].Life = params.Dead 538 default: 539 results[i].Life = params.Alive 540 } 541 } 542 return results, nil 543 } 544 545 createdVolumes := make(chan interface{}, 1) 546 s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 547 createdVolumes <- args 548 if len(args) != 1 { 549 return nil, errors.New("expected one argument") 550 } 551 return []storage.CreateVolumesResult{{ 552 Volume: &storage.Volume{Tag: args[0].Tag}, 553 }}, nil 554 } 555 556 destroyedVolumes := make(chan interface{}, 1) 557 s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) { 558 destroyedVolumes <- volumeIds 559 return make([]error, len(volumeIds)), nil 560 } 561 562 args := &workerArgs{ 563 volumes: volumeAccessor, 564 life: &mockLifecycleManager{ 565 life: life, 566 }, 567 } 568 worker := newStorageProvisioner(c, args) 569 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 570 defer worker.Kill() 571 572 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 573 MachineTag: "machine-1", AttachmentTag: "volume-1", 574 }, { 575 MachineTag: "machine-1", AttachmentTag: "volume-2", 576 }} 577 volumeAccessor.volumesWatcher.changes <- []string{"1"} 578 args.environ.watcher.changes <- struct{}{} 579 waitChannel(c, validated, "waiting for volume parameter validation") 580 assertNoEvent(c, createdVolumes, "volume created") 581 c.Assert(validateCalls, gc.Equals, 1) 582 583 // Failure to create volume-1 should not block creation volume-2. 584 volumeAccessor.volumesWatcher.changes <- []string{"2"} 585 waitChannel(c, validated, "waiting for volume parameter validation") 586 createVolumeParams := waitChannel(c, createdVolumes, "volume created").([]storage.VolumeParams) 587 c.Assert(createVolumeParams, gc.HasLen, 1) 588 c.Assert(createVolumeParams[0].Tag.String(), gc.Equals, "volume-2") 589 c.Assert(validateCalls, gc.Equals, 2) 590 591 volumeAccessor.volumesWatcher.changes <- []string{"3"} 592 waitChannel(c, validated, "waiting for volume parameter validation") 593 assertNoEvent(c, destroyedVolumes, "volume destroyed") 594 c.Assert(validateCalls, gc.Equals, 3) 595 596 // Failure to destroy volume-3 should not block creation of volume-4. 597 volumeAccessor.volumesWatcher.changes <- []string{"4"} 598 waitChannel(c, validated, "waiting for volume parameter validation") 599 destroyVolumeParams := waitChannel(c, destroyedVolumes, "volume destroyed").([]string) 600 c.Assert(destroyVolumeParams, jc.DeepEquals, []string{"vol-ume"}) 601 c.Assert(validateCalls, gc.Equals, 4) 602 603 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 604 {Tag: "volume-1", Status: "error", Info: "something is wrong"}, 605 {Tag: "volume-2", Status: "attaching"}, 606 {Tag: "volume-3", Status: "error", Info: "something is wrong"}, 607 // destroyed volumes are removed immediately, 608 // so there is no status update. 609 }) 610 } 611 612 func (s *storageProvisionerSuite) TestValidateFilesystemParams(c *gc.C) { 613 filesystemAccessor := newMockFilesystemAccessor() 614 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 615 filesystemAccessor.provisionedFilesystems["filesystem-3"] = params.Filesystem{FilesystemTag: "filesystem-3"} 616 filesystemAccessor.provisionedFilesystems["filesystem-4"] = params.Filesystem{ 617 FilesystemTag: "filesystem-4", 618 Info: params.FilesystemInfo{FilesystemId: "fs-id"}, 619 } 620 621 var validateCalls int 622 validated := make(chan interface{}, 1) 623 s.provider.validateFilesystemParamsFunc = func(p storage.FilesystemParams) error { 624 validateCalls++ 625 validated <- p 626 switch p.Tag.String() { 627 case "filesystem-1", "filesystem-3": 628 return errors.New("something is wrong") 629 } 630 return nil 631 } 632 633 life := func(tags []names.Tag) ([]params.LifeResult, error) { 634 results := make([]params.LifeResult, len(tags)) 635 for i := range results { 636 switch tags[i].String() { 637 case "filesystem-3", "filesystem-4": 638 results[i].Life = params.Dead 639 default: 640 results[i].Life = params.Alive 641 } 642 } 643 return results, nil 644 } 645 646 createdFilesystems := make(chan interface{}, 1) 647 s.provider.createFilesystemsFunc = func(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) { 648 createdFilesystems <- args 649 if len(args) != 1 { 650 return nil, errors.New("expected one argument") 651 } 652 return []storage.CreateFilesystemsResult{{ 653 Filesystem: &storage.Filesystem{Tag: args[0].Tag}, 654 }}, nil 655 } 656 657 destroyedFilesystems := make(chan interface{}, 1) 658 s.provider.destroyFilesystemsFunc = func(filesystemIds []string) ([]error, error) { 659 destroyedFilesystems <- filesystemIds 660 return make([]error, len(filesystemIds)), nil 661 } 662 663 args := &workerArgs{ 664 filesystems: filesystemAccessor, 665 life: &mockLifecycleManager{ 666 life: life, 667 }, 668 } 669 worker := newStorageProvisioner(c, args) 670 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 671 defer worker.Kill() 672 673 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 674 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 675 }, { 676 MachineTag: "machine-1", AttachmentTag: "filesystem-2", 677 }} 678 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 679 args.environ.watcher.changes <- struct{}{} 680 waitChannel(c, validated, "waiting for filesystem parameter validation") 681 assertNoEvent(c, createdFilesystems, "filesystem created") 682 c.Assert(validateCalls, gc.Equals, 1) 683 684 // Failure to create filesystem-1 should not block creation filesystem-2. 685 filesystemAccessor.filesystemsWatcher.changes <- []string{"2"} 686 waitChannel(c, validated, "waiting for filesystem parameter validation") 687 createFilesystemParams := waitChannel(c, createdFilesystems, "filesystem created").([]storage.FilesystemParams) 688 c.Assert(createFilesystemParams, gc.HasLen, 1) 689 c.Assert(createFilesystemParams[0].Tag.String(), gc.Equals, "filesystem-2") 690 c.Assert(validateCalls, gc.Equals, 2) 691 692 filesystemAccessor.filesystemsWatcher.changes <- []string{"3"} 693 waitChannel(c, validated, "waiting for filesystem parameter validation") 694 assertNoEvent(c, destroyedFilesystems, "filesystem destroyed") 695 c.Assert(validateCalls, gc.Equals, 3) 696 697 // Failure to destroy filesystem-3 should not block creation of filesystem-4. 698 filesystemAccessor.filesystemsWatcher.changes <- []string{"4"} 699 waitChannel(c, validated, "waiting for filesystem parameter validation") 700 destroyFilesystemParams := waitChannel(c, destroyedFilesystems, "filesystem destroyed").([]string) 701 c.Assert(destroyFilesystemParams, jc.DeepEquals, []string{"fs-id"}) 702 c.Assert(validateCalls, gc.Equals, 4) 703 704 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 705 {Tag: "filesystem-1", Status: "error", Info: "something is wrong"}, 706 {Tag: "filesystem-2", Status: "attaching"}, 707 {Tag: "filesystem-3", Status: "error", Info: "something is wrong"}, 708 // destroyed filesystems are removed immediately, 709 // so there is no status update. 710 }) 711 } 712 713 func (s *storageProvisionerSuite) TestFilesystemAdded(c *gc.C) { 714 expectedFilesystems := []params.Filesystem{{ 715 FilesystemTag: "filesystem-1", 716 Info: params.FilesystemInfo{ 717 FilesystemId: "id-1", 718 Size: 1024, 719 }, 720 }, { 721 FilesystemTag: "filesystem-2", 722 Info: params.FilesystemInfo{ 723 FilesystemId: "id-2", 724 Size: 1024, 725 }, 726 }} 727 728 filesystemInfoSet := make(chan interface{}) 729 filesystemAccessor := newMockFilesystemAccessor() 730 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 731 defer close(filesystemInfoSet) 732 c.Assert(filesystems, jc.SameContents, expectedFilesystems) 733 return nil, nil 734 } 735 736 args := &workerArgs{filesystems: filesystemAccessor} 737 worker := newStorageProvisioner(c, args) 738 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 739 defer worker.Kill() 740 741 // The worker should create filesystems according to ids "1" and "2". 742 filesystemAccessor.filesystemsWatcher.changes <- []string{"1", "2"} 743 // ... but not until the environment config is available. 744 assertNoEvent(c, filesystemInfoSet, "filesystem info set") 745 args.environ.watcher.changes <- struct{}{} 746 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 747 } 748 749 func (s *storageProvisionerSuite) TestVolumeNeedsInstance(c *gc.C) { 750 volumeInfoSet := make(chan interface{}) 751 volumeAccessor := newMockVolumeAccessor() 752 volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) { 753 defer close(volumeInfoSet) 754 return nil, nil 755 } 756 volumeAccessor.setVolumeAttachmentInfo = func([]params.VolumeAttachment) ([]params.ErrorResult, error) { 757 return nil, nil 758 } 759 760 args := &workerArgs{volumes: volumeAccessor} 761 worker := newStorageProvisioner(c, args) 762 defer worker.Wait() 763 defer worker.Kill() 764 765 volumeAccessor.volumesWatcher.changes <- []string{needsInstanceVolumeId} 766 args.environ.watcher.changes <- struct{}{} 767 assertNoEvent(c, volumeInfoSet, "volume info set") 768 args.machines.instanceIds[names.NewMachineTag("1")] = "inst-id" 769 args.machines.watcher.changes <- struct{}{} 770 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 771 } 772 773 func (s *storageProvisionerSuite) TestVolumeNonDynamic(c *gc.C) { 774 volumeInfoSet := make(chan interface{}) 775 volumeAccessor := newMockVolumeAccessor() 776 volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) { 777 defer close(volumeInfoSet) 778 return nil, nil 779 } 780 781 args := &workerArgs{volumes: volumeAccessor} 782 worker := newStorageProvisioner(c, args) 783 defer worker.Wait() 784 defer worker.Kill() 785 786 // Volumes for non-dynamic providers should not be created. 787 s.provider.dynamic = false 788 args.environ.watcher.changes <- struct{}{} 789 volumeAccessor.volumesWatcher.changes <- []string{"1"} 790 assertNoEvent(c, volumeInfoSet, "volume info set") 791 } 792 793 func (s *storageProvisionerSuite) TestVolumeAttachmentAdded(c *gc.C) { 794 // We should get two volume attachments: 795 // - volume-1 to machine-1, because the volume and 796 // machine are provisioned, but the attachment is not. 797 // - volume-1 to machine-0, because the volume, 798 // machine, and attachment are provisioned, but 799 // in a previous session, so a reattachment is 800 // requested. 801 expectedVolumeAttachments := []params.VolumeAttachment{{ 802 VolumeTag: "volume-1", 803 MachineTag: "machine-1", 804 Info: params.VolumeAttachmentInfo{ 805 DeviceName: "/dev/sda1", 806 ReadOnly: true, 807 }, 808 }, { 809 VolumeTag: "volume-1", 810 MachineTag: "machine-0", 811 Info: params.VolumeAttachmentInfo{ 812 DeviceName: "/dev/sda1", 813 ReadOnly: true, 814 }, 815 }} 816 817 var allVolumeAttachments []params.VolumeAttachment 818 volumeAttachmentInfoSet := make(chan interface{}) 819 volumeAccessor := newMockVolumeAccessor() 820 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 821 allVolumeAttachments = append(allVolumeAttachments, volumeAttachments...) 822 volumeAttachmentInfoSet <- nil 823 return make([]params.ErrorResult, len(volumeAttachments)), nil 824 } 825 826 // volume-1, machine-0, and machine-1 are provisioned. 827 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 828 VolumeTag: "volume-1", 829 Info: params.VolumeInfo{ 830 VolumeId: "vol-123", 831 }, 832 } 833 volumeAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 834 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 835 836 // machine-0/volume-1 attachment is already created. 837 // We should see a reattachment. 838 alreadyAttached := params.MachineStorageId{ 839 MachineTag: "machine-0", 840 AttachmentTag: "volume-1", 841 } 842 volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{ 843 MachineTag: "machine-0", 844 VolumeTag: "volume-1", 845 } 846 847 args := &workerArgs{volumes: volumeAccessor} 848 worker := newStorageProvisioner(c, args) 849 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 850 defer worker.Kill() 851 852 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 853 MachineTag: "machine-1", AttachmentTag: "volume-1", 854 }, { 855 MachineTag: "machine-1", AttachmentTag: "volume-2", 856 }, { 857 MachineTag: "machine-2", AttachmentTag: "volume-1", 858 }, { 859 MachineTag: "machine-0", AttachmentTag: "volume-1", 860 }} 861 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 862 volumeAccessor.volumesWatcher.changes <- []string{"1"} 863 args.environ.watcher.changes <- struct{}{} 864 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 865 c.Assert(allVolumeAttachments, jc.SameContents, expectedVolumeAttachments) 866 867 // Reattachment should only happen once per session. 868 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 869 MachineTag: "machine-0", 870 AttachmentTag: "volume-1", 871 }} 872 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 873 } 874 875 func (s *storageProvisionerSuite) TestFilesystemAttachmentAdded(c *gc.C) { 876 // We should only get a single filesystem attachment, because it is the 877 // only combination where both machine and filesystem are already 878 // provisioned, and the attachmenti s not. 879 // We should get two filesystem attachments: 880 // - filesystem-1 to machine-1, because the filesystem and 881 // machine are provisioned, but the attachment is not. 882 // - filesystem-1 to machine-0, because the filesystem, 883 // machine, and attachment are provisioned, but in a 884 // previous session, so a reattachment is requested. 885 expectedFilesystemAttachments := []params.FilesystemAttachment{{ 886 FilesystemTag: "filesystem-1", 887 MachineTag: "machine-1", 888 Info: params.FilesystemAttachmentInfo{ 889 MountPoint: "/srv/fs-123", 890 }, 891 }, { 892 FilesystemTag: "filesystem-1", 893 MachineTag: "machine-0", 894 Info: params.FilesystemAttachmentInfo{ 895 MountPoint: "/srv/fs-123", 896 }, 897 }} 898 899 var allFilesystemAttachments []params.FilesystemAttachment 900 filesystemAttachmentInfoSet := make(chan interface{}) 901 filesystemAccessor := newMockFilesystemAccessor() 902 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 903 allFilesystemAttachments = append(allFilesystemAttachments, filesystemAttachments...) 904 filesystemAttachmentInfoSet <- nil 905 return make([]params.ErrorResult, len(filesystemAttachments)), nil 906 } 907 908 // filesystem-1 and machine-1 are provisioned. 909 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 910 FilesystemTag: "filesystem-1", 911 Info: params.FilesystemInfo{ 912 FilesystemId: "fs-123", 913 }, 914 } 915 filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 916 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 917 918 // machine-0/filesystem-1 attachment is already created. 919 // We should see a reattachment. 920 alreadyAttached := params.MachineStorageId{ 921 MachineTag: "machine-0", 922 AttachmentTag: "filesystem-1", 923 } 924 filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{ 925 MachineTag: "machine-0", 926 FilesystemTag: "filesystem-1", 927 } 928 929 args := &workerArgs{filesystems: filesystemAccessor} 930 worker := newStorageProvisioner(c, args) 931 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 932 defer worker.Kill() 933 934 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 935 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 936 }, { 937 MachineTag: "machine-1", AttachmentTag: "filesystem-2", 938 }, { 939 MachineTag: "machine-2", AttachmentTag: "filesystem-1", 940 }, { 941 MachineTag: "machine-0", AttachmentTag: "filesystem-1", 942 }} 943 // ... but not until the environment config is available. 944 assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set") 945 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 946 args.environ.watcher.changes <- struct{}{} 947 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 948 c.Assert(allFilesystemAttachments, jc.SameContents, expectedFilesystemAttachments) 949 950 // Reattachment should only happen once per session. 951 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 952 MachineTag: "machine-0", 953 AttachmentTag: "filesystem-1", 954 }} 955 assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set") 956 } 957 958 func (s *storageProvisionerSuite) TestCreateVolumeBackedFilesystem(c *gc.C) { 959 filesystemInfoSet := make(chan interface{}) 960 filesystemAccessor := newMockFilesystemAccessor() 961 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 962 filesystemInfoSet <- filesystems 963 return nil, nil 964 } 965 966 args := &workerArgs{ 967 scope: names.NewMachineTag("0"), 968 filesystems: filesystemAccessor, 969 } 970 worker := newStorageProvisioner(c, args) 971 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 972 defer worker.Kill() 973 974 args.volumes.blockDevices[params.MachineStorageId{ 975 MachineTag: "machine-0", 976 AttachmentTag: "volume-0-0", 977 }] = storage.BlockDevice{ 978 DeviceName: "xvdf1", 979 Size: 123, 980 } 981 filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0", "0/1"} 982 assertNoEvent(c, filesystemInfoSet, "filesystem info set") 983 args.environ.watcher.changes <- struct{}{} 984 985 // Only the block device for volume 0/0 is attached at the moment, 986 // so only the corresponding filesystem will be created. 987 filesystemInfo := waitChannel( 988 c, filesystemInfoSet, 989 "waiting for filesystem info to be set", 990 ).([]params.Filesystem) 991 c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{ 992 FilesystemTag: "filesystem-0-0", 993 Info: params.FilesystemInfo{ 994 FilesystemId: "xvdf1", 995 Size: 123, 996 }, 997 }}) 998 999 // If we now attach the block device for volume 0/1 and trigger the 1000 // notification, then the storage provisioner will wake up and create 1001 // the filesystem. 1002 args.volumes.blockDevices[params.MachineStorageId{ 1003 MachineTag: "machine-0", 1004 AttachmentTag: "volume-0-1", 1005 }] = storage.BlockDevice{ 1006 DeviceName: "xvdf2", 1007 Size: 246, 1008 } 1009 args.volumes.blockDevicesWatcher.changes <- struct{}{} 1010 filesystemInfo = waitChannel( 1011 c, filesystemInfoSet, 1012 "waiting for filesystem info to be set", 1013 ).([]params.Filesystem) 1014 c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{ 1015 FilesystemTag: "filesystem-0-1", 1016 Info: params.FilesystemInfo{ 1017 FilesystemId: "xvdf2", 1018 Size: 246, 1019 }, 1020 }}) 1021 } 1022 1023 func (s *storageProvisionerSuite) TestAttachVolumeBackedFilesystem(c *gc.C) { 1024 infoSet := make(chan interface{}) 1025 filesystemAccessor := newMockFilesystemAccessor() 1026 filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 1027 infoSet <- attachments 1028 return nil, nil 1029 } 1030 1031 args := &workerArgs{ 1032 scope: names.NewMachineTag("0"), 1033 filesystems: filesystemAccessor, 1034 } 1035 worker := newStorageProvisioner(c, args) 1036 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1037 defer worker.Kill() 1038 1039 filesystemAccessor.provisionedFilesystems["filesystem-0-0"] = params.Filesystem{ 1040 FilesystemTag: "filesystem-0-0", 1041 VolumeTag: "volume-0-0", 1042 Info: params.FilesystemInfo{ 1043 FilesystemId: "whatever", 1044 Size: 123, 1045 }, 1046 } 1047 filesystemAccessor.provisionedMachines["machine-0"] = instance.Id("already-provisioned-0") 1048 1049 args.volumes.blockDevices[params.MachineStorageId{ 1050 MachineTag: "machine-0", 1051 AttachmentTag: "volume-0-0", 1052 }] = storage.BlockDevice{ 1053 DeviceName: "xvdf1", 1054 Size: 123, 1055 } 1056 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1057 MachineTag: "machine-0", 1058 AttachmentTag: "filesystem-0-0", 1059 }} 1060 assertNoEvent(c, infoSet, "filesystem attachment info set") 1061 args.environ.watcher.changes <- struct{}{} 1062 filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0"} 1063 1064 info := waitChannel( 1065 c, infoSet, "waiting for filesystem attachment info to be set", 1066 ).([]params.FilesystemAttachment) 1067 c.Assert(info, jc.DeepEquals, []params.FilesystemAttachment{{ 1068 FilesystemTag: "filesystem-0-0", 1069 MachineTag: "machine-0", 1070 Info: params.FilesystemAttachmentInfo{ 1071 MountPoint: "/mnt/xvdf1", 1072 ReadOnly: true, 1073 }, 1074 }}) 1075 } 1076 1077 func (s *storageProvisionerSuite) TestUpdateModelConfig(c *gc.C) { 1078 volumeAccessor := newMockVolumeAccessor() 1079 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 1080 s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) { 1081 c.Assert(envConfig, gc.NotNil) 1082 c.Assert(sourceConfig, gc.NotNil) 1083 c.Assert(envConfig.AllAttrs()["foo"], gc.Equals, "bar") 1084 return nil, errors.New("zinga") 1085 } 1086 1087 args := &workerArgs{volumes: volumeAccessor} 1088 worker := newStorageProvisioner(c, args) 1089 defer worker.Wait() 1090 defer worker.Kill() 1091 1092 newConfig, err := args.environ.cfg.Apply(map[string]interface{}{"foo": "bar"}) 1093 c.Assert(err, jc.ErrorIsNil) 1094 1095 args.environ.watcher.changes <- struct{}{} 1096 args.environ.setConfig(newConfig) 1097 args.environ.watcher.changes <- struct{}{} 1098 args.volumes.volumesWatcher.changes <- []string{"1", "2"} 1099 1100 err = worker.Wait() 1101 c.Assert(err, gc.ErrorMatches, `creating volumes: getting volume source: getting storage source "dummy": zinga`) 1102 } 1103 1104 func (s *storageProvisionerSuite) TestResourceTags(c *gc.C) { 1105 volumeInfoSet := make(chan interface{}) 1106 volumeAccessor := newMockVolumeAccessor() 1107 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 1108 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 1109 defer close(volumeInfoSet) 1110 return nil, nil 1111 } 1112 1113 filesystemInfoSet := make(chan interface{}) 1114 filesystemAccessor := newMockFilesystemAccessor() 1115 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 1116 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 1117 defer close(filesystemInfoSet) 1118 return nil, nil 1119 } 1120 1121 var volumeSource dummyVolumeSource 1122 s.provider.volumeSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.VolumeSource, error) { 1123 return &volumeSource, nil 1124 } 1125 1126 var filesystemSource dummyFilesystemSource 1127 s.provider.filesystemSourceFunc = func(envConfig *config.Config, sourceConfig *storage.Config) (storage.FilesystemSource, error) { 1128 return &filesystemSource, nil 1129 } 1130 1131 args := &workerArgs{ 1132 volumes: volumeAccessor, 1133 filesystems: filesystemAccessor, 1134 } 1135 worker := newStorageProvisioner(c, args) 1136 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1137 defer worker.Kill() 1138 1139 volumeAccessor.volumesWatcher.changes <- []string{"1"} 1140 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 1141 args.environ.watcher.changes <- struct{}{} 1142 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 1143 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 1144 c.Assert(volumeSource.createVolumesArgs, jc.DeepEquals, [][]storage.VolumeParams{{{ 1145 Tag: names.NewVolumeTag("1"), 1146 Size: 1024, 1147 Provider: "dummy", 1148 Attributes: map[string]interface{}{"persistent": true}, 1149 ResourceTags: map[string]string{"very": "fancy"}, 1150 Attachment: &storage.VolumeAttachmentParams{ 1151 Volume: names.NewVolumeTag("1"), 1152 AttachmentParams: storage.AttachmentParams{ 1153 Machine: names.NewMachineTag("1"), 1154 Provider: "dummy", 1155 InstanceId: "already-provisioned-1", 1156 ReadOnly: true, 1157 }, 1158 }, 1159 }}}) 1160 c.Assert(filesystemSource.createFilesystemsArgs, jc.DeepEquals, [][]storage.FilesystemParams{{{ 1161 Tag: names.NewFilesystemTag("1"), 1162 Size: 1024, 1163 Provider: "dummy", 1164 ResourceTags: map[string]string{"very": "fancy"}, 1165 }}}) 1166 } 1167 1168 func (s *storageProvisionerSuite) TestSetVolumeInfoErrorStopsWorker(c *gc.C) { 1169 volumeAccessor := newMockVolumeAccessor() 1170 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 1171 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 1172 return nil, errors.New("belly up") 1173 } 1174 1175 args := &workerArgs{volumes: volumeAccessor} 1176 worker := newStorageProvisioner(c, args) 1177 defer worker.Wait() 1178 defer worker.Kill() 1179 1180 done := make(chan interface{}) 1181 go func() { 1182 defer close(done) 1183 err := worker.Wait() 1184 c.Assert(err, gc.ErrorMatches, "creating volumes: publishing volumes to state: belly up") 1185 }() 1186 1187 args.volumes.volumesWatcher.changes <- []string{"1"} 1188 args.environ.watcher.changes <- struct{}{} 1189 waitChannel(c, done, "waiting for worker to exit") 1190 } 1191 1192 func (s *storageProvisionerSuite) TestSetVolumeInfoErrorResultDoesNotStopWorker(c *gc.C) { 1193 volumeAccessor := newMockVolumeAccessor() 1194 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 1195 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 1196 return []params.ErrorResult{{Error: ¶ms.Error{Message: "message", Code: "code"}}}, nil 1197 } 1198 1199 args := &workerArgs{volumes: volumeAccessor} 1200 worker := newStorageProvisioner(c, args) 1201 defer func() { 1202 err := worker.Wait() 1203 c.Assert(err, jc.ErrorIsNil) 1204 }() 1205 defer worker.Kill() 1206 1207 done := make(chan interface{}) 1208 go func() { 1209 defer close(done) 1210 worker.Wait() 1211 }() 1212 1213 args.volumes.volumesWatcher.changes <- []string{"1"} 1214 args.environ.watcher.changes <- struct{}{} 1215 assertNoEvent(c, done, "worker exited") 1216 } 1217 1218 func (s *storageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) { 1219 removed := make(chan interface{}) 1220 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1221 defer close(removed) 1222 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 1223 MachineTag: "machine-0", 1224 AttachmentTag: "volume-0", 1225 }}) 1226 return make([]params.ErrorResult, len(ids)), nil 1227 } 1228 1229 args := &workerArgs{ 1230 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 1231 } 1232 worker := newStorageProvisioner(c, args) 1233 defer worker.Wait() 1234 defer worker.Kill() 1235 1236 args.volumes.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1237 MachineTag: "machine-0", AttachmentTag: "volume-0", 1238 }} 1239 args.environ.watcher.changes <- struct{}{} 1240 waitChannel(c, removed, "waiting for attachment to be removed") 1241 } 1242 1243 func (s *storageProvisionerSuite) TestDetachVolumes(c *gc.C) { 1244 var attached bool 1245 volumeAttachmentInfoSet := make(chan interface{}) 1246 volumeAccessor := newMockVolumeAccessor() 1247 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 1248 close(volumeAttachmentInfoSet) 1249 attached = true 1250 for _, a := range volumeAttachments { 1251 id := params.MachineStorageId{ 1252 MachineTag: a.MachineTag, 1253 AttachmentTag: a.VolumeTag, 1254 } 1255 volumeAccessor.provisionedAttachments[id] = a 1256 } 1257 return make([]params.ErrorResult, len(volumeAttachments)), nil 1258 } 1259 1260 expectedAttachmentIds := []params.MachineStorageId{{ 1261 MachineTag: "machine-1", AttachmentTag: "volume-1", 1262 }} 1263 1264 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1265 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1266 life := params.Alive 1267 if attached { 1268 life = params.Dying 1269 } 1270 return []params.LifeResult{{Life: life}}, nil 1271 } 1272 1273 detached := make(chan interface{}) 1274 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) { 1275 c.Assert(args, gc.HasLen, 1) 1276 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 1277 c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 1278 defer close(detached) 1279 return make([]error, len(args)), nil 1280 } 1281 1282 removed := make(chan interface{}) 1283 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1284 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1285 close(removed) 1286 return make([]params.ErrorResult, len(ids)), nil 1287 } 1288 1289 // volume-1 and machine-1 are provisioned. 1290 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 1291 VolumeTag: "volume-1", 1292 Info: params.VolumeInfo{ 1293 VolumeId: "vol-123", 1294 }, 1295 } 1296 volumeAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 1297 1298 args := &workerArgs{ 1299 volumes: volumeAccessor, 1300 life: &mockLifecycleManager{ 1301 attachmentLife: attachmentLife, 1302 removeAttachments: removeAttachments, 1303 }, 1304 } 1305 worker := newStorageProvisioner(c, args) 1306 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1307 defer worker.Kill() 1308 1309 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1310 MachineTag: "machine-1", AttachmentTag: "volume-1", 1311 }} 1312 volumeAccessor.volumesWatcher.changes <- []string{"1"} 1313 args.environ.watcher.changes <- struct{}{} 1314 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 1315 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1316 MachineTag: "machine-1", AttachmentTag: "volume-1", 1317 }} 1318 waitChannel(c, detached, "waiting for volume to be detached") 1319 waitChannel(c, removed, "waiting for attachment to be removed") 1320 } 1321 1322 func (s *storageProvisionerSuite) TestDetachVolumesRetry(c *gc.C) { 1323 machine := names.NewMachineTag("1") 1324 volume := names.NewVolumeTag("1") 1325 attachmentId := params.MachineStorageId{ 1326 MachineTag: machine.String(), 1327 AttachmentTag: volume.String(), 1328 } 1329 volumeAccessor := newMockVolumeAccessor() 1330 volumeAccessor.provisionedAttachments[attachmentId] = params.VolumeAttachment{ 1331 MachineTag: machine.String(), 1332 VolumeTag: volume.String(), 1333 } 1334 volumeAccessor.provisionedVolumes[volume.String()] = params.Volume{ 1335 VolumeTag: volume.String(), 1336 Info: params.VolumeInfo{ 1337 VolumeId: "vol-123", 1338 }, 1339 } 1340 volumeAccessor.provisionedMachines[machine.String()] = instance.Id("already-provisioned-1") 1341 1342 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1343 return []params.LifeResult{{Life: params.Dying}}, nil 1344 } 1345 1346 // mockFunc's After will progress the current time by the specified 1347 // duration and signal the channel immediately. 1348 clock := &mockClock{} 1349 var detachVolumeTimes []time.Time 1350 1351 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) { 1352 detachVolumeTimes = append(detachVolumeTimes, clock.Now()) 1353 if len(detachVolumeTimes) < 10 { 1354 return []error{errors.New("badness")}, nil 1355 } 1356 return []error{nil}, nil 1357 } 1358 1359 removed := make(chan interface{}) 1360 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1361 close(removed) 1362 return make([]params.ErrorResult, len(ids)), nil 1363 } 1364 1365 args := &workerArgs{ 1366 volumes: volumeAccessor, 1367 clock: clock, 1368 life: &mockLifecycleManager{ 1369 attachmentLife: attachmentLife, 1370 removeAttachments: removeAttachments, 1371 }, 1372 } 1373 worker := newStorageProvisioner(c, args) 1374 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1375 defer worker.Kill() 1376 1377 volumeAccessor.volumesWatcher.changes <- []string{volume.Id()} 1378 args.environ.watcher.changes <- struct{}{} 1379 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1380 MachineTag: machine.String(), 1381 AttachmentTag: volume.String(), 1382 }} 1383 waitChannel(c, removed, "waiting for attachment to be removed") 1384 c.Assert(detachVolumeTimes, gc.HasLen, 10) 1385 1386 // The first attempt should have been immediate: T0. 1387 c.Assert(detachVolumeTimes[0], gc.Equals, time.Time{}) 1388 1389 delays := make([]time.Duration, len(detachVolumeTimes)-1) 1390 for i := range detachVolumeTimes[1:] { 1391 delays[i] = detachVolumeTimes[i+1].Sub(detachVolumeTimes[i]) 1392 } 1393 c.Assert(delays, jc.DeepEquals, []time.Duration{ 1394 30 * time.Second, 1395 1 * time.Minute, 1396 2 * time.Minute, 1397 4 * time.Minute, 1398 8 * time.Minute, 1399 16 * time.Minute, 1400 30 * time.Minute, // ceiling reached 1401 30 * time.Minute, 1402 30 * time.Minute, 1403 }) 1404 1405 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 1406 {Tag: "volume-1", Status: "detaching", Info: "badness"}, // DetachVolumes 1407 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1408 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1409 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1410 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1411 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1412 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1413 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1414 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1415 {Tag: "volume-1", Status: "detached", Info: ""}, 1416 }) 1417 } 1418 1419 func (s *storageProvisionerSuite) TestDetachFilesystemsUnattached(c *gc.C) { 1420 removed := make(chan interface{}) 1421 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1422 defer close(removed) 1423 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 1424 MachineTag: "machine-0", 1425 AttachmentTag: "filesystem-0", 1426 }}) 1427 return make([]params.ErrorResult, len(ids)), nil 1428 } 1429 1430 args := &workerArgs{ 1431 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 1432 } 1433 worker := newStorageProvisioner(c, args) 1434 defer worker.Wait() 1435 defer worker.Kill() 1436 1437 args.filesystems.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1438 MachineTag: "machine-0", AttachmentTag: "filesystem-0", 1439 }} 1440 args.environ.watcher.changes <- struct{}{} 1441 waitChannel(c, removed, "waiting for attachment to be removed") 1442 } 1443 1444 func (s *storageProvisionerSuite) TestDetachFilesystems(c *gc.C) { 1445 var attached bool 1446 filesystemAttachmentInfoSet := make(chan interface{}) 1447 filesystemAccessor := newMockFilesystemAccessor() 1448 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 1449 close(filesystemAttachmentInfoSet) 1450 attached = true 1451 for _, a := range filesystemAttachments { 1452 id := params.MachineStorageId{ 1453 MachineTag: a.MachineTag, 1454 AttachmentTag: a.FilesystemTag, 1455 } 1456 filesystemAccessor.provisionedAttachments[id] = a 1457 } 1458 return make([]params.ErrorResult, len(filesystemAttachments)), nil 1459 } 1460 1461 expectedAttachmentIds := []params.MachineStorageId{{ 1462 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1463 }} 1464 1465 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1466 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1467 life := params.Alive 1468 if attached { 1469 life = params.Dying 1470 } 1471 return []params.LifeResult{{Life: life}}, nil 1472 } 1473 1474 detached := make(chan interface{}) 1475 s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]error, error) { 1476 c.Assert(args, gc.HasLen, 1) 1477 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 1478 c.Assert(args[0].Filesystem.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 1479 defer close(detached) 1480 return make([]error, len(args)), nil 1481 } 1482 1483 removed := make(chan interface{}) 1484 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1485 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1486 close(removed) 1487 return make([]params.ErrorResult, len(ids)), nil 1488 } 1489 1490 // filesystem-1 and machine-1 are provisioned. 1491 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 1492 FilesystemTag: "filesystem-1", 1493 Info: params.FilesystemInfo{ 1494 FilesystemId: "fs-id", 1495 }, 1496 } 1497 filesystemAccessor.provisionedMachines["machine-1"] = instance.Id("already-provisioned-1") 1498 1499 args := &workerArgs{ 1500 filesystems: filesystemAccessor, 1501 life: &mockLifecycleManager{ 1502 attachmentLife: attachmentLife, 1503 removeAttachments: removeAttachments, 1504 }, 1505 } 1506 worker := newStorageProvisioner(c, args) 1507 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1508 defer worker.Kill() 1509 1510 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1511 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1512 }} 1513 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 1514 args.environ.watcher.changes <- struct{}{} 1515 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 1516 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1517 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1518 }} 1519 waitChannel(c, detached, "waiting for filesystem to be detached") 1520 waitChannel(c, removed, "waiting for attachment to be removed") 1521 } 1522 1523 func (s *storageProvisionerSuite) TestDestroyVolumes(c *gc.C) { 1524 provisionedVolume := names.NewVolumeTag("1") 1525 unprovisionedVolume := names.NewVolumeTag("2") 1526 1527 volumeAccessor := newMockVolumeAccessor() 1528 volumeAccessor.provisionVolume(provisionedVolume) 1529 1530 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1531 results := make([]params.LifeResult, len(tags)) 1532 for i := range results { 1533 results[i].Life = params.Dead 1534 } 1535 return results, nil 1536 } 1537 1538 destroyedChan := make(chan interface{}, 1) 1539 s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) { 1540 destroyedChan <- volumeIds 1541 return make([]error, len(volumeIds)), nil 1542 } 1543 1544 removedChan := make(chan interface{}, 1) 1545 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1546 removedChan <- tags 1547 return make([]params.ErrorResult, len(tags)), nil 1548 } 1549 1550 args := &workerArgs{ 1551 volumes: volumeAccessor, 1552 life: &mockLifecycleManager{ 1553 life: life, 1554 remove: remove, 1555 }, 1556 } 1557 worker := newStorageProvisioner(c, args) 1558 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1559 defer worker.Kill() 1560 1561 volumeAccessor.volumesWatcher.changes <- []string{ 1562 provisionedVolume.Id(), 1563 unprovisionedVolume.Id(), 1564 } 1565 args.environ.watcher.changes <- struct{}{} 1566 1567 // Both volumes should be removed; the provisioned one 1568 // should be deprovisioned first. 1569 1570 destroyed := waitChannel(c, destroyedChan, "waiting for volume to be deprovisioned") 1571 assertNoEvent(c, destroyedChan, "volumes deprovisioned") 1572 c.Assert(destroyed, jc.DeepEquals, []string{"vol-1"}) 1573 1574 var removed []names.Tag 1575 for len(removed) < 2 { 1576 tags := waitChannel(c, removedChan, "waiting for volumes to be removed").([]names.Tag) 1577 removed = append(removed, tags...) 1578 } 1579 c.Assert(removed, jc.SameContents, []names.Tag{provisionedVolume, unprovisionedVolume}) 1580 assertNoEvent(c, removedChan, "volumes removed") 1581 } 1582 1583 func (s *storageProvisionerSuite) TestDestroyVolumesRetry(c *gc.C) { 1584 volume := names.NewVolumeTag("1") 1585 volumeAccessor := newMockVolumeAccessor() 1586 volumeAccessor.provisionVolume(volume) 1587 1588 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1589 return []params.LifeResult{{Life: params.Dead}}, nil 1590 } 1591 1592 // mockFunc's After will progress the current time by the specified 1593 // duration and signal the channel immediately. 1594 clock := &mockClock{} 1595 var destroyVolumeTimes []time.Time 1596 1597 s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) { 1598 destroyVolumeTimes = append(destroyVolumeTimes, clock.Now()) 1599 if len(destroyVolumeTimes) < 10 { 1600 return []error{errors.New("badness")}, nil 1601 } 1602 return []error{nil}, nil 1603 } 1604 1605 removedChan := make(chan interface{}, 1) 1606 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1607 removedChan <- tags 1608 return make([]params.ErrorResult, len(tags)), nil 1609 } 1610 1611 args := &workerArgs{ 1612 volumes: volumeAccessor, 1613 clock: clock, 1614 life: &mockLifecycleManager{ 1615 life: life, 1616 remove: remove, 1617 }, 1618 } 1619 worker := newStorageProvisioner(c, args) 1620 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1621 defer worker.Kill() 1622 1623 volumeAccessor.volumesWatcher.changes <- []string{volume.Id()} 1624 args.environ.watcher.changes <- struct{}{} 1625 waitChannel(c, removedChan, "waiting for volume to be removed") 1626 c.Assert(destroyVolumeTimes, gc.HasLen, 10) 1627 1628 // The first attempt should have been immediate: T0. 1629 c.Assert(destroyVolumeTimes[0], gc.Equals, time.Time{}) 1630 1631 delays := make([]time.Duration, len(destroyVolumeTimes)-1) 1632 for i := range destroyVolumeTimes[1:] { 1633 delays[i] = destroyVolumeTimes[i+1].Sub(destroyVolumeTimes[i]) 1634 } 1635 c.Assert(delays, jc.DeepEquals, []time.Duration{ 1636 30 * time.Second, 1637 1 * time.Minute, 1638 2 * time.Minute, 1639 4 * time.Minute, 1640 8 * time.Minute, 1641 16 * time.Minute, 1642 30 * time.Minute, // ceiling reached 1643 30 * time.Minute, 1644 30 * time.Minute, 1645 }) 1646 1647 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 1648 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1649 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1650 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1651 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1652 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1653 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1654 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1655 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1656 {Tag: "volume-1", Status: "destroying", Info: "badness"}, 1657 }) 1658 } 1659 1660 func (s *storageProvisionerSuite) TestDestroyFilesystems(c *gc.C) { 1661 provisionedFilesystem := names.NewFilesystemTag("1") 1662 unprovisionedFilesystem := names.NewFilesystemTag("2") 1663 1664 filesystemAccessor := newMockFilesystemAccessor() 1665 filesystemAccessor.provisionFilesystem(provisionedFilesystem) 1666 1667 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1668 results := make([]params.LifeResult, len(tags)) 1669 for i := range results { 1670 results[i].Life = params.Dead 1671 } 1672 return results, nil 1673 } 1674 1675 removedChan := make(chan interface{}, 1) 1676 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1677 removedChan <- tags 1678 return make([]params.ErrorResult, len(tags)), nil 1679 } 1680 1681 args := &workerArgs{ 1682 filesystems: filesystemAccessor, 1683 life: &mockLifecycleManager{ 1684 life: life, 1685 remove: remove, 1686 }, 1687 } 1688 worker := newStorageProvisioner(c, args) 1689 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1690 defer worker.Kill() 1691 1692 filesystemAccessor.filesystemsWatcher.changes <- []string{ 1693 provisionedFilesystem.Id(), 1694 unprovisionedFilesystem.Id(), 1695 } 1696 args.environ.watcher.changes <- struct{}{} 1697 1698 // Both filesystems should be removed; the provisioned one 1699 // *should* be deprovisioned first, but we don't currently 1700 // have the ability to do so via the storage provider API. 1701 1702 var removed []names.Tag 1703 for len(removed) < 2 { 1704 tags := waitChannel(c, removedChan, "waiting for filesystems to be removed").([]names.Tag) 1705 removed = append(removed, tags...) 1706 } 1707 c.Assert(removed, jc.SameContents, []names.Tag{provisionedFilesystem, unprovisionedFilesystem}) 1708 assertNoEvent(c, removedChan, "filesystems removed") 1709 } 1710 1711 func newStorageProvisioner(c *gc.C, args *workerArgs) worker.Worker { 1712 if args == nil { 1713 args = &workerArgs{} 1714 } 1715 var storageDir string 1716 switch args.scope.(type) { 1717 case names.MachineTag: 1718 storageDir = "storage-dir" 1719 case names.ModelTag: 1720 case nil: 1721 args.scope = coretesting.ModelTag 1722 } 1723 if args.volumes == nil { 1724 args.volumes = newMockVolumeAccessor() 1725 } 1726 if args.filesystems == nil { 1727 args.filesystems = newMockFilesystemAccessor() 1728 } 1729 if args.life == nil { 1730 args.life = &mockLifecycleManager{} 1731 } 1732 if args.environ == nil { 1733 args.environ = newMockModelAccessor(c) 1734 } 1735 if args.machines == nil { 1736 args.machines = newMockMachineAccessor(c) 1737 } 1738 if args.clock == nil { 1739 args.clock = &mockClock{} 1740 } 1741 if args.statusSetter == nil { 1742 args.statusSetter = &mockStatusSetter{} 1743 } 1744 worker, err := storageprovisioner.NewStorageProvisioner(storageprovisioner.Config{ 1745 Scope: args.scope, 1746 StorageDir: storageDir, 1747 Volumes: args.volumes, 1748 Filesystems: args.filesystems, 1749 Life: args.life, 1750 Environ: args.environ, 1751 Machines: args.machines, 1752 Status: args.statusSetter, 1753 Clock: args.clock, 1754 }) 1755 c.Assert(err, jc.ErrorIsNil) 1756 return worker 1757 } 1758 1759 type workerArgs struct { 1760 scope names.Tag 1761 volumes *mockVolumeAccessor 1762 filesystems *mockFilesystemAccessor 1763 life *mockLifecycleManager 1764 environ *mockModelAccessor 1765 machines *mockMachineAccessor 1766 clock clock.Clock 1767 statusSetter *mockStatusSetter 1768 } 1769 1770 func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} { 1771 select { 1772 case v := <-ch: 1773 return v 1774 case <-time.After(coretesting.LongWait): 1775 c.Fatalf("timed out " + activity) 1776 panic("unreachable") 1777 } 1778 } 1779 1780 func assertNoEvent(c *gc.C, ch <-chan interface{}, event string) { 1781 select { 1782 case <-ch: 1783 c.Fatalf("unexpected " + event) 1784 case <-time.After(coretesting.ShortWait): 1785 } 1786 }