github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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 stdcontext "context" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names/v5" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/worker/v3" 16 "github.com/juju/worker/v3/workertest" 17 gc "gopkg.in/check.v1" 18 19 "github.com/juju/juju/core/life" 20 "github.com/juju/juju/core/watcher" 21 "github.com/juju/juju/environs/context" 22 "github.com/juju/juju/rpc/params" 23 "github.com/juju/juju/storage" 24 coretesting "github.com/juju/juju/testing" 25 "github.com/juju/juju/worker/storageprovisioner" 26 ) 27 28 type storageProvisionerSuite struct { 29 coretesting.BaseSuite 30 provider *dummyProvider 31 registry storage.ProviderRegistry 32 managedFilesystemSource *mockManagedFilesystemSource 33 } 34 35 var _ = gc.Suite(&storageProvisionerSuite{}) 36 37 func (s *storageProvisionerSuite) SetUpTest(c *gc.C) { 38 s.BaseSuite.SetUpTest(c) 39 s.provider = &dummyProvider{dynamic: true} 40 s.registry = storage.StaticProviderRegistry{ 41 map[storage.ProviderType]storage.Provider{ 42 "dummy": s.provider, 43 }, 44 } 45 46 s.managedFilesystemSource = nil 47 s.PatchValue( 48 storageprovisioner.NewManagedFilesystemSource, 49 func( 50 blockDevices map[names.VolumeTag]storage.BlockDevice, 51 filesystems map[names.FilesystemTag]storage.Filesystem, 52 ) storage.FilesystemSource { 53 s.managedFilesystemSource = &mockManagedFilesystemSource{ 54 blockDevices: blockDevices, 55 filesystems: filesystems, 56 } 57 return s.managedFilesystemSource 58 }, 59 ) 60 s.PatchValue(storageprovisioner.DefaultDependentChangesTimeout, 10*time.Millisecond) 61 } 62 63 func (s *storageProvisionerSuite) TestStartStop(c *gc.C) { 64 worker, err := storageprovisioner.NewStorageProvisioner(storageprovisioner.Config{ 65 Scope: coretesting.ModelTag, 66 Volumes: newMockVolumeAccessor(), 67 Filesystems: newMockFilesystemAccessor(), 68 Life: &mockLifecycleManager{}, 69 Registry: s.registry, 70 Machines: newMockMachineAccessor(c), 71 Status: &mockStatusSetter{}, 72 Clock: &mockClock{}, 73 Logger: loggo.GetLogger("test"), 74 CloudCallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext { 75 return context.NewEmptyCloudCallContext() 76 }, 77 }) 78 c.Assert(err, jc.ErrorIsNil) 79 80 worker.Kill() 81 c.Assert(worker.Wait(), gc.IsNil) 82 } 83 84 func (s *storageProvisionerSuite) TestInvalidConfig(c *gc.C) { 85 _, err := storageprovisioner.NewStorageProvisioner(almostValidConfig()) 86 c.Check(err, jc.Satisfies, errors.IsNotValid) 87 } 88 89 func (s *storageProvisionerSuite) TestVolumeAdded(c *gc.C) { 90 expectedVolumes := []params.Volume{{ 91 VolumeTag: "volume-1", 92 Info: params.VolumeInfo{ 93 VolumeId: "id-1", 94 HardwareId: "serial-1", 95 Size: 1024, 96 Persistent: true, 97 }, 98 }, { 99 VolumeTag: "volume-2", 100 Info: params.VolumeInfo{ 101 VolumeId: "id-2", 102 HardwareId: "serial-2", 103 Size: 1024, 104 }, 105 }} 106 expectedVolumeAttachments := []params.VolumeAttachment{{ 107 VolumeTag: "volume-1", 108 MachineTag: "machine-1", 109 Info: params.VolumeAttachmentInfo{ 110 DeviceName: "/dev/sda1", 111 ReadOnly: true, 112 }, 113 }, { 114 VolumeTag: "volume-2", 115 MachineTag: "machine-1", 116 Info: params.VolumeAttachmentInfo{ 117 DeviceName: "/dev/sda2", 118 }, 119 }} 120 121 volumeInfoSet := make(chan interface{}) 122 volumeAccessor := newMockVolumeAccessor() 123 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 124 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 125 defer close(volumeInfoSet) 126 c.Assert(volumes, jc.SameContents, expectedVolumes) 127 return nil, nil 128 } 129 130 volumeAttachmentInfoSet := make(chan interface{}) 131 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 132 defer close(volumeAttachmentInfoSet) 133 c.Assert(volumeAttachments, jc.SameContents, expectedVolumeAttachments) 134 return nil, nil 135 } 136 volumeAttachmentPlansCreate := make(chan interface{}) 137 volumeAccessor.createVolumeAttachmentPlans = func(volumeAttachmentPlans []params.VolumeAttachmentPlan) ([]params.ErrorResult, error) { 138 defer close(volumeAttachmentPlansCreate) 139 return make([]params.ErrorResult, len(volumeAttachmentPlans)), nil 140 } 141 142 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 143 worker := newStorageProvisioner(c, args) 144 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 145 defer worker.Kill() 146 147 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 148 MachineTag: "machine-1", AttachmentTag: "volume-1", 149 }, { 150 MachineTag: "machine-1", AttachmentTag: "volume-2", 151 }} 152 assertNoEvent(c, volumeAttachmentPlansCreate, "volume attachment plans set") 153 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set") 154 // The worker should create volumes according to ids "1" and "2". 155 volumeAccessor.volumesWatcher.changes <- []string{"1", "2"} 156 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 157 waitChannel(c, volumeAttachmentPlansCreate, "waiting for volume attachment plans to be set") 158 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 159 } 160 161 func (s *storageProvisionerSuite) TestCreateVolumeCreatesAttachment(c *gc.C) { 162 volumeAccessor := newMockVolumeAccessor() 163 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 164 165 volumeAttachmentInfoSet := make(chan interface{}) 166 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 167 defer close(volumeAttachmentInfoSet) 168 return make([]params.ErrorResult, len(volumeAttachments)), nil 169 } 170 volumeAttachmentPlansCreate := make(chan interface{}) 171 volumeAccessor.createVolumeAttachmentPlans = func(volumeAttachmentPlans []params.VolumeAttachmentPlan) ([]params.ErrorResult, error) { 172 defer close(volumeAttachmentPlansCreate) 173 return make([]params.ErrorResult, len(volumeAttachmentPlans)), nil 174 } 175 176 s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 177 volumeAccessor.provisionedAttachments[params.MachineStorageId{ 178 MachineTag: args[0].Attachment.Machine.String(), 179 AttachmentTag: args[0].Attachment.Volume.String(), 180 }] = params.VolumeAttachment{ 181 VolumeTag: args[0].Attachment.Volume.String(), 182 MachineTag: args[0].Attachment.Machine.String(), 183 } 184 return []storage.CreateVolumesResult{{ 185 Volume: &storage.Volume{ 186 Tag: args[0].Tag, 187 VolumeInfo: storage.VolumeInfo{ 188 VolumeId: "vol-ume", 189 }, 190 }, 191 VolumeAttachment: &storage.VolumeAttachment{ 192 Volume: args[0].Attachment.Volume, 193 Machine: args[0].Attachment.Machine, 194 }, 195 }}, nil 196 } 197 198 attachVolumesCalled := make(chan interface{}) 199 s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 200 defer close(attachVolumesCalled) 201 return nil, errors.New("should not be called") 202 } 203 204 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 205 worker := newStorageProvisioner(c, args) 206 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 207 defer worker.Kill() 208 209 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 210 MachineTag: "machine-1", AttachmentTag: "volume-1", 211 }} 212 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment set") 213 214 // The worker should create volumes according to ids "1". 215 volumeAccessor.volumesWatcher.changes <- []string{"1"} 216 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 217 assertNoEvent(c, attachVolumesCalled, "AttachVolumes called") 218 } 219 220 func (s *storageProvisionerSuite) TestCreateVolumeRetry(c *gc.C) { 221 volumeInfoSet := make(chan interface{}) 222 volumeAccessor := newMockVolumeAccessor() 223 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 224 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 225 defer close(volumeInfoSet) 226 return make([]params.ErrorResult, len(volumes)), nil 227 } 228 229 // mockFunc's After will progress the current time by the specified 230 // duration and signal the channel immediately. 231 clock := &mockClock{} 232 var createVolumeTimes []time.Time 233 234 s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 235 createVolumeTimes = append(createVolumeTimes, clock.Now()) 236 if len(createVolumeTimes) < 10 { 237 return []storage.CreateVolumesResult{{Error: errors.New("badness")}}, nil 238 } 239 return []storage.CreateVolumesResult{{ 240 Volume: &storage.Volume{Tag: args[0].Tag}, 241 }}, nil 242 } 243 244 args := &workerArgs{volumes: volumeAccessor, clock: clock, registry: s.registry} 245 worker := newStorageProvisioner(c, args) 246 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 247 defer worker.Kill() 248 249 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 250 MachineTag: "machine-1", AttachmentTag: "volume-1", 251 }} 252 volumeAccessor.volumesWatcher.changes <- []string{"1"} 253 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 254 c.Assert(createVolumeTimes, gc.HasLen, 10) 255 256 // The first attempt should have been immediate: T0. 257 c.Assert(createVolumeTimes[0], gc.Equals, time.Time{}) 258 259 delays := make([]time.Duration, len(createVolumeTimes)-1) 260 for i := range createVolumeTimes[1:] { 261 delays[i] = createVolumeTimes[i+1].Sub(createVolumeTimes[i]) 262 } 263 c.Assert(delays, jc.DeepEquals, []time.Duration{ 264 30 * time.Second, 265 1 * time.Minute, 266 2 * time.Minute, 267 4 * time.Minute, 268 8 * time.Minute, 269 16 * time.Minute, 270 30 * time.Minute, // ceiling reached 271 30 * time.Minute, 272 30 * time.Minute, 273 }) 274 275 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 276 {Tag: "volume-1", Status: "pending", Info: "badness"}, 277 {Tag: "volume-1", Status: "pending", Info: "badness"}, 278 {Tag: "volume-1", Status: "pending", Info: "badness"}, 279 {Tag: "volume-1", Status: "pending", Info: "badness"}, 280 {Tag: "volume-1", Status: "pending", Info: "badness"}, 281 {Tag: "volume-1", Status: "pending", Info: "badness"}, 282 {Tag: "volume-1", Status: "pending", Info: "badness"}, 283 {Tag: "volume-1", Status: "pending", Info: "badness"}, 284 {Tag: "volume-1", Status: "pending", Info: "badness"}, 285 {Tag: "volume-1", Status: "attaching", Info: ""}, 286 }) 287 } 288 289 func (s *storageProvisionerSuite) TestCreateFilesystemRetry(c *gc.C) { 290 filesystemInfoSet := make(chan interface{}) 291 filesystemAccessor := newMockFilesystemAccessor() 292 filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 293 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 294 defer close(filesystemInfoSet) 295 return make([]params.ErrorResult, len(filesystems)), nil 296 } 297 298 // mockFunc's After will progress the current time by the specified 299 // duration and signal the channel immediately. 300 clock := &mockClock{} 301 var createFilesystemTimes []time.Time 302 303 s.provider.createFilesystemsFunc = func(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) { 304 createFilesystemTimes = append(createFilesystemTimes, clock.Now()) 305 if len(createFilesystemTimes) < 10 { 306 return []storage.CreateFilesystemsResult{{Error: errors.New("badness")}}, nil 307 } 308 return []storage.CreateFilesystemsResult{{ 309 Filesystem: &storage.Filesystem{Tag: args[0].Tag}, 310 }}, nil 311 } 312 313 args := &workerArgs{filesystems: filesystemAccessor, clock: clock, registry: s.registry} 314 worker := newStorageProvisioner(c, args) 315 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 316 defer worker.Kill() 317 318 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 319 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 320 }} 321 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 322 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 323 c.Assert(createFilesystemTimes, gc.HasLen, 10) 324 325 // The first attempt should have been immediate: T0. 326 c.Assert(createFilesystemTimes[0], gc.Equals, time.Time{}) 327 328 delays := make([]time.Duration, len(createFilesystemTimes)-1) 329 for i := range createFilesystemTimes[1:] { 330 delays[i] = createFilesystemTimes[i+1].Sub(createFilesystemTimes[i]) 331 } 332 c.Assert(delays, jc.DeepEquals, []time.Duration{ 333 30 * time.Second, 334 1 * time.Minute, 335 2 * time.Minute, 336 4 * time.Minute, 337 8 * time.Minute, 338 16 * time.Minute, 339 30 * time.Minute, // ceiling reached 340 30 * time.Minute, 341 30 * time.Minute, 342 }) 343 344 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 345 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 346 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 347 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 348 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 349 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 350 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 351 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 352 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 353 {Tag: "filesystem-1", Status: "pending", Info: "badness"}, 354 {Tag: "filesystem-1", Status: "attaching", Info: ""}, 355 }) 356 } 357 358 func (s *storageProvisionerSuite) TestFilesystemChannelReceivedOrder(c *gc.C) { 359 alreadyAttached := params.MachineStorageId{ 360 MachineTag: "machine-1", 361 AttachmentTag: "filesystem-1", 362 } 363 fileSystem := params.Filesystem{ 364 FilesystemTag: "filesystem-1", 365 Info: params.FilesystemInfo{ 366 FilesystemId: "1/1", 367 }, 368 } 369 370 filesystemAttachInfoSet := make(chan interface{}) 371 filesystemAccessor := newMockFilesystemAccessor() 372 filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 373 filesystemAccessor.provisionedFilesystems["filesystem-1"] = fileSystem 374 filesystemAccessor.provisionedMachinesFilesystems["filesystem-1"] = fileSystem 375 filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{ 376 MachineTag: "machine-1", 377 FilesystemTag: "filesystem-1", 378 Info: params.FilesystemAttachmentInfo{MountPoint: "/dev/sda1"}, 379 } 380 filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 381 defer close(filesystemAttachInfoSet) 382 return make([]params.ErrorResult, len(attachments)), nil 383 } 384 385 // mockFunc's After will progress the current time by the specified 386 // duration and signal the channel immediately. 387 clock := &mockClock{} 388 s.provider.createFilesystemsFunc = func(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) { 389 return []storage.CreateFilesystemsResult{{ 390 Filesystem: &storage.Filesystem{Tag: args[0].Tag}, 391 }}, nil 392 } 393 394 life := func(tags []names.Tag) ([]params.LifeResult, error) { 395 results := make([]params.LifeResult, len(tags)) 396 for i := range results { 397 results[i].Life = life.Alive 398 } 399 return results, nil 400 } 401 402 args := &workerArgs{ 403 filesystems: filesystemAccessor, 404 life: &mockLifecycleManager{ 405 life: life, 406 }, 407 clock: clock, 408 registry: s.registry, 409 } 410 worker := newStorageProvisioner(c, args) 411 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 412 defer worker.Kill() 413 414 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 415 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 416 }} 417 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 418 waitChannel(c, filesystemAttachInfoSet, "waiting for filesystem attach info to be set") 419 420 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 421 {Tag: "filesystem-1", Status: "attached", Info: ""}, 422 }) 423 } 424 425 func (s *storageProvisionerSuite) TestAttachVolumeRetry(c *gc.C) { 426 volumeInfoSet := make(chan interface{}) 427 volumeAccessor := newMockVolumeAccessor() 428 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 429 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 430 defer close(volumeInfoSet) 431 return make([]params.ErrorResult, len(volumes)), nil 432 } 433 volumeAttachmentInfoSet := make(chan interface{}) 434 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 435 defer close(volumeAttachmentInfoSet) 436 return make([]params.ErrorResult, len(volumeAttachments)), nil 437 } 438 439 // mockFunc's After will progress the current time by the specified 440 // duration and signal the channel immediately. 441 clock := &mockClock{} 442 var attachVolumeTimes []time.Time 443 444 s.provider.attachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 445 attachVolumeTimes = append(attachVolumeTimes, clock.Now()) 446 if len(attachVolumeTimes) < 10 { 447 return []storage.AttachVolumesResult{{Error: errors.New("badness")}}, nil 448 } 449 return []storage.AttachVolumesResult{{ 450 VolumeAttachment: &storage.VolumeAttachment{ 451 args[0].Volume, 452 args[0].Machine, 453 storage.VolumeAttachmentInfo{ 454 DeviceName: "/dev/sda1", 455 }, 456 }, 457 }}, nil 458 } 459 460 args := &workerArgs{volumes: volumeAccessor, clock: clock, registry: s.registry} 461 worker := newStorageProvisioner(c, args) 462 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 463 defer worker.Kill() 464 465 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 466 MachineTag: "machine-1", AttachmentTag: "volume-1", 467 }} 468 volumeAccessor.volumesWatcher.changes <- []string{"1"} 469 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 470 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 471 c.Assert(attachVolumeTimes, gc.HasLen, 10) 472 473 // The first attempt should have been immediate: T0. 474 c.Assert(attachVolumeTimes[0], gc.Equals, time.Time{}) 475 476 delays := make([]time.Duration, len(attachVolumeTimes)-1) 477 for i := range attachVolumeTimes[1:] { 478 delays[i] = attachVolumeTimes[i+1].Sub(attachVolumeTimes[i]) 479 } 480 c.Assert(delays, jc.DeepEquals, []time.Duration{ 481 30 * time.Second, 482 1 * time.Minute, 483 2 * time.Minute, 484 4 * time.Minute, 485 8 * time.Minute, 486 16 * time.Minute, 487 30 * time.Minute, // ceiling reached 488 30 * time.Minute, 489 30 * time.Minute, 490 }) 491 492 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 493 {Tag: "volume-1", Status: "attaching", Info: ""}, // CreateVolumes 494 {Tag: "volume-1", Status: "attaching", Info: "badness"}, // AttachVolumes 495 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 496 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 497 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 498 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 499 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 500 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 501 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 502 {Tag: "volume-1", Status: "attaching", Info: "badness"}, 503 {Tag: "volume-1", Status: "attached", Info: ""}, 504 }) 505 } 506 507 func (s *storageProvisionerSuite) TestAttachFilesystemRetry(c *gc.C) { 508 filesystemInfoSet := make(chan interface{}) 509 filesystemAccessor := newMockFilesystemAccessor() 510 filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 511 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 512 defer close(filesystemInfoSet) 513 return make([]params.ErrorResult, len(filesystems)), nil 514 } 515 filesystemAttachmentInfoSet := make(chan interface{}) 516 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 517 defer close(filesystemAttachmentInfoSet) 518 return make([]params.ErrorResult, len(filesystemAttachments)), nil 519 } 520 521 // mockFunc's After will progress the current time by the specified 522 // duration and signal the channel immediately. 523 clock := &mockClock{} 524 var attachFilesystemTimes []time.Time 525 526 s.provider.attachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]storage.AttachFilesystemsResult, error) { 527 attachFilesystemTimes = append(attachFilesystemTimes, clock.Now()) 528 if len(attachFilesystemTimes) < 10 { 529 return []storage.AttachFilesystemsResult{{Error: errors.New("badness")}}, nil 530 } 531 return []storage.AttachFilesystemsResult{{ 532 FilesystemAttachment: &storage.FilesystemAttachment{ 533 args[0].Filesystem, 534 args[0].Machine, 535 storage.FilesystemAttachmentInfo{ 536 Path: "/oh/over/there", 537 }, 538 }, 539 }}, nil 540 } 541 542 args := &workerArgs{filesystems: filesystemAccessor, clock: clock, registry: s.registry} 543 worker := newStorageProvisioner(c, args) 544 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 545 defer worker.Kill() 546 547 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 548 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 549 }} 550 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 551 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 552 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 553 c.Assert(attachFilesystemTimes, gc.HasLen, 10) 554 555 // The first attempt should have been immediate: T0. 556 c.Assert(attachFilesystemTimes[0], gc.Equals, time.Time{}) 557 558 delays := make([]time.Duration, len(attachFilesystemTimes)-1) 559 for i := range attachFilesystemTimes[1:] { 560 delays[i] = attachFilesystemTimes[i+1].Sub(attachFilesystemTimes[i]) 561 } 562 c.Assert(delays, jc.DeepEquals, []time.Duration{ 563 30 * time.Second, 564 1 * time.Minute, 565 2 * time.Minute, 566 4 * time.Minute, 567 8 * time.Minute, 568 16 * time.Minute, 569 30 * time.Minute, // ceiling reached 570 30 * time.Minute, 571 30 * time.Minute, 572 }) 573 574 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 575 {Tag: "filesystem-1", Status: "attaching", Info: ""}, // CreateFilesystems 576 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, // AttachFilesystems 577 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 578 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 579 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 580 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 581 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 582 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 583 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 584 {Tag: "filesystem-1", Status: "attaching", Info: "badness"}, 585 {Tag: "filesystem-1", Status: "attached", Info: ""}, 586 }) 587 } 588 589 func (s *storageProvisionerSuite) TestValidateVolumeParams(c *gc.C) { 590 volumeAccessor := newMockVolumeAccessor() 591 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 592 volumeAccessor.provisionedVolumes["volume-3"] = params.Volume{ 593 VolumeTag: "volume-3", 594 Info: params.VolumeInfo{VolumeId: "vol-ume"}, 595 } 596 597 var validateCalls int 598 validated := make(chan interface{}, 1) 599 s.provider.validateVolumeParamsFunc = func(p storage.VolumeParams) error { 600 validateCalls++ 601 validated <- p 602 switch p.Tag.String() { 603 case "volume-1", "volume-3": 604 return errors.New("something is wrong") 605 } 606 return nil 607 } 608 609 life := func(tags []names.Tag) ([]params.LifeResult, error) { 610 results := make([]params.LifeResult, len(tags)) 611 for i := range results { 612 switch tags[i].String() { 613 case "volume-3": 614 results[i].Life = life.Dead 615 default: 616 results[i].Life = life.Alive 617 } 618 } 619 return results, nil 620 } 621 622 createdVolumes := make(chan interface{}, 1) 623 s.provider.createVolumesFunc = func(args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 624 createdVolumes <- args 625 if len(args) != 1 { 626 return nil, errors.New("expected one argument") 627 } 628 return []storage.CreateVolumesResult{{ 629 Volume: &storage.Volume{Tag: args[0].Tag}, 630 }}, nil 631 } 632 633 destroyedVolumes := make(chan interface{}, 1) 634 s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) { 635 destroyedVolumes <- volumeIds 636 return make([]error, len(volumeIds)), nil 637 } 638 639 args := &workerArgs{ 640 volumes: volumeAccessor, 641 life: &mockLifecycleManager{ 642 life: life, 643 }, 644 registry: s.registry, 645 } 646 worker := newStorageProvisioner(c, args) 647 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 648 defer worker.Kill() 649 650 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 651 MachineTag: "machine-1", AttachmentTag: "volume-1", 652 }, { 653 MachineTag: "machine-1", AttachmentTag: "volume-2", 654 }} 655 volumeAccessor.volumesWatcher.changes <- []string{"1"} 656 waitChannel(c, validated, "waiting for volume parameter validation") 657 assertNoEvent(c, createdVolumes, "volume created") 658 c.Assert(validateCalls, gc.Equals, 1) 659 660 // Failure to create volume-1 should not block creation volume-2. 661 volumeAccessor.volumesWatcher.changes <- []string{"2"} 662 waitChannel(c, validated, "waiting for volume parameter validation") 663 createVolumeParams := waitChannel(c, createdVolumes, "volume created").([]storage.VolumeParams) 664 c.Assert(createVolumeParams, gc.HasLen, 1) 665 c.Assert(createVolumeParams[0].Tag.String(), gc.Equals, "volume-2") 666 c.Assert(validateCalls, gc.Equals, 2) 667 668 // destroying filesystems does not validate parameters 669 volumeAccessor.volumesWatcher.changes <- []string{"3"} 670 assertNoEvent(c, validated, "volume destruction params validated") 671 destroyVolumeParams := waitChannel(c, destroyedVolumes, "volume destroyed").([]string) 672 c.Assert(destroyVolumeParams, jc.DeepEquals, []string{"vol-ume"}) 673 c.Assert(validateCalls, gc.Equals, 2) // no change 674 675 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 676 {Tag: "volume-1", Status: "error", Info: "something is wrong"}, 677 {Tag: "volume-2", Status: "attaching"}, 678 // destroyed volumes are removed immediately, 679 // so there is no status update. 680 }) 681 } 682 683 func (s *storageProvisionerSuite) TestValidateFilesystemParams(c *gc.C) { 684 filesystemAccessor := newMockFilesystemAccessor() 685 filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 686 filesystemAccessor.provisionedFilesystems["filesystem-3"] = params.Filesystem{ 687 FilesystemTag: "filesystem-3", 688 Info: params.FilesystemInfo{FilesystemId: "fs-id"}, 689 } 690 691 var validateCalls int 692 validated := make(chan interface{}, 1) 693 s.provider.validateFilesystemParamsFunc = func(p storage.FilesystemParams) error { 694 validateCalls++ 695 validated <- p 696 switch p.Tag.String() { 697 case "filesystem-1", "filesystem-3": 698 return errors.New("something is wrong") 699 } 700 return nil 701 } 702 703 life := func(tags []names.Tag) ([]params.LifeResult, error) { 704 results := make([]params.LifeResult, len(tags)) 705 for i := range results { 706 switch tags[i].String() { 707 case "filesystem-3": 708 results[i].Life = life.Dead 709 default: 710 results[i].Life = life.Alive 711 } 712 } 713 return results, nil 714 } 715 716 createdFilesystems := make(chan interface{}, 1) 717 s.provider.createFilesystemsFunc = func(args []storage.FilesystemParams) ([]storage.CreateFilesystemsResult, error) { 718 createdFilesystems <- args 719 if len(args) != 1 { 720 return nil, errors.New("expected one argument") 721 } 722 return []storage.CreateFilesystemsResult{{ 723 Filesystem: &storage.Filesystem{Tag: args[0].Tag}, 724 }}, nil 725 } 726 727 destroyedFilesystems := make(chan interface{}, 1) 728 s.provider.destroyFilesystemsFunc = func(filesystemIds []string) ([]error, error) { 729 destroyedFilesystems <- filesystemIds 730 return make([]error, len(filesystemIds)), nil 731 } 732 733 args := &workerArgs{ 734 filesystems: filesystemAccessor, 735 life: &mockLifecycleManager{ 736 life: life, 737 }, 738 registry: s.registry, 739 } 740 worker := newStorageProvisioner(c, args) 741 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 742 defer worker.Kill() 743 744 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 745 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 746 }, { 747 MachineTag: "machine-1", AttachmentTag: "filesystem-2", 748 }} 749 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 750 waitChannel(c, validated, "waiting for filesystem parameter validation") 751 assertNoEvent(c, createdFilesystems, "filesystem created") 752 c.Assert(validateCalls, gc.Equals, 1) 753 754 // Failure to create filesystem-1 should not block creation filesystem-2. 755 filesystemAccessor.filesystemsWatcher.changes <- []string{"2"} 756 waitChannel(c, validated, "waiting for filesystem parameter validation") 757 createFilesystemParams := waitChannel(c, createdFilesystems, "filesystem created").([]storage.FilesystemParams) 758 c.Assert(createFilesystemParams, gc.HasLen, 1) 759 c.Assert(createFilesystemParams[0].Tag.String(), gc.Equals, "filesystem-2") 760 c.Assert(validateCalls, gc.Equals, 2) 761 762 // destroying filesystems does not validate parameters 763 filesystemAccessor.filesystemsWatcher.changes <- []string{"3"} 764 assertNoEvent(c, validated, "filesystem destruction params validated") 765 destroyFilesystemParams := waitChannel(c, destroyedFilesystems, "filesystem destroyed").([]string) 766 c.Assert(destroyFilesystemParams, jc.DeepEquals, []string{"fs-id"}) 767 c.Assert(validateCalls, gc.Equals, 2) // no change 768 769 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 770 {Tag: "filesystem-1", Status: "error", Info: "something is wrong"}, 771 {Tag: "filesystem-2", Status: "attaching"}, 772 // destroyed filesystems are removed immediately, 773 // so there is no status update. 774 }) 775 } 776 777 func (s *storageProvisionerSuite) TestFilesystemAdded(c *gc.C) { 778 expectedFilesystems := []params.Filesystem{{ 779 FilesystemTag: "filesystem-1", 780 Info: params.FilesystemInfo{ 781 FilesystemId: "id-1", 782 Size: 1024, 783 }, 784 }, { 785 FilesystemTag: "filesystem-2", 786 Info: params.FilesystemInfo{ 787 FilesystemId: "id-2", 788 Size: 1024, 789 }, 790 }} 791 792 filesystemInfoSet := make(chan interface{}) 793 filesystemAccessor := newMockFilesystemAccessor() 794 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 795 defer close(filesystemInfoSet) 796 c.Assert(filesystems, jc.SameContents, expectedFilesystems) 797 return nil, nil 798 } 799 800 args := &workerArgs{filesystems: filesystemAccessor, registry: s.registry} 801 worker := newStorageProvisioner(c, args) 802 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 803 defer worker.Kill() 804 805 // The worker should create filesystems according to ids "1" and "2". 806 filesystemAccessor.filesystemsWatcher.changes <- []string{"1", "2"} 807 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 808 } 809 810 func (s *storageProvisionerSuite) TestVolumeNeedsInstance(c *gc.C) { 811 volumeInfoSet := make(chan interface{}) 812 volumeAccessor := newMockVolumeAccessor() 813 volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) { 814 defer close(volumeInfoSet) 815 return nil, nil 816 } 817 volumeAccessor.setVolumeAttachmentInfo = func([]params.VolumeAttachment) ([]params.ErrorResult, error) { 818 return nil, nil 819 } 820 821 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 822 worker := newStorageProvisioner(c, args) 823 defer worker.Wait() 824 defer worker.Kill() 825 826 volumeAccessor.volumesWatcher.changes <- []string{needsInstanceVolumeId} 827 assertNoEvent(c, volumeInfoSet, "volume info set") 828 args.machines.instanceIds[names.NewMachineTag("1")] = "inst-id" 829 args.machines.watcher.changes <- struct{}{} 830 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 831 } 832 833 // TestVolumeIncoherent tests that we do not panic when observing 834 // a pending volume that has no attachments. We send a volume 835 // update for a volume that is alive and unprovisioned, but has 836 // no machine attachment. Such volumes are ignored by the storage 837 // provisioner. 838 // 839 // See: https://bugs.launchpad.net/juju/+bug/1732616 840 func (s *storageProvisionerSuite) TestVolumeIncoherent(c *gc.C) { 841 volumeAccessor := newMockVolumeAccessor() 842 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 843 worker := newStorageProvisioner(c, args) 844 defer workertest.CleanKill(c, worker) 845 846 // Send 3 times, because the channel has a buffer size of 1. 847 // The third send guarantees we've sent at least the 2nd one 848 // through, which means at least the 1st has been processed 849 // (and ignored). 850 for i := 0; i < 3; i++ { 851 volumeAccessor.volumesWatcher.changes <- []string{noAttachmentVolumeId} 852 } 853 } 854 855 func (s *storageProvisionerSuite) TestVolumeNonDynamic(c *gc.C) { 856 volumeInfoSet := make(chan interface{}) 857 volumeAccessor := newMockVolumeAccessor() 858 volumeAccessor.setVolumeInfo = func([]params.Volume) ([]params.ErrorResult, error) { 859 defer close(volumeInfoSet) 860 return nil, nil 861 } 862 863 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 864 worker := newStorageProvisioner(c, args) 865 defer worker.Wait() 866 defer worker.Kill() 867 868 // Volumes for non-dynamic providers should not be created. 869 s.provider.dynamic = false 870 volumeAccessor.volumesWatcher.changes <- []string{"1"} 871 assertNoEvent(c, volumeInfoSet, "volume info set") 872 } 873 874 func (s *storageProvisionerSuite) TestVolumeAttachmentAdded(c *gc.C) { 875 // We should get two volume attachments: 876 // - volume-1 to machine-1, because the volume and 877 // machine are provisioned, but the attachment is not. 878 // - volume-1 to machine-0, because the volume, 879 // machine, and attachment are provisioned, but 880 // in a previous session, so a reattachment is 881 // requested. 882 expectedVolumeAttachments := []params.VolumeAttachment{{ 883 VolumeTag: "volume-1", 884 MachineTag: "machine-1", 885 Info: params.VolumeAttachmentInfo{ 886 DeviceName: "/dev/sda1", 887 ReadOnly: true, 888 }, 889 }, { 890 VolumeTag: "volume-1", 891 MachineTag: "machine-0", 892 Info: params.VolumeAttachmentInfo{ 893 DeviceName: "/dev/sda1", 894 ReadOnly: true, 895 }, 896 }} 897 898 var allVolumeAttachments []params.VolumeAttachment 899 volumeAttachmentInfoSet := make(chan interface{}) 900 volumeAccessor := newMockVolumeAccessor() 901 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 902 allVolumeAttachments = append(allVolumeAttachments, volumeAttachments...) 903 volumeAttachmentInfoSet <- nil 904 return make([]params.ErrorResult, len(volumeAttachments)), nil 905 } 906 907 // volume-1, machine-0, and machine-1 are provisioned. 908 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 909 VolumeTag: "volume-1", 910 Info: params.VolumeInfo{ 911 VolumeId: "vol-123", 912 }, 913 } 914 volumeAccessor.provisionedMachines["machine-0"] = "already-provisioned-0" 915 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 916 917 // machine-0/volume-1 attachment is already created. 918 // We should see a reattachment. 919 alreadyAttached := params.MachineStorageId{ 920 MachineTag: "machine-0", 921 AttachmentTag: "volume-1", 922 } 923 volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{ 924 MachineTag: "machine-0", 925 VolumeTag: "volume-1", 926 } 927 928 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 929 worker := newStorageProvisioner(c, args) 930 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 931 defer worker.Kill() 932 933 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 934 MachineTag: "machine-1", AttachmentTag: "volume-1", 935 }, { 936 MachineTag: "machine-1", AttachmentTag: "volume-2", 937 }, { 938 MachineTag: "machine-2", AttachmentTag: "volume-1", 939 }, { 940 MachineTag: "machine-0", AttachmentTag: "volume-1", 941 }} 942 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 943 volumeAccessor.volumesWatcher.changes <- []string{"1"} 944 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 945 c.Assert(allVolumeAttachments, jc.SameContents, expectedVolumeAttachments) 946 947 // Reattachment should only happen once per session. 948 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 949 MachineTag: "machine-0", 950 AttachmentTag: "volume-1", 951 }} 952 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 953 } 954 955 func (s *storageProvisionerSuite) TestVolumeAttachmentNoStaticReattachment(c *gc.C) { 956 // Static storage should never be reattached. 957 s.provider.dynamic = false 958 959 volumeAttachmentInfoSet := make(chan interface{}) 960 volumeAccessor := newMockVolumeAccessor() 961 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 962 volumeAttachmentInfoSet <- nil 963 return make([]params.ErrorResult, len(volumeAttachments)), nil 964 } 965 966 // volume-1, machine-0, and machine-1 are provisioned. 967 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 968 VolumeTag: "volume-1", 969 Info: params.VolumeInfo{ 970 VolumeId: "vol-123", 971 }, 972 } 973 volumeAccessor.provisionedMachines["machine-0"] = "already-provisioned-0" 974 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 975 976 alreadyAttached := params.MachineStorageId{ 977 MachineTag: "machine-0", 978 AttachmentTag: "volume-1", 979 } 980 volumeAccessor.provisionedAttachments[alreadyAttached] = params.VolumeAttachment{ 981 MachineTag: "machine-0", 982 VolumeTag: "volume-1", 983 } 984 985 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 986 worker := newStorageProvisioner(c, args) 987 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 988 defer worker.Kill() 989 990 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 991 MachineTag: "machine-0", AttachmentTag: "volume-1", 992 }} 993 volumeAccessor.volumesWatcher.changes <- []string{"1"} 994 assertNoEvent(c, volumeAttachmentInfoSet, "volume attachment info set") 995 } 996 997 func (s *storageProvisionerSuite) TestFilesystemAttachmentAdded(c *gc.C) { 998 // We should only get a single filesystem attachment, because it is the 999 // only combination where both machine and filesystem are already 1000 // provisioned, and the attachmenti s not. 1001 // We should get two filesystem attachments: 1002 // - filesystem-1 to machine-1, because the filesystem and 1003 // machine are provisioned, but the attachment is not. 1004 // - filesystem-1 to machine-0, because the filesystem, 1005 // machine, and attachment are provisioned, but in a 1006 // previous session, so a reattachment is requested. 1007 expectedFilesystemAttachments := []params.FilesystemAttachment{{ 1008 FilesystemTag: "filesystem-1", 1009 MachineTag: "machine-1", 1010 Info: params.FilesystemAttachmentInfo{ 1011 MountPoint: "/srv/fs-123", 1012 }, 1013 }, { 1014 FilesystemTag: "filesystem-1", 1015 MachineTag: "machine-0", 1016 Info: params.FilesystemAttachmentInfo{ 1017 MountPoint: "/srv/fs-123", 1018 }, 1019 }} 1020 1021 var allFilesystemAttachments []params.FilesystemAttachment 1022 filesystemAttachmentInfoSet := make(chan interface{}) 1023 filesystemAccessor := newMockFilesystemAccessor() 1024 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 1025 allFilesystemAttachments = append(allFilesystemAttachments, filesystemAttachments...) 1026 filesystemAttachmentInfoSet <- nil 1027 return make([]params.ErrorResult, len(filesystemAttachments)), nil 1028 } 1029 1030 // filesystem-1 and machine-1 are provisioned. 1031 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 1032 FilesystemTag: "filesystem-1", 1033 Info: params.FilesystemInfo{ 1034 FilesystemId: "fs-123", 1035 }, 1036 } 1037 filesystemAccessor.provisionedMachines["machine-0"] = "already-provisioned-0" 1038 filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1039 1040 // machine-0/filesystem-1 attachment is already created. 1041 // We should see a reattachment. 1042 alreadyAttached := params.MachineStorageId{ 1043 MachineTag: "machine-0", 1044 AttachmentTag: "filesystem-1", 1045 } 1046 filesystemAccessor.provisionedAttachments[alreadyAttached] = params.FilesystemAttachment{ 1047 MachineTag: "machine-0", 1048 FilesystemTag: "filesystem-1", 1049 } 1050 1051 args := &workerArgs{filesystems: filesystemAccessor, registry: s.registry} 1052 worker := newStorageProvisioner(c, args) 1053 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1054 defer worker.Kill() 1055 1056 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1057 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1058 }, { 1059 MachineTag: "machine-1", AttachmentTag: "filesystem-2", 1060 }, { 1061 MachineTag: "machine-2", AttachmentTag: "filesystem-1", 1062 }, { 1063 MachineTag: "machine-0", AttachmentTag: "filesystem-1", 1064 }} 1065 assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set") 1066 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 1067 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 1068 c.Assert(allFilesystemAttachments, jc.SameContents, expectedFilesystemAttachments) 1069 1070 // Reattachment should only happen once per session. 1071 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1072 MachineTag: "machine-0", 1073 AttachmentTag: "filesystem-1", 1074 }} 1075 assertNoEvent(c, filesystemAttachmentInfoSet, "filesystem attachment info set") 1076 } 1077 1078 func (s *storageProvisionerSuite) TestCreateVolumeBackedFilesystem(c *gc.C) { 1079 filesystemInfoSet := make(chan interface{}) 1080 filesystemAccessor := newMockFilesystemAccessor() 1081 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 1082 filesystemInfoSet <- filesystems 1083 return nil, nil 1084 } 1085 1086 args := &workerArgs{ 1087 scope: names.NewMachineTag("0"), 1088 filesystems: filesystemAccessor, 1089 registry: s.registry, 1090 } 1091 worker := newStorageProvisioner(c, args) 1092 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1093 defer worker.Kill() 1094 1095 args.volumes.blockDevices[params.MachineStorageId{ 1096 MachineTag: "machine-0", 1097 AttachmentTag: "volume-0-0", 1098 }] = storage.BlockDevice{ 1099 DeviceName: "xvdf1", 1100 Size: 123, 1101 } 1102 filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0", "0/1"} 1103 1104 // Only the block device for volume 0/0 is attached at the moment, 1105 // so only the corresponding filesystem will be created. 1106 filesystemInfo := waitChannel( 1107 c, filesystemInfoSet, 1108 "waiting for filesystem info to be set", 1109 ).([]params.Filesystem) 1110 c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{ 1111 FilesystemTag: "filesystem-0-0", 1112 Info: params.FilesystemInfo{ 1113 FilesystemId: "xvdf1", 1114 Size: 123, 1115 }, 1116 }}) 1117 1118 // If we now attach the block device for volume 0/1 and trigger the 1119 // notification, then the storage provisioner will wake up and create 1120 // the filesystem. 1121 args.volumes.blockDevices[params.MachineStorageId{ 1122 MachineTag: "machine-0", 1123 AttachmentTag: "volume-0-1", 1124 }] = storage.BlockDevice{ 1125 DeviceName: "xvdf2", 1126 Size: 246, 1127 } 1128 args.volumes.blockDevicesWatcher.changes <- struct{}{} 1129 filesystemInfo = waitChannel( 1130 c, filesystemInfoSet, 1131 "waiting for filesystem info to be set", 1132 ).([]params.Filesystem) 1133 c.Assert(filesystemInfo, jc.DeepEquals, []params.Filesystem{{ 1134 FilesystemTag: "filesystem-0-1", 1135 Info: params.FilesystemInfo{ 1136 FilesystemId: "xvdf2", 1137 Size: 246, 1138 }, 1139 }}) 1140 } 1141 1142 func (s *storageProvisionerSuite) TestAttachVolumeBackedFilesystem(c *gc.C) { 1143 infoSet := make(chan interface{}) 1144 filesystemAccessor := newMockFilesystemAccessor() 1145 filesystemAccessor.setFilesystemAttachmentInfo = func(attachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 1146 infoSet <- attachments 1147 return make([]params.ErrorResult, len(attachments)), nil 1148 } 1149 1150 args := &workerArgs{ 1151 scope: names.NewMachineTag("0"), 1152 filesystems: filesystemAccessor, 1153 registry: s.registry, 1154 } 1155 worker := newStorageProvisioner(c, args) 1156 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1157 defer worker.Kill() 1158 1159 filesystemAccessor.provisionedFilesystems["filesystem-0-0"] = params.Filesystem{ 1160 FilesystemTag: "filesystem-0-0", 1161 VolumeTag: "volume-0-0", 1162 Info: params.FilesystemInfo{ 1163 FilesystemId: "whatever", 1164 Size: 123, 1165 }, 1166 } 1167 filesystemAccessor.provisionedMachines["machine-0"] = "already-provisioned-0" 1168 1169 args.volumes.blockDevices[params.MachineStorageId{ 1170 MachineTag: "machine-0", 1171 AttachmentTag: "volume-0-0", 1172 }] = storage.BlockDevice{ 1173 DeviceName: "xvdf1", 1174 Size: 123, 1175 } 1176 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1177 MachineTag: "machine-0", 1178 AttachmentTag: "filesystem-0-0", 1179 }} 1180 filesystemAccessor.filesystemsWatcher.changes <- []string{"0/0"} 1181 1182 info := waitChannel( 1183 c, infoSet, "waiting for filesystem attachment info to be set", 1184 ).([]params.FilesystemAttachment) 1185 c.Assert(info, jc.DeepEquals, []params.FilesystemAttachment{{ 1186 FilesystemTag: "filesystem-0-0", 1187 MachineTag: "machine-0", 1188 Info: params.FilesystemAttachmentInfo{ 1189 MountPoint: "/mnt/xvdf1", 1190 ReadOnly: true, 1191 }, 1192 }}) 1193 1194 // Update the UUID of the block device and check attachment update. 1195 args.volumes.blockDevices[params.MachineStorageId{ 1196 MachineTag: "machine-0", 1197 AttachmentTag: "volume-0-0", 1198 }] = storage.BlockDevice{ 1199 DeviceName: "xvdf1", 1200 Size: 123, 1201 UUID: "deadbeaf", 1202 } 1203 s.managedFilesystemSource.attachedFilesystems = make(chan interface{}, 1) 1204 args.volumes.blockDevicesWatcher.changes <- struct{}{} 1205 attachInfo := waitChannel( 1206 c, s.managedFilesystemSource.attachedFilesystems, 1207 "waiting for filesystem attachements", 1208 ).([]storage.AttachFilesystemsResult) 1209 c.Assert(attachInfo, jc.DeepEquals, []storage.AttachFilesystemsResult{{ 1210 FilesystemAttachment: &storage.FilesystemAttachment{ 1211 Filesystem: names.NewFilesystemTag("0/0"), 1212 FilesystemAttachmentInfo: storage.FilesystemAttachmentInfo{ 1213 Path: "/mnt/xvdf1", 1214 ReadOnly: true, 1215 }, 1216 }, 1217 }}) 1218 1219 } 1220 1221 func (s *storageProvisionerSuite) TestResourceTags(c *gc.C) { 1222 volumeInfoSet := make(chan interface{}) 1223 volumeAccessor := newMockVolumeAccessor() 1224 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1225 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 1226 defer close(volumeInfoSet) 1227 return nil, nil 1228 } 1229 1230 filesystemInfoSet := make(chan interface{}) 1231 filesystemAccessor := newMockFilesystemAccessor() 1232 filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1233 filesystemAccessor.setFilesystemInfo = func(filesystems []params.Filesystem) ([]params.ErrorResult, error) { 1234 defer close(filesystemInfoSet) 1235 return nil, nil 1236 } 1237 1238 var volumeSource dummyVolumeSource 1239 s.provider.volumeSourceFunc = func(sourceConfig *storage.Config) (storage.VolumeSource, error) { 1240 return &volumeSource, nil 1241 } 1242 1243 var filesystemSource dummyFilesystemSource 1244 s.provider.filesystemSourceFunc = func(sourceConfig *storage.Config) (storage.FilesystemSource, error) { 1245 return &filesystemSource, nil 1246 } 1247 1248 args := &workerArgs{ 1249 volumes: volumeAccessor, 1250 filesystems: filesystemAccessor, 1251 registry: s.registry, 1252 } 1253 worker := newStorageProvisioner(c, args) 1254 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1255 defer worker.Kill() 1256 1257 volumeAccessor.volumesWatcher.changes <- []string{"1"} 1258 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 1259 waitChannel(c, volumeInfoSet, "waiting for volume info to be set") 1260 waitChannel(c, filesystemInfoSet, "waiting for filesystem info to be set") 1261 c.Assert(volumeSource.createVolumesArgs, jc.DeepEquals, [][]storage.VolumeParams{{{ 1262 Tag: names.NewVolumeTag("1"), 1263 Size: 1024, 1264 Provider: "dummy", 1265 Attributes: map[string]interface{}{"persistent": true}, 1266 ResourceTags: map[string]string{"very": "fancy"}, 1267 Attachment: &storage.VolumeAttachmentParams{ 1268 Volume: names.NewVolumeTag("1"), 1269 AttachmentParams: storage.AttachmentParams{ 1270 Machine: names.NewMachineTag("1"), 1271 Provider: "dummy", 1272 InstanceId: "already-provisioned-1", 1273 ReadOnly: true, 1274 }, 1275 }, 1276 }}}) 1277 c.Assert(filesystemSource.createFilesystemsArgs, jc.DeepEquals, [][]storage.FilesystemParams{{{ 1278 Tag: names.NewFilesystemTag("1"), 1279 Size: 1024, 1280 Provider: "dummy", 1281 ResourceTags: map[string]string{"very": "fancy"}, 1282 }}}) 1283 } 1284 1285 func (s *storageProvisionerSuite) TestSetVolumeInfoErrorStopsWorker(c *gc.C) { 1286 volumeAccessor := newMockVolumeAccessor() 1287 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1288 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 1289 return nil, errors.New("belly up") 1290 } 1291 1292 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 1293 worker := newStorageProvisioner(c, args) 1294 defer worker.Wait() 1295 defer worker.Kill() 1296 1297 done := make(chan interface{}) 1298 go func() { 1299 defer close(done) 1300 err := worker.Wait() 1301 c.Assert(err, gc.ErrorMatches, "creating volumes: publishing volumes to state: belly up") 1302 }() 1303 1304 args.volumes.volumesWatcher.changes <- []string{"1"} 1305 waitChannel(c, done, "waiting for worker to exit") 1306 } 1307 1308 func (s *storageProvisionerSuite) TestSetVolumeInfoErrorResultDoesNotStopWorker(c *gc.C) { 1309 volumeAccessor := newMockVolumeAccessor() 1310 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1311 volumeAccessor.setVolumeInfo = func(volumes []params.Volume) ([]params.ErrorResult, error) { 1312 return []params.ErrorResult{{Error: ¶ms.Error{Message: "message", Code: "code"}}}, nil 1313 } 1314 1315 args := &workerArgs{volumes: volumeAccessor, registry: s.registry} 1316 worker := newStorageProvisioner(c, args) 1317 defer func() { 1318 err := worker.Wait() 1319 c.Assert(err, jc.ErrorIsNil) 1320 }() 1321 defer worker.Kill() 1322 1323 done := make(chan interface{}) 1324 go func() { 1325 defer close(done) 1326 worker.Wait() 1327 }() 1328 1329 args.volumes.volumesWatcher.changes <- []string{"1"} 1330 assertNoEvent(c, done, "worker exited") 1331 } 1332 1333 func (s *storageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) { 1334 removed := make(chan interface{}) 1335 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1336 defer close(removed) 1337 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 1338 MachineTag: "machine-0", 1339 AttachmentTag: "volume-0", 1340 }}) 1341 return make([]params.ErrorResult, len(ids)), nil 1342 } 1343 1344 args := &workerArgs{ 1345 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 1346 registry: s.registry, 1347 } 1348 worker := newStorageProvisioner(c, args) 1349 defer worker.Wait() 1350 defer worker.Kill() 1351 1352 args.volumes.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1353 MachineTag: "machine-0", AttachmentTag: "volume-0", 1354 }} 1355 waitChannel(c, removed, "waiting for attachment to be removed") 1356 } 1357 1358 func (s *storageProvisionerSuite) TestDetachVolumes(c *gc.C) { 1359 var attached bool 1360 volumeAttachmentInfoSet := make(chan interface{}) 1361 volumeAccessor := newMockVolumeAccessor() 1362 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 1363 close(volumeAttachmentInfoSet) 1364 attached = true 1365 for _, a := range volumeAttachments { 1366 id := params.MachineStorageId{ 1367 MachineTag: a.MachineTag, 1368 AttachmentTag: a.VolumeTag, 1369 } 1370 volumeAccessor.provisionedAttachments[id] = a 1371 } 1372 return make([]params.ErrorResult, len(volumeAttachments)), nil 1373 } 1374 1375 expectedAttachmentIds := []params.MachineStorageId{{ 1376 MachineTag: "machine-1", AttachmentTag: "volume-1", 1377 }} 1378 1379 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1380 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1381 value := life.Alive 1382 if attached { 1383 value = life.Dying 1384 } 1385 return []params.LifeResult{{Life: value}}, nil 1386 } 1387 1388 detached := make(chan interface{}) 1389 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) { 1390 c.Assert(args, gc.HasLen, 1) 1391 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 1392 c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 1393 defer close(detached) 1394 return make([]error, len(args)), nil 1395 } 1396 1397 removed := make(chan interface{}) 1398 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1399 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1400 close(removed) 1401 return make([]params.ErrorResult, len(ids)), nil 1402 } 1403 1404 // volume-1 and machine-1 are provisioned. 1405 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 1406 VolumeTag: "volume-1", 1407 Info: params.VolumeInfo{ 1408 VolumeId: "vol-123", 1409 }, 1410 } 1411 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1412 1413 args := &workerArgs{ 1414 volumes: volumeAccessor, 1415 life: &mockLifecycleManager{ 1416 attachmentLife: attachmentLife, 1417 removeAttachments: removeAttachments, 1418 }, 1419 registry: s.registry, 1420 } 1421 worker := newStorageProvisioner(c, args) 1422 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1423 defer worker.Kill() 1424 1425 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1426 MachineTag: "machine-1", AttachmentTag: "volume-1", 1427 }} 1428 volumeAccessor.volumesWatcher.changes <- []string{"1"} 1429 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 1430 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1431 MachineTag: "machine-1", AttachmentTag: "volume-1", 1432 }} 1433 waitChannel(c, detached, "waiting for volume to be detached") 1434 waitChannel(c, removed, "waiting for attachment to be removed") 1435 } 1436 1437 func (s *storageProvisionerSuite) TestDetachVolumesRetry(c *gc.C) { 1438 machine := names.NewMachineTag("1") 1439 volume := names.NewVolumeTag("1") 1440 attachmentId := params.MachineStorageId{ 1441 MachineTag: machine.String(), 1442 AttachmentTag: volume.String(), 1443 } 1444 volumeAccessor := newMockVolumeAccessor() 1445 volumeAccessor.provisionedAttachments[attachmentId] = params.VolumeAttachment{ 1446 MachineTag: machine.String(), 1447 VolumeTag: volume.String(), 1448 } 1449 volumeAccessor.provisionedVolumes[volume.String()] = params.Volume{ 1450 VolumeTag: volume.String(), 1451 Info: params.VolumeInfo{ 1452 VolumeId: "vol-123", 1453 }, 1454 } 1455 volumeAccessor.provisionedMachines[machine.String()] = "already-provisioned-1" 1456 1457 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1458 return []params.LifeResult{{Life: life.Dying}}, nil 1459 } 1460 1461 // mockFunc's After will progress the current time by the specified 1462 // duration and signal the channel immediately. 1463 clock := &mockClock{} 1464 var detachVolumeTimes []time.Time 1465 1466 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) { 1467 detachVolumeTimes = append(detachVolumeTimes, clock.Now()) 1468 if len(detachVolumeTimes) < 10 { 1469 return []error{errors.New("badness")}, nil 1470 } 1471 return []error{nil}, nil 1472 } 1473 1474 removed := make(chan interface{}) 1475 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1476 close(removed) 1477 return make([]params.ErrorResult, len(ids)), nil 1478 } 1479 1480 args := &workerArgs{ 1481 volumes: volumeAccessor, 1482 clock: clock, 1483 life: &mockLifecycleManager{ 1484 attachmentLife: attachmentLife, 1485 removeAttachments: removeAttachments, 1486 }, 1487 registry: s.registry, 1488 } 1489 worker := newStorageProvisioner(c, args) 1490 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1491 defer worker.Kill() 1492 1493 volumeAccessor.volumesWatcher.changes <- []string{volume.Id()} 1494 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1495 MachineTag: machine.String(), 1496 AttachmentTag: volume.String(), 1497 }} 1498 waitChannel(c, removed, "waiting for attachment to be removed") 1499 c.Assert(detachVolumeTimes, gc.HasLen, 10) 1500 1501 // The first attempt should have been immediate: T0. 1502 c.Assert(detachVolumeTimes[0], gc.Equals, time.Time{}) 1503 1504 delays := make([]time.Duration, len(detachVolumeTimes)-1) 1505 for i := range detachVolumeTimes[1:] { 1506 delays[i] = detachVolumeTimes[i+1].Sub(detachVolumeTimes[i]) 1507 } 1508 c.Assert(delays, jc.DeepEquals, []time.Duration{ 1509 30 * time.Second, 1510 1 * time.Minute, 1511 2 * time.Minute, 1512 4 * time.Minute, 1513 8 * time.Minute, 1514 16 * time.Minute, 1515 30 * time.Minute, // ceiling reached 1516 30 * time.Minute, 1517 30 * time.Minute, 1518 }) 1519 1520 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 1521 {Tag: "volume-1", Status: "detaching", Info: "badness"}, // DetachVolumes 1522 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1523 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1524 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1525 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1526 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1527 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1528 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1529 {Tag: "volume-1", Status: "detaching", Info: "badness"}, 1530 {Tag: "volume-1", Status: "detached", Info: ""}, 1531 }) 1532 } 1533 1534 func (s *storageProvisionerSuite) TestDetachVolumesNotFound(c *gc.C) { 1535 // This test just checks that there are no unexpected api calls 1536 // if a volume attachment is deleted from state. 1537 var attached bool 1538 volumeAttachmentInfoSet := make(chan interface{}) 1539 volumeAccessor := newMockVolumeAccessor() 1540 volumeAccessor.setVolumeAttachmentInfo = func(volumeAttachments []params.VolumeAttachment) ([]params.ErrorResult, error) { 1541 close(volumeAttachmentInfoSet) 1542 attached = true 1543 for _, a := range volumeAttachments { 1544 id := params.MachineStorageId{ 1545 MachineTag: a.MachineTag, 1546 AttachmentTag: a.VolumeTag, 1547 } 1548 volumeAccessor.provisionedAttachments[id] = a 1549 } 1550 return make([]params.ErrorResult, len(volumeAttachments)), nil 1551 } 1552 1553 expectedAttachmentIds := []params.MachineStorageId{{ 1554 MachineTag: "machine-1", AttachmentTag: "volume-1", 1555 }} 1556 1557 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1558 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1559 value := life.Alive 1560 var lifeErr *params.Error 1561 if attached { 1562 lifeErr = ¶ms.Error{Code: params.CodeNotFound} 1563 } 1564 return []params.LifeResult{{Life: value, Error: lifeErr}}, nil 1565 } 1566 1567 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) { 1568 c.Fatalf("unexpected call to detachVolumes") 1569 return nil, nil 1570 } 1571 s.provider.destroyVolumesFunc = func(ids []string) ([]error, error) { 1572 c.Fatalf("unexpected call to destroyVolumes") 1573 return nil, nil 1574 } 1575 1576 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1577 c.Fatalf("unexpected call to removeAttachments") 1578 return nil, nil 1579 } 1580 1581 // volume-1 and machine-1 are provisioned. 1582 volumeAccessor.provisionedVolumes["volume-1"] = params.Volume{ 1583 VolumeTag: "volume-1", 1584 Info: params.VolumeInfo{ 1585 VolumeId: "vol-123", 1586 }, 1587 } 1588 volumeAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1589 1590 args := &workerArgs{ 1591 volumes: volumeAccessor, 1592 life: &mockLifecycleManager{ 1593 attachmentLife: attachmentLife, 1594 removeAttachments: removeAttachments, 1595 }, 1596 registry: s.registry, 1597 } 1598 worker := newStorageProvisioner(c, args) 1599 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1600 1601 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1602 MachineTag: "machine-1", AttachmentTag: "volume-1", 1603 }} 1604 volumeAccessor.volumesWatcher.changes <- []string{"1"} 1605 waitChannel(c, volumeAttachmentInfoSet, "waiting for volume attachments to be set") 1606 1607 // This results in a not found attachment. 1608 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1609 MachineTag: "machine-1", AttachmentTag: "volume-1", 1610 }} 1611 workertest.CleanKill(c, worker) 1612 } 1613 1614 func (s *storageProvisionerSuite) TestDetachFilesystemsUnattached(c *gc.C) { 1615 removed := make(chan interface{}) 1616 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1617 defer close(removed) 1618 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 1619 MachineTag: "machine-0", 1620 AttachmentTag: "filesystem-0", 1621 }}) 1622 return make([]params.ErrorResult, len(ids)), nil 1623 } 1624 1625 args := &workerArgs{ 1626 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 1627 registry: s.registry, 1628 } 1629 worker := newStorageProvisioner(c, args) 1630 defer worker.Wait() 1631 defer worker.Kill() 1632 1633 args.filesystems.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1634 MachineTag: "machine-0", AttachmentTag: "filesystem-0", 1635 }} 1636 waitChannel(c, removed, "waiting for attachment to be removed") 1637 } 1638 1639 func (s *storageProvisionerSuite) TestDetachFilesystems(c *gc.C) { 1640 var attached bool 1641 filesystemAttachmentInfoSet := make(chan interface{}) 1642 filesystemAccessor := newMockFilesystemAccessor() 1643 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 1644 close(filesystemAttachmentInfoSet) 1645 attached = true 1646 for _, a := range filesystemAttachments { 1647 id := params.MachineStorageId{ 1648 MachineTag: a.MachineTag, 1649 AttachmentTag: a.FilesystemTag, 1650 } 1651 filesystemAccessor.provisionedAttachments[id] = a 1652 } 1653 return make([]params.ErrorResult, len(filesystemAttachments)), nil 1654 } 1655 1656 expectedAttachmentIds := []params.MachineStorageId{{ 1657 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1658 }} 1659 1660 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1661 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1662 value := life.Alive 1663 if attached { 1664 value = life.Dying 1665 } 1666 return []params.LifeResult{{Life: value}}, nil 1667 } 1668 1669 detached := make(chan interface{}) 1670 s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]error, error) { 1671 c.Assert(args, gc.HasLen, 1) 1672 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 1673 c.Assert(args[0].Filesystem.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 1674 defer close(detached) 1675 return make([]error, len(args)), nil 1676 } 1677 1678 removed := make(chan interface{}) 1679 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1680 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1681 close(removed) 1682 return make([]params.ErrorResult, len(ids)), nil 1683 } 1684 1685 // filesystem-1 and machine-1 are provisioned. 1686 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 1687 FilesystemTag: "filesystem-1", 1688 Info: params.FilesystemInfo{ 1689 FilesystemId: "fs-id", 1690 }, 1691 } 1692 filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1693 1694 args := &workerArgs{ 1695 filesystems: filesystemAccessor, 1696 life: &mockLifecycleManager{ 1697 attachmentLife: attachmentLife, 1698 removeAttachments: removeAttachments, 1699 }, 1700 registry: s.registry, 1701 } 1702 worker := newStorageProvisioner(c, args) 1703 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1704 defer worker.Kill() 1705 1706 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1707 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1708 }} 1709 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 1710 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 1711 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1712 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1713 }} 1714 waitChannel(c, detached, "waiting for filesystem to be detached") 1715 waitChannel(c, removed, "waiting for attachment to be removed") 1716 } 1717 1718 func (s *storageProvisionerSuite) TestDetachFilesystemsNotFound(c *gc.C) { 1719 // This test just checks that there are no unexpected api calls 1720 // if a volume attachment is deleted from state. 1721 var attached bool 1722 filesystemAttachmentInfoSet := make(chan interface{}) 1723 filesystemAccessor := newMockFilesystemAccessor() 1724 filesystemAccessor.setFilesystemAttachmentInfo = func(filesystemAttachments []params.FilesystemAttachment) ([]params.ErrorResult, error) { 1725 close(filesystemAttachmentInfoSet) 1726 attached = true 1727 for _, a := range filesystemAttachments { 1728 id := params.MachineStorageId{ 1729 MachineTag: a.MachineTag, 1730 AttachmentTag: a.FilesystemTag, 1731 } 1732 filesystemAccessor.provisionedAttachments[id] = a 1733 } 1734 return make([]params.ErrorResult, len(filesystemAttachments)), nil 1735 } 1736 1737 expectedAttachmentIds := []params.MachineStorageId{{ 1738 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1739 }} 1740 1741 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 1742 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 1743 value := life.Alive 1744 var lifeErr *params.Error 1745 if attached { 1746 lifeErr = ¶ms.Error{Code: params.CodeNotFound} 1747 } 1748 return []params.LifeResult{{Life: value, Error: lifeErr}}, nil 1749 } 1750 1751 s.provider.detachFilesystemsFunc = func(args []storage.FilesystemAttachmentParams) ([]error, error) { 1752 c.Fatalf("unexpected call to detachFilesystems") 1753 return nil, nil 1754 } 1755 s.provider.destroyFilesystemsFunc = func(ids []string) ([]error, error) { 1756 c.Fatalf("unexpected call to destroyFilesystems") 1757 return nil, nil 1758 } 1759 1760 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 1761 c.Fatalf("unexpected call to removeAttachments") 1762 return nil, nil 1763 } 1764 1765 // filesystem-1 and machine-1 are provisioned. 1766 filesystemAccessor.provisionedFilesystems["filesystem-1"] = params.Filesystem{ 1767 FilesystemTag: "filesystem-1", 1768 Info: params.FilesystemInfo{ 1769 FilesystemId: "fs-id", 1770 }, 1771 } 1772 filesystemAccessor.provisionedMachines["machine-1"] = "already-provisioned-1" 1773 1774 args := &workerArgs{ 1775 filesystems: filesystemAccessor, 1776 life: &mockLifecycleManager{ 1777 attachmentLife: attachmentLife, 1778 removeAttachments: removeAttachments, 1779 }, 1780 registry: s.registry, 1781 } 1782 worker := newStorageProvisioner(c, args) 1783 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1784 1785 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1786 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1787 }} 1788 filesystemAccessor.filesystemsWatcher.changes <- []string{"1"} 1789 waitChannel(c, filesystemAttachmentInfoSet, "waiting for filesystem attachments to be set") 1790 1791 // This results in a not found attachment. 1792 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 1793 MachineTag: "machine-1", AttachmentTag: "filesystem-1", 1794 }} 1795 workertest.CleanKill(c, worker) 1796 } 1797 1798 func (s *storageProvisionerSuite) TestDestroyVolumes(c *gc.C) { 1799 unprovisionedVolume := names.NewVolumeTag("0") 1800 provisionedDestroyVolume := names.NewVolumeTag("1") 1801 provisionedReleaseVolume := names.NewVolumeTag("2") 1802 1803 volumeAccessor := newMockVolumeAccessor() 1804 volumeAccessor.provisionVolume(provisionedDestroyVolume) 1805 volumeAccessor.provisionVolume(provisionedReleaseVolume) 1806 1807 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1808 results := make([]params.LifeResult, len(tags)) 1809 for i := range results { 1810 results[i].Life = life.Dead 1811 } 1812 return results, nil 1813 } 1814 1815 destroyedChan := make(chan interface{}, 1) 1816 s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) { 1817 destroyedChan <- volumeIds 1818 return make([]error, len(volumeIds)), nil 1819 } 1820 1821 releasedChan := make(chan interface{}, 1) 1822 s.provider.releaseVolumesFunc = func(volumeIds []string) ([]error, error) { 1823 releasedChan <- volumeIds 1824 return make([]error, len(volumeIds)), nil 1825 } 1826 1827 removedChan := make(chan interface{}, 1) 1828 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1829 removedChan <- tags 1830 return make([]params.ErrorResult, len(tags)), nil 1831 } 1832 1833 args := &workerArgs{ 1834 volumes: volumeAccessor, 1835 life: &mockLifecycleManager{ 1836 life: life, 1837 remove: remove, 1838 }, 1839 registry: s.registry, 1840 } 1841 worker := newStorageProvisioner(c, args) 1842 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1843 defer worker.Kill() 1844 1845 volumeAccessor.volumesWatcher.changes <- []string{ 1846 unprovisionedVolume.Id(), 1847 provisionedDestroyVolume.Id(), 1848 provisionedReleaseVolume.Id(), 1849 } 1850 1851 // All volumes should be removed; the provisioned ones 1852 // should be destroyed/released first. 1853 1854 destroyed := waitChannel(c, destroyedChan, "waiting for volume to be destroyed") 1855 assertNoEvent(c, destroyedChan, "volumes destroyed") 1856 c.Assert(destroyed, jc.DeepEquals, []string{"vol-1"}) 1857 1858 released := waitChannel(c, releasedChan, "waiting for volume to be released") 1859 assertNoEvent(c, releasedChan, "volumes released") 1860 c.Assert(released, jc.DeepEquals, []string{"vol-2"}) 1861 1862 var removed []names.Tag 1863 for len(removed) < 3 { 1864 tags := waitChannel(c, removedChan, "waiting for volumes to be removed").([]names.Tag) 1865 removed = append(removed, tags...) 1866 } 1867 c.Assert(removed, jc.SameContents, []names.Tag{ 1868 unprovisionedVolume, 1869 provisionedDestroyVolume, 1870 provisionedReleaseVolume, 1871 }) 1872 assertNoEvent(c, removedChan, "volumes removed") 1873 } 1874 1875 func (s *storageProvisionerSuite) TestDestroyVolumesRetry(c *gc.C) { 1876 volume := names.NewVolumeTag("1") 1877 volumeAccessor := newMockVolumeAccessor() 1878 volumeAccessor.provisionVolume(volume) 1879 1880 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1881 return []params.LifeResult{{Life: life.Dead}}, nil 1882 } 1883 1884 // mockFunc's After will progress the current time by the specified 1885 // duration and signal the channel immediately. 1886 clock := &mockClock{} 1887 var destroyVolumeTimes []time.Time 1888 1889 s.provider.destroyVolumesFunc = func(volumeIds []string) ([]error, error) { 1890 destroyVolumeTimes = append(destroyVolumeTimes, clock.Now()) 1891 if len(destroyVolumeTimes) < 10 { 1892 return []error{errors.New("badness")}, nil 1893 } 1894 return []error{nil}, nil 1895 } 1896 1897 removedChan := make(chan interface{}, 1) 1898 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1899 removedChan <- tags 1900 return make([]params.ErrorResult, len(tags)), nil 1901 } 1902 1903 args := &workerArgs{ 1904 volumes: volumeAccessor, 1905 clock: clock, 1906 life: &mockLifecycleManager{ 1907 life: life, 1908 remove: remove, 1909 }, 1910 registry: s.registry, 1911 } 1912 worker := newStorageProvisioner(c, args) 1913 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1914 defer worker.Kill() 1915 1916 volumeAccessor.volumesWatcher.changes <- []string{volume.Id()} 1917 waitChannel(c, removedChan, "waiting for volume to be removed") 1918 c.Assert(destroyVolumeTimes, gc.HasLen, 10) 1919 1920 // The first attempt should have been immediate: T0. 1921 c.Assert(destroyVolumeTimes[0], gc.Equals, time.Time{}) 1922 1923 delays := make([]time.Duration, len(destroyVolumeTimes)-1) 1924 for i := range destroyVolumeTimes[1:] { 1925 delays[i] = destroyVolumeTimes[i+1].Sub(destroyVolumeTimes[i]) 1926 } 1927 c.Assert(delays, jc.DeepEquals, []time.Duration{ 1928 30 * time.Second, 1929 1 * time.Minute, 1930 2 * time.Minute, 1931 4 * time.Minute, 1932 8 * time.Minute, 1933 16 * time.Minute, 1934 30 * time.Minute, // ceiling reached 1935 30 * time.Minute, 1936 30 * time.Minute, 1937 }) 1938 1939 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 1940 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1941 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1942 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1943 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1944 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1945 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1946 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1947 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1948 {Tag: "volume-1", Status: "error", Info: "destroying volume: badness"}, 1949 }) 1950 } 1951 1952 func (s *storageProvisionerSuite) TestDestroyFilesystems(c *gc.C) { 1953 unprovisionedFilesystem := names.NewFilesystemTag("0") 1954 provisionedDestroyFilesystem := names.NewFilesystemTag("1") 1955 provisionedReleaseFilesystem := names.NewFilesystemTag("2") 1956 1957 filesystemAccessor := newMockFilesystemAccessor() 1958 filesystemAccessor.provisionFilesystem(provisionedDestroyFilesystem) 1959 filesystemAccessor.provisionFilesystem(provisionedReleaseFilesystem) 1960 1961 life := func(tags []names.Tag) ([]params.LifeResult, error) { 1962 results := make([]params.LifeResult, len(tags)) 1963 for i := range results { 1964 results[i].Life = life.Dead 1965 } 1966 return results, nil 1967 } 1968 1969 destroyedChan := make(chan interface{}, 1) 1970 s.provider.destroyFilesystemsFunc = func(filesystemIds []string) ([]error, error) { 1971 destroyedChan <- filesystemIds 1972 return make([]error, len(filesystemIds)), nil 1973 } 1974 1975 releasedChan := make(chan interface{}, 1) 1976 s.provider.releaseFilesystemsFunc = func(filesystemIds []string) ([]error, error) { 1977 releasedChan <- filesystemIds 1978 return make([]error, len(filesystemIds)), nil 1979 } 1980 1981 removedChan := make(chan interface{}, 1) 1982 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 1983 removedChan <- tags 1984 return make([]params.ErrorResult, len(tags)), nil 1985 } 1986 1987 args := &workerArgs{ 1988 filesystems: filesystemAccessor, 1989 life: &mockLifecycleManager{ 1990 life: life, 1991 remove: remove, 1992 }, 1993 registry: s.registry, 1994 } 1995 worker := newStorageProvisioner(c, args) 1996 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 1997 defer worker.Kill() 1998 1999 filesystemAccessor.filesystemsWatcher.changes <- []string{ 2000 unprovisionedFilesystem.Id(), 2001 provisionedDestroyFilesystem.Id(), 2002 provisionedReleaseFilesystem.Id(), 2003 } 2004 2005 // Both filesystems should be removed; the provisioned ones 2006 // should be destroyed/released first. 2007 2008 destroyed := waitChannel(c, destroyedChan, "waiting for filesystem to be destroyed") 2009 assertNoEvent(c, destroyedChan, "filesystems destroyed") 2010 c.Assert(destroyed, jc.DeepEquals, []string{"fs-1"}) 2011 2012 released := waitChannel(c, releasedChan, "waiting for filesystem to be released") 2013 assertNoEvent(c, releasedChan, "filesystems released") 2014 c.Assert(released, jc.DeepEquals, []string{"fs-2"}) 2015 2016 var removed []names.Tag 2017 for len(removed) < 3 { 2018 tags := waitChannel(c, removedChan, "waiting for filesystems to be removed").([]names.Tag) 2019 removed = append(removed, tags...) 2020 } 2021 c.Assert(removed, jc.SameContents, []names.Tag{ 2022 unprovisionedFilesystem, 2023 provisionedDestroyFilesystem, 2024 provisionedReleaseFilesystem, 2025 }) 2026 assertNoEvent(c, removedChan, "filesystems removed") 2027 } 2028 2029 func (s *storageProvisionerSuite) TestDestroyFilesystemsRetry(c *gc.C) { 2030 provisionedDestroyFilesystem := names.NewFilesystemTag("0") 2031 2032 filesystemAccessor := newMockFilesystemAccessor() 2033 filesystemAccessor.provisionFilesystem(provisionedDestroyFilesystem) 2034 2035 life := func(tags []names.Tag) ([]params.LifeResult, error) { 2036 return []params.LifeResult{{Life: life.Dead}}, nil 2037 } 2038 2039 // mockFunc's After will progress the current time by the specified 2040 // duration and signal the channel immediately. 2041 clock := &mockClock{} 2042 var destroyFilesystemTimes []time.Time 2043 s.provider.destroyFilesystemsFunc = func(filesystemIds []string) ([]error, error) { 2044 destroyFilesystemTimes = append(destroyFilesystemTimes, clock.Now()) 2045 if len(destroyFilesystemTimes) < 10 { 2046 return []error{errors.New("destroyFilesystems failed, please retry later")}, nil 2047 } 2048 return []error{nil}, nil 2049 } 2050 2051 removedChan := make(chan interface{}, 1) 2052 remove := func(tags []names.Tag) ([]params.ErrorResult, error) { 2053 removedChan <- tags 2054 return make([]params.ErrorResult, len(tags)), nil 2055 } 2056 2057 args := &workerArgs{ 2058 filesystems: filesystemAccessor, 2059 clock: clock, 2060 life: &mockLifecycleManager{ 2061 life: life, 2062 remove: remove, 2063 }, 2064 registry: s.registry, 2065 } 2066 worker := newStorageProvisioner(c, args) 2067 defer func() { c.Assert(worker.Wait(), gc.IsNil) }() 2068 defer worker.Kill() 2069 2070 filesystemAccessor.filesystemsWatcher.changes <- []string{ 2071 provisionedDestroyFilesystem.Id(), 2072 } 2073 2074 waitChannel(c, removedChan, "waiting for filesystem to be removed") 2075 c.Assert(destroyFilesystemTimes, gc.HasLen, 10) 2076 2077 // The first attempt should have been immediate: T0. 2078 c.Assert(destroyFilesystemTimes[0], gc.Equals, time.Time{}) 2079 2080 delays := make([]time.Duration, len(destroyFilesystemTimes)-1) 2081 for i := range destroyFilesystemTimes[1:] { 2082 delays[i] = destroyFilesystemTimes[i+1].Sub(destroyFilesystemTimes[i]) 2083 } 2084 c.Assert(delays, jc.DeepEquals, []time.Duration{ 2085 30 * time.Second, 2086 1 * time.Minute, 2087 2 * time.Minute, 2088 4 * time.Minute, 2089 8 * time.Minute, 2090 16 * time.Minute, 2091 30 * time.Minute, // ceiling reached 2092 30 * time.Minute, 2093 30 * time.Minute, 2094 }) 2095 2096 c.Assert(args.statusSetter.args, jc.DeepEquals, []params.EntityStatusArgs{ 2097 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2098 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2099 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2100 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2101 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2102 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2103 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2104 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2105 {Tag: "filesystem-0", Status: "error", Info: "removing filesystem: destroyFilesystems failed, please retry later"}, 2106 }) 2107 } 2108 2109 type caasStorageProvisionerSuite struct { 2110 coretesting.BaseSuite 2111 provider *dummyProvider 2112 registry storage.ProviderRegistry 2113 } 2114 2115 var _ = gc.Suite(&caasStorageProvisionerSuite{}) 2116 2117 func (s *caasStorageProvisionerSuite) SetUpTest(c *gc.C) { 2118 s.BaseSuite.SetUpTest(c) 2119 s.provider = &dummyProvider{dynamic: true} 2120 s.registry = storage.StaticProviderRegistry{ 2121 map[storage.ProviderType]storage.Provider{ 2122 "dummy": s.provider, 2123 }, 2124 } 2125 s.PatchValue(storageprovisioner.DefaultDependentChangesTimeout, 10*time.Millisecond) 2126 } 2127 2128 func (s *caasStorageProvisionerSuite) TestDetachVolumesUnattached(c *gc.C) { 2129 removed := make(chan interface{}) 2130 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 2131 defer close(removed) 2132 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 2133 MachineTag: "unit-mariadb-0", 2134 AttachmentTag: "volume-0", 2135 }}) 2136 return make([]params.ErrorResult, len(ids)), nil 2137 } 2138 2139 args := &workerArgs{ 2140 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 2141 registry: s.registry, 2142 } 2143 w := newStorageProvisioner(c, args) 2144 defer w.Wait() 2145 defer w.Kill() 2146 2147 args.volumes.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 2148 MachineTag: "unit-mariadb-0", AttachmentTag: "volume-0", 2149 }} 2150 waitChannel(c, removed, "waiting for attachment to be removed") 2151 } 2152 2153 func (s *caasStorageProvisionerSuite) TestDetachVolumes(c *gc.C) { 2154 volumeAccessor := newMockVolumeAccessor() 2155 2156 expectedAttachmentIds := []params.MachineStorageId{{ 2157 MachineTag: "unit-mariadb-1", AttachmentTag: "volume-1", 2158 }} 2159 2160 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 2161 return []params.LifeResult{{Life: life.Dying}}, nil 2162 } 2163 2164 detached := make(chan interface{}) 2165 s.provider.detachVolumesFunc = func(args []storage.VolumeAttachmentParams) ([]error, error) { 2166 c.Assert(args, gc.HasLen, 1) 2167 c.Assert(args[0].Machine.String(), gc.Equals, expectedAttachmentIds[0].MachineTag) 2168 c.Assert(args[0].Volume.String(), gc.Equals, expectedAttachmentIds[0].AttachmentTag) 2169 defer close(detached) 2170 return make([]error, len(args)), nil 2171 } 2172 2173 args := &workerArgs{ 2174 volumes: volumeAccessor, 2175 life: &mockLifecycleManager{ 2176 attachmentLife: attachmentLife, 2177 }, 2178 registry: s.registry, 2179 } 2180 w := newStorageProvisioner(c, args) 2181 defer func() { c.Assert(w.Wait(), gc.IsNil) }() 2182 defer w.Kill() 2183 2184 volumeAccessor.provisionedAttachments[expectedAttachmentIds[0]] = params.VolumeAttachment{ 2185 MachineTag: "unit-mariadb-1", 2186 VolumeTag: "volume-1", 2187 } 2188 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 2189 MachineTag: "unit-mariadb-1", AttachmentTag: "volume-1", 2190 }} 2191 waitChannel(c, detached, "waiting for volume to be detached") 2192 } 2193 2194 func (s *caasStorageProvisionerSuite) TestRemoveVolumes(c *gc.C) { 2195 volumeAccessor := newMockVolumeAccessor() 2196 2197 expectedAttachmentIds := []params.MachineStorageId{{ 2198 MachineTag: "unit-mariadb-1", AttachmentTag: "volume-1", 2199 }} 2200 2201 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 2202 return []params.LifeResult{{Life: life.Dying}}, nil 2203 } 2204 2205 removed := make(chan interface{}) 2206 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 2207 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 2208 close(removed) 2209 return make([]params.ErrorResult, len(ids)), nil 2210 } 2211 2212 args := &workerArgs{ 2213 volumes: volumeAccessor, 2214 life: &mockLifecycleManager{ 2215 attachmentLife: attachmentLife, 2216 removeAttachments: removeAttachments, 2217 }, 2218 registry: s.registry, 2219 } 2220 w := newStorageProvisioner(c, args) 2221 defer func() { c.Assert(w.Wait(), gc.IsNil) }() 2222 defer w.Kill() 2223 2224 volumeAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 2225 MachineTag: "unit-mariadb-1", AttachmentTag: "volume-1", 2226 }} 2227 waitChannel(c, removed, "waiting for attachment to be removed") 2228 } 2229 2230 func (s *caasStorageProvisionerSuite) TestDetachFilesystems(c *gc.C) { 2231 removed := make(chan interface{}) 2232 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 2233 defer close(removed) 2234 c.Assert(ids, gc.DeepEquals, []params.MachineStorageId{{ 2235 MachineTag: "unit-mariadb-0", 2236 AttachmentTag: "filesystem-0", 2237 }}) 2238 return make([]params.ErrorResult, len(ids)), nil 2239 } 2240 2241 args := &workerArgs{ 2242 life: &mockLifecycleManager{removeAttachments: removeAttachments}, 2243 registry: s.registry, 2244 } 2245 w := newStorageProvisioner(c, args) 2246 defer w.Wait() 2247 defer w.Kill() 2248 2249 args.filesystems.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 2250 MachineTag: "unit-mariadb-0", AttachmentTag: "filesystem-0", 2251 }} 2252 waitChannel(c, removed, "waiting for attachment to be removed") 2253 } 2254 2255 func (s *caasStorageProvisionerSuite) TestRemoveFilesystems(c *gc.C) { 2256 filesystemAccessor := newMockFilesystemAccessor() 2257 2258 expectedAttachmentIds := []params.MachineStorageId{{ 2259 MachineTag: "unit-mariadb-1", AttachmentTag: "filesystem-1", 2260 }} 2261 2262 attachmentLife := func(ids []params.MachineStorageId) ([]params.LifeResult, error) { 2263 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 2264 return []params.LifeResult{{Life: life.Dying}}, nil 2265 } 2266 2267 removed := make(chan interface{}) 2268 removeAttachments := func(ids []params.MachineStorageId) ([]params.ErrorResult, error) { 2269 c.Assert(ids, gc.DeepEquals, expectedAttachmentIds) 2270 close(removed) 2271 return make([]params.ErrorResult, len(ids)), nil 2272 } 2273 2274 args := &workerArgs{ 2275 filesystems: filesystemAccessor, 2276 life: &mockLifecycleManager{ 2277 attachmentLife: attachmentLife, 2278 removeAttachments: removeAttachments, 2279 }, 2280 registry: s.registry, 2281 } 2282 w := newStorageProvisioner(c, args) 2283 defer func() { c.Assert(w.Wait(), gc.IsNil) }() 2284 defer w.Kill() 2285 2286 filesystemAccessor.attachmentsWatcher.changes <- []watcher.MachineStorageId{{ 2287 MachineTag: "unit-mariadb-1", AttachmentTag: "filesystem-1", 2288 }} 2289 waitChannel(c, removed, "waiting for filesystem to be removed") 2290 } 2291 2292 func newStorageProvisioner(c *gc.C, args *workerArgs) worker.Worker { 2293 if args == nil { 2294 args = &workerArgs{} 2295 } 2296 var storageDir string 2297 switch args.scope.(type) { 2298 case names.MachineTag: 2299 storageDir = "storage-dir" 2300 case names.ModelTag: 2301 case nil: 2302 args.scope = coretesting.ModelTag 2303 } 2304 if args.volumes == nil { 2305 args.volumes = newMockVolumeAccessor() 2306 } 2307 if args.filesystems == nil { 2308 args.filesystems = newMockFilesystemAccessor() 2309 } 2310 if args.life == nil { 2311 args.life = &mockLifecycleManager{} 2312 } 2313 if args.machines == nil { 2314 args.machines = newMockMachineAccessor(c) 2315 } 2316 if args.clock == nil { 2317 args.clock = &mockClock{} 2318 } 2319 if args.statusSetter == nil { 2320 args.statusSetter = &mockStatusSetter{} 2321 } 2322 worker, err := storageprovisioner.NewStorageProvisioner(storageprovisioner.Config{ 2323 Scope: args.scope, 2324 StorageDir: storageDir, 2325 Volumes: args.volumes, 2326 Filesystems: args.filesystems, 2327 Life: args.life, 2328 Registry: args.registry, 2329 Machines: args.machines, 2330 Status: args.statusSetter, 2331 Clock: args.clock, 2332 Logger: loggo.GetLogger("test"), 2333 CloudCallContextFunc: func(_ stdcontext.Context) context.ProviderCallContext { 2334 return context.NewEmptyCloudCallContext() 2335 }, 2336 }) 2337 c.Assert(err, jc.ErrorIsNil) 2338 return worker 2339 } 2340 2341 type workerArgs struct { 2342 scope names.Tag 2343 volumes *mockVolumeAccessor 2344 filesystems *mockFilesystemAccessor 2345 life *mockLifecycleManager 2346 registry storage.ProviderRegistry 2347 machines *mockMachineAccessor 2348 clock clock.Clock 2349 statusSetter *mockStatusSetter 2350 } 2351 2352 func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} { 2353 select { 2354 case v := <-ch: 2355 return v 2356 case <-time.After(coretesting.LongWait): 2357 c.Fatalf("timed out " + activity) 2358 panic("unreachable") 2359 } 2360 } 2361 2362 func assertNoEvent(c *gc.C, ch <-chan interface{}, event string) { 2363 select { 2364 case <-ch: 2365 c.Fatalf("unexpected " + event) 2366 case <-time.After(coretesting.ShortWait): 2367 } 2368 }