github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/provisioner/provisioner_task_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provisioner_test 5 6 import ( 7 "fmt" 8 "reflect" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/golang/mock/gomock" 14 "github.com/juju/errors" 15 "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/version" 18 gc "gopkg.in/check.v1" 19 "gopkg.in/juju/names.v2" 20 "gopkg.in/juju/worker.v1/workertest" 21 22 "github.com/juju/juju/api" 23 apiprovisioner "github.com/juju/juju/api/provisioner" 24 apiprovisionermock "github.com/juju/juju/api/provisioner/mocks" 25 "github.com/juju/juju/apiserver/params" 26 "github.com/juju/juju/controller/authentication" 27 "github.com/juju/juju/core/constraints" 28 "github.com/juju/juju/core/instance" 29 "github.com/juju/juju/core/lxdprofile" 30 "github.com/juju/juju/core/status" 31 "github.com/juju/juju/core/watcher" 32 "github.com/juju/juju/core/watcher/watchertest" 33 "github.com/juju/juju/environs" 34 "github.com/juju/juju/environs/config" 35 "github.com/juju/juju/environs/context" 36 "github.com/juju/juju/environs/imagemetadata" 37 "github.com/juju/juju/environs/instances" 38 jujuversion "github.com/juju/juju/juju/version" 39 "github.com/juju/juju/mongo" 40 "github.com/juju/juju/provider/common" 41 "github.com/juju/juju/provider/common/mocks" 42 coretesting "github.com/juju/juju/testing" 43 "github.com/juju/juju/worker/provisioner" 44 provisionermocks "github.com/juju/juju/worker/provisioner/mocks" 45 ) 46 47 type ProvisionerTaskSuite struct { 48 testing.IsolationSuite 49 50 modelMachinesChanges chan []string 51 modelMachinesWatcher watcher.StringsWatcher 52 53 machineErrorRetryChanges chan struct{} 54 machineErrorRetryWatcher watcher.NotifyWatcher 55 56 modelMachinesProfileChanges chan []string 57 modelMachinesProfileWatcher watcher.StringsWatcher 58 59 machinesResults []apiprovisioner.MachineResult 60 machineStatusResults []apiprovisioner.MachineStatusResult 61 machineGetter *testMachineGetter 62 63 instances []instances.Instance 64 instanceBroker *testInstanceBroker 65 66 callCtx *context.CloudCallContext 67 invalidCredential bool 68 69 auth *testAuthenticationProvider 70 } 71 72 var _ = gc.Suite(&ProvisionerTaskSuite{}) 73 74 func (s *ProvisionerTaskSuite) SetUpTest(c *gc.C) { 75 s.IsolationSuite.SetUpTest(c) 76 77 s.modelMachinesChanges = make(chan []string) 78 s.modelMachinesWatcher = watchertest.NewMockStringsWatcher(s.modelMachinesChanges) 79 80 s.machineErrorRetryChanges = make(chan struct{}) 81 s.machineErrorRetryWatcher = watchertest.NewMockNotifyWatcher(s.machineErrorRetryChanges) 82 83 s.modelMachinesProfileChanges = make(chan []string) 84 s.modelMachinesProfileWatcher = watchertest.NewMockStringsWatcher(s.modelMachinesProfileChanges) 85 86 s.machinesResults = []apiprovisioner.MachineResult{} 87 s.machineStatusResults = []apiprovisioner.MachineStatusResult{} 88 s.machineGetter = &testMachineGetter{ 89 Stub: &testing.Stub{}, 90 machinesFunc: func(machines ...names.MachineTag) ([]apiprovisioner.MachineResult, error) { 91 return s.machinesResults, nil 92 }, 93 machinesWithTransientErrorsFunc: func() ([]apiprovisioner.MachineStatusResult, error) { 94 return s.machineStatusResults, nil 95 }, 96 } 97 98 s.instances = []instances.Instance{} 99 s.instanceBroker = &testInstanceBroker{ 100 Stub: &testing.Stub{}, 101 callsChan: make(chan string, 2), 102 allInstancesFunc: func(ctx context.ProviderCallContext) ([]instances.Instance, error) { 103 return s.instances, nil 104 }, 105 } 106 107 s.callCtx = &context.CloudCallContext{ 108 InvalidateCredentialFunc: func(string) error { 109 s.invalidCredential = true 110 return nil 111 }, 112 } 113 s.auth = &testAuthenticationProvider{&testing.Stub{}} 114 } 115 116 func (s *ProvisionerTaskSuite) TestStartStop(c *gc.C) { 117 task := s.newProvisionerTask(c, 118 config.HarvestAll, 119 &mockDistributionGroupFinder{}, 120 mockToolsFinder{}, 121 ) 122 workertest.CheckAlive(c, task) 123 workertest.CleanKill(c, task) 124 125 err := workertest.CheckKilled(c, task) 126 c.Assert(err, jc.ErrorIsNil) 127 err = workertest.CheckKilled(c, s.modelMachinesWatcher) 128 c.Assert(err, jc.ErrorIsNil) 129 err = workertest.CheckKilled(c, s.machineErrorRetryWatcher) 130 c.Assert(err, jc.ErrorIsNil) 131 s.machineGetter.CheckNoCalls(c) 132 s.instanceBroker.CheckNoCalls(c) 133 } 134 135 func (s *ProvisionerTaskSuite) TestStopInstancesIgnoresMachinesWithKeep(c *gc.C) { 136 task := s.newProvisionerTask(c, 137 config.HarvestAll, 138 &mockDistributionGroupFinder{}, 139 mockToolsFinder{}, 140 ) 141 defer workertest.CleanKill(c, task) 142 143 i0 := &testInstance{id: "zero"} 144 i1 := &testInstance{id: "one"} 145 s.instances = []instances.Instance{ 146 i0, 147 i1, 148 } 149 150 m0 := &testMachine{ 151 id: "0", 152 life: params.Dead, 153 instance: i0, 154 } 155 m1 := &testMachine{ 156 id: "1", 157 life: params.Dead, 158 instance: i1, 159 keepInstance: true, 160 } 161 c.Assert(m0.markForRemoval, jc.IsFalse) 162 c.Assert(m1.markForRemoval, jc.IsFalse) 163 164 s.machinesResults = []apiprovisioner.MachineResult{ 165 {Machine: m0}, 166 {Machine: m1}, 167 } 168 169 s.sendModelMachinesChange(c, "0", "1") 170 171 s.waitForTask(c, []string{"AllInstances", "StopInstances"}) 172 173 workertest.CleanKill(c, task) 174 close(s.instanceBroker.callsChan) 175 s.machineGetter.CheckCallNames(c, "Machines") 176 s.instanceBroker.CheckCalls(c, []testing.StubCall{ 177 {"AllInstances", []interface{}{s.callCtx}}, 178 {"StopInstances", []interface{}{s.callCtx, []instance.Id{"zero"}}}, 179 }) 180 c.Assert(m0.markForRemoval, jc.IsTrue) 181 c.Assert(m1.markForRemoval, jc.IsTrue) 182 } 183 184 func (s *ProvisionerTaskSuite) TestProvisionerRetries(c *gc.C) { 185 s.instanceBroker.SetErrors( 186 errors.New("errors 1"), 187 errors.New("errors 2"), 188 ) 189 190 task := s.newProvisionerTaskWithRetry(c, 191 config.HarvestAll, 192 &mockDistributionGroupFinder{}, 193 mockToolsFinder{}, 194 provisioner.NewRetryStrategy(0*time.Second, 1), 195 ) 196 197 m0 := &testMachine{ 198 id: "0", 199 } 200 s.machineStatusResults = []apiprovisioner.MachineStatusResult{ 201 {Machine: m0, Status: params.StatusResult{}}, 202 } 203 s.sendMachineErrorRetryChange(c) 204 205 s.waitForTask(c, []string{"StartInstance", "StartInstance"}) 206 207 workertest.CleanKill(c, task) 208 close(s.instanceBroker.callsChan) 209 s.machineGetter.CheckCallNames(c, "MachinesWithTransientErrors") 210 s.auth.CheckCallNames(c, "SetupAuthentication") 211 s.instanceBroker.CheckCallNames(c, "StartInstance", "StartInstance") 212 } 213 214 func (s *ProvisionerTaskSuite) TestProcessProfileChanges(c *gc.C) { 215 ctrl := gomock.NewController(c) 216 defer ctrl.Finish() 217 218 // Setup mockMachine0 to successfully change from an 219 // old profile to a new profile. 220 mockMachine0, info0 := setUpSuccessfulMockProfileMachine(ctrl, "0", "juju-default-lxd-profile-0", false) 221 mockMachine0.EXPECT().SetCharmProfiles([]string{info0.NewProfileName, "juju-default-different-0"}).Return(nil) 222 223 // Setup mockMachine1 to successfully change from an 224 // old profile to a new profile. 225 mockMachine1, info1 := setUpSuccessfulMockProfileMachine(ctrl, "1", "juju-default-lxd-profile-0", true) 226 mockMachine1.EXPECT().SetCharmProfiles([]string{info1.NewProfileName, "juju-default-different-0"}).Return(nil) 227 228 // Setup mockMachine2 to have a failure from CharmProfileChangeInfo() 229 mockMachine2 := setUpFailureMockProfileMachine(ctrl, "2") 230 231 // Setup mockMachine3 to be a new subordinate unit adding 232 // an lxd profile. 233 mockMachine3, info3 := setUpSuccessfulMockProfileMachine(ctrl, "3", "", true) 234 mockMachine3.EXPECT().SetCharmProfiles([]string{"juju-default-different-0", info3.NewProfileName}).Return(nil) 235 236 s.machinesResults = []apiprovisioner.MachineResult{ 237 {Machine: mockMachine0, Err: nil}, 238 {Machine: mockMachine1, Err: nil}, 239 {Machine: mockMachine2, Err: nil}, 240 {Machine: mockMachine3, Err: nil}, 241 } 242 243 mockBroker := provisionermocks.NewMockLXDProfileInstanceBroker(ctrl) 244 lExp := mockBroker.EXPECT() 245 machineCharmProfiles := []string{"default", "juju-default", info0.NewProfileName, "juju-default-different-0"} 246 lExp.ReplaceOrAddInstanceProfile( 247 "0", info0.OldProfileName, info0.NewProfileName, info0.LXDProfile, 248 ).Return(machineCharmProfiles, nil) 249 lExp.ReplaceOrAddInstanceProfile( 250 "1", info1.OldProfileName, info1.NewProfileName, info1.LXDProfile, 251 ).Return(machineCharmProfiles, nil) 252 lExp.ReplaceOrAddInstanceProfile( 253 "3", info3.OldProfileName, info3.NewProfileName, info3.LXDProfile, 254 ).Return([]string{"default", "juju-default", "juju-default-different-0", info3.NewProfileName}, nil) 255 256 task := s.newProvisionerTaskWithBroker(c, mockBroker, nil) 257 c.Assert(provisioner.ProcessProfileChanges(task, []string{"0", "1", "2", "3"}), jc.ErrorIsNil) 258 } 259 260 func setUpSuccessfulMockProfileMachine(ctrl *gomock.Controller, num, old string, sub bool) (*apiprovisionermock.MockMachineProvisioner, apiprovisioner.CharmProfileChangeInfo) { 261 mockMachine := apiprovisionermock.NewMockMachineProvisioner(ctrl) 262 mExp := mockMachine.EXPECT() 263 newProfileName := "juju-default-lxd-profile-1" 264 info := apiprovisioner.CharmProfileChangeInfo{ 265 OldProfileName: old, 266 NewProfileName: newProfileName, 267 LXDProfile: nil, 268 Subordinate: sub, 269 } 270 mExp.CharmProfileChangeInfo().Return(info, nil) 271 mExp.Id().Return(num) 272 mExp.InstanceId().Return(instance.Id(num), nil) 273 if old == "" && sub { 274 mExp.RemoveUpgradeCharmProfileData().Return(nil) 275 } else { 276 mExp.SetInstanceStatus(status.Running, "Running", nil).Return(nil) 277 mExp.SetStatus(status.Started, "", nil).Return(nil) 278 mExp.SetUpgradeCharmProfileComplete(lxdprofile.SuccessStatus).Return(nil) 279 } 280 281 return mockMachine, info 282 } 283 284 func setUpFailureMockProfileMachine(ctrl *gomock.Controller, num string) *apiprovisionermock.MockMachineProvisioner { 285 mockMachine := apiprovisionermock.NewMockMachineProvisioner(ctrl) 286 mExp := mockMachine.EXPECT() 287 mExp.CharmProfileChangeInfo().Return(apiprovisioner.CharmProfileChangeInfo{}, errors.New("fail me")) 288 mExp.Id().Return(num) 289 mExp.SetInstanceStatus(status.Error, gomock.Any(), nil).Return(nil) 290 mExp.SetUpgradeCharmProfileComplete(gomock.Any()).Return(nil) 291 292 return mockMachine 293 } 294 295 func (s *ProvisionerTaskSuite) TestProcessProfileChangesNoLXDBroker(c *gc.C) { 296 ctrl := gomock.NewController(c) 297 defer ctrl.Finish() 298 299 mockMachine := apiprovisionermock.NewMockMachineProvisioner(ctrl) 300 mExp := mockMachine.EXPECT() 301 mExp.SetUpgradeCharmProfileComplete(lxdprofile.NotSupportedStatus).Return(nil) 302 303 s.machinesResults = []apiprovisioner.MachineResult{ 304 {Machine: mockMachine, Err: nil}, 305 } 306 307 task := s.newProvisionerTask(c, 308 config.HarvestAll, 309 &mockDistributionGroupFinder{}, 310 mockToolsFinder{}, 311 ) 312 defer workertest.CleanKill(c, task) 313 314 c.Assert(provisioner.ProcessProfileChanges(task, []string{"0"}), jc.ErrorIsNil) 315 } 316 317 func (s *ProvisionerTaskSuite) testProcessOneMachineProfileChangeAddProfile(c *gc.C, sub bool) { 318 ctrl := gomock.NewController(c) 319 defer ctrl.Finish() 320 321 mockMachineProvisioner := apiprovisionermock.NewMockMachineProvisioner(ctrl) 322 mExp := mockMachineProvisioner.EXPECT() 323 newProfileName := "juju-default-lxd-profile-0" 324 info := apiprovisioner.CharmProfileChangeInfo{ 325 OldProfileName: "", 326 NewProfileName: newProfileName, 327 LXDProfile: nil, 328 Subordinate: sub, 329 } 330 mExp.CharmProfileChangeInfo().Return(info, nil) 331 mExp.Id().Return("0") 332 mExp.InstanceId().Return(instance.Id("0"), nil) 333 differentProfileName := "juju-default-different-0" 334 mExp.SetCharmProfiles([]string{differentProfileName, newProfileName}).Return(nil) 335 336 mockLXDProfiler := provisionermocks.NewMockLXDProfileInstanceBroker(ctrl) 337 lExp := mockLXDProfiler.EXPECT() 338 machineCharmProfiles := []string{"default", "juju-default", differentProfileName, newProfileName} 339 lExp.ReplaceOrAddInstanceProfile( 340 "0", info.OldProfileName, info.NewProfileName, info.LXDProfile, 341 ).Return(machineCharmProfiles, nil) 342 343 remove, err := provisioner.ProcessOneMachineProfileChanges(mockMachineProvisioner, mockLXDProfiler) 344 c.Assert(err, jc.ErrorIsNil) 345 c.Assert(remove, gc.Equals, sub) 346 } 347 348 func (s *ProvisionerTaskSuite) TestProcessOneMachineProfileChangeAddProfile(c *gc.C) { 349 s.testProcessOneMachineProfileChangeAddProfile(c, false) 350 } 351 352 func (s *ProvisionerTaskSuite) TestProcessOneMachineProfileChangeAddProfileSubordinate(c *gc.C) { 353 s.testProcessOneMachineProfileChangeAddProfile(c, true) 354 } 355 356 func (s *ProvisionerTaskSuite) TestProcessOneMachineProfileChangeRemoveProfileSubordinate(c *gc.C) { 357 info := apiprovisioner.CharmProfileChangeInfo{ 358 OldProfileName: "juju-default-lxd-profile-0", 359 NewProfileName: "", 360 LXDProfile: nil, 361 Subordinate: true, 362 } 363 364 ctrl, mockMachineProvisioner, mockLXDProfiler := setUpMocksProcessOneMachineProfileChange(c, info) 365 defer ctrl.Finish() 366 367 remove, err := provisioner.ProcessOneMachineProfileChanges(mockMachineProvisioner, mockLXDProfiler) 368 c.Assert(err, jc.ErrorIsNil) 369 c.Assert(remove, gc.Equals, false) 370 } 371 372 func (s *ProvisionerTaskSuite) TestProcessOneMachineProfileChangeChangeProfile(c *gc.C) { 373 info := apiprovisioner.CharmProfileChangeInfo{ 374 OldProfileName: "juju-default-lxd-profile-0", 375 NewProfileName: "juju-default-lxd-profile-1", 376 LXDProfile: nil, 377 Subordinate: true, 378 } 379 380 ctrl, mockMachineProvisioner, mockLXDProfiler := setUpMocksProcessOneMachineProfileChange(c, info) 381 defer ctrl.Finish() 382 383 remove, err := provisioner.ProcessOneMachineProfileChanges(mockMachineProvisioner, mockLXDProfiler) 384 c.Assert(err, jc.ErrorIsNil) 385 c.Assert(remove, gc.Equals, false) 386 } 387 388 func setUpMocksProcessOneMachineProfileChange(c *gc.C, info apiprovisioner.CharmProfileChangeInfo) (*gomock.Controller, *apiprovisionermock.MockMachineProvisioner, *provisionermocks.MockLXDProfileInstanceBroker) { 389 ctrl := gomock.NewController(c) 390 391 mockMachineProvisioner := apiprovisionermock.NewMockMachineProvisioner(ctrl) 392 mExp := mockMachineProvisioner.EXPECT() 393 mExp.CharmProfileChangeInfo().Return(info, nil) 394 mExp.Id().Return("0") 395 mExp.InstanceId().Return(instance.Id("0"), nil) 396 397 differentProfileName := "juju-default-different-0" 398 machineCharmProfiles := []string{"default", "juju-default"} 399 if info.NewProfileName != "" { 400 mExp.SetCharmProfiles([]string{info.NewProfileName, differentProfileName}).Return(nil) 401 machineCharmProfiles = append(machineCharmProfiles, info.NewProfileName) 402 } else { 403 mExp.SetCharmProfiles([]string{differentProfileName}).Return(nil) 404 } 405 machineCharmProfiles = append(machineCharmProfiles, differentProfileName) 406 407 mockLXDProfiler := provisionermocks.NewMockLXDProfileInstanceBroker(ctrl) 408 mockLXDProfiler.EXPECT().ReplaceOrAddInstanceProfile( 409 "0", info.OldProfileName, info.NewProfileName, info.LXDProfile, 410 ).Return(machineCharmProfiles, nil) 411 412 return ctrl, mockMachineProvisioner, mockLXDProfiler 413 } 414 415 func (s *ProvisionerTaskSuite) TestZoneConstraintsNoZoneAvailable(c *gc.C) { 416 ctrl := gomock.NewController(c) 417 defer ctrl.Finish() 418 419 broker := s.setUpZonedEnviron(ctrl) 420 421 // Constraint for availability zone az9 can not be satisfied; 422 // this broker only knows of az1, az2, az3. 423 azConstraints := newAZConstraintStartInstanceParamsMatcher("az9") 424 broker.EXPECT().DeriveAvailabilityZones(s.callCtx, azConstraints).Return([]string{}, nil) 425 426 task := s.newProvisionerTaskWithBroker(c, broker, nil) 427 428 m0 := &testMachine{ 429 id: "0", 430 constraints: "zones=az9", 431 } 432 s.machineStatusResults = []apiprovisioner.MachineStatusResult{{Machine: m0, Status: params.StatusResult{}}} 433 s.sendMachineErrorRetryChange(c) 434 435 // Wait for instance status to be set. 436 timeout := time.After(coretesting.LongWait) 437 for msg := ""; msg == ""; { 438 select { 439 case <-time.After(coretesting.ShortWait): 440 _, msg, _ = m0.InstanceStatus() 441 case <-timeout: 442 c.Fatalf("machine InstanceStatus was not set") 443 } 444 } 445 446 _, msg, err := m0.InstanceStatus() 447 c.Assert(err, jc.ErrorIsNil) 448 c.Check(msg, gc.Equals, "suitable availability zone for machine 0 not found") 449 450 workertest.CleanKill(c, task) 451 } 452 453 func (s *ProvisionerTaskSuite) TestZoneConstraintsNoDistributionGroup(c *gc.C) { 454 ctrl := gomock.NewController(c) 455 defer ctrl.Finish() 456 457 broker := s.setUpZonedEnviron(ctrl) 458 azConstraints := newAZConstraintStartInstanceParamsMatcher("az1") 459 broker.EXPECT().DeriveAvailabilityZones(s.callCtx, azConstraints).Return([]string{}, nil) 460 461 // For the call to start instance, we expect the same zone constraint to 462 // be present, but we also expect that the zone in start instance params 463 // matches the constraint, based on being available in this environ. 464 azConstraintsAndDerivedZone := newAZConstraintStartInstanceParamsMatcher("az1") 465 azConstraintsAndDerivedZone.addMatch("availability zone: az1", func(p environs.StartInstanceParams) bool { 466 return p.AvailabilityZone == "az1" 467 }) 468 469 // Use satisfaction of this call as the synchronisation point. 470 started := make(chan struct{}) 471 broker.EXPECT().StartInstance(s.callCtx, azConstraints).Return(&environs.StartInstanceResult{ 472 Instance: &testInstance{id: "instance-1"}, 473 }, nil).Do(func(_ ...interface{}) { 474 go func() { started <- struct{}{} }() 475 }) 476 477 task := s.newProvisionerTaskWithBroker(c, broker, nil) 478 479 m0 := &testMachine{ 480 id: "0", 481 constraints: "zones=az1", 482 } 483 s.machineStatusResults = []apiprovisioner.MachineStatusResult{{Machine: m0, Status: params.StatusResult{}}} 484 s.sendMachineErrorRetryChange(c) 485 486 select { 487 case <-started: 488 case <-time.After(coretesting.LongWait): 489 c.Fatalf("no matching call to StartInstance") 490 } 491 492 workertest.CleanKill(c, task) 493 } 494 495 func (s *ProvisionerTaskSuite) TestZoneConstraintsWithDistributionGroup(c *gc.C) { 496 ctrl := gomock.NewController(c) 497 defer ctrl.Finish() 498 499 broker := s.setUpZonedEnviron(ctrl) 500 azConstraints := newAZConstraintStartInstanceParamsMatcher("az1", "az2") 501 broker.EXPECT().DeriveAvailabilityZones(s.callCtx, azConstraints).Return([]string{}, nil) 502 503 // For the call to start instance, we expect the same zone constraints to 504 // be present, but we also expect that the zone in start instance params 505 // was selected from the constraints, based on a machine from the same 506 // distribution group already being in one of the zones. 507 azConstraintsAndDerivedZone := newAZConstraintStartInstanceParamsMatcher("az1", "az2") 508 azConstraintsAndDerivedZone.addMatch("availability zone: az2", func(p environs.StartInstanceParams) bool { 509 return p.AvailabilityZone == "az2" 510 }) 511 512 // Use satisfaction of this call as the synchronisation point. 513 started := make(chan struct{}) 514 broker.EXPECT().StartInstance(s.callCtx, azConstraints).Return(&environs.StartInstanceResult{ 515 Instance: &testInstance{id: "instance-1"}, 516 }, nil).Do(func(_ ...interface{}) { 517 go func() { started <- struct{}{} }() 518 }) 519 520 // Another machine from the same distribution group is already in az1, 521 // so we expect the machine to be created in az2. 522 task := s.newProvisionerTaskWithBroker(c, broker, map[names.MachineTag][]string{ 523 names.NewMachineTag("0"): {"az1"}, 524 }) 525 526 m0 := &testMachine{ 527 id: "0", 528 constraints: "zones=az1,az2", 529 } 530 s.machineStatusResults = []apiprovisioner.MachineStatusResult{{Machine: m0, Status: params.StatusResult{}}} 531 s.sendMachineErrorRetryChange(c) 532 533 select { 534 case <-started: 535 case <-time.After(coretesting.LongWait): 536 c.Fatalf("no matching call to StartInstance") 537 } 538 539 workertest.CleanKill(c, task) 540 } 541 542 // setUpZonedEnviron creates a mock environ with instances based on those set 543 // on the test suite, and 3 availability zones. 544 func (s *ProvisionerTaskSuite) setUpZonedEnviron(ctrl *gomock.Controller) *mocks.MockZonedEnviron { 545 instanceIds := make([]instance.Id, len(s.instances)) 546 for i, inst := range s.instances { 547 instanceIds[i] = inst.Id() 548 } 549 550 // Environ has 3 availability zones: az1, az2, az3. 551 zones := make([]common.AvailabilityZone, 3) 552 for i := 0; i < 3; i++ { 553 az := mocks.NewMockAvailabilityZone(ctrl) 554 az.EXPECT().Name().Return(fmt.Sprintf("az%d", i+1)) 555 az.EXPECT().Available().Return(true) 556 zones[i] = az 557 } 558 559 broker := mocks.NewMockZonedEnviron(ctrl) 560 exp := broker.EXPECT() 561 exp.AllInstances(s.callCtx).Return(s.instances, nil) 562 exp.InstanceAvailabilityZoneNames(s.callCtx, instanceIds).Return([]string{}, nil) 563 exp.AvailabilityZones(s.callCtx).Return(zones, nil) 564 return broker 565 } 566 567 func (s *ProvisionerTaskSuite) waitForTask(c *gc.C, expectedCalls []string) { 568 var calls []string 569 for { 570 select { 571 case call := <-s.instanceBroker.callsChan: 572 calls = append(calls, call) 573 case <-time.After(coretesting.LongWait): 574 c.Fatalf("stopping worker chan didn't stop") 575 } 576 if reflect.DeepEqual(expectedCalls, calls) { 577 // we are done 578 break 579 } 580 } 581 } 582 583 func (s *ProvisionerTaskSuite) sendModelMachinesChange(c *gc.C, ids ...string) { 584 select { 585 case s.modelMachinesChanges <- ids: 586 case <-time.After(coretesting.LongWait): 587 c.Fatal("timed out sending model machines change") 588 } 589 } 590 591 func (s *ProvisionerTaskSuite) sendMachineErrorRetryChange(c *gc.C) { 592 select { 593 case s.machineErrorRetryChanges <- struct{}{}: 594 case <-time.After(coretesting.LongWait): 595 c.Fatal("timed out sending machine error retry change") 596 } 597 } 598 599 func (s *ProvisionerTaskSuite) newProvisionerTask( 600 c *gc.C, 601 harvestingMethod config.HarvestMode, 602 distributionGroupFinder provisioner.DistributionGroupFinder, 603 toolsFinder provisioner.ToolsFinder, 604 ) provisioner.ProvisionerTask { 605 return s.newProvisionerTaskWithRetry(c, 606 harvestingMethod, 607 distributionGroupFinder, 608 toolsFinder, 609 provisioner.NewRetryStrategy(0*time.Second, 0), 610 ) 611 } 612 613 func (s *ProvisionerTaskSuite) newProvisionerTaskWithRetry( 614 c *gc.C, 615 harvestingMethod config.HarvestMode, 616 distributionGroupFinder provisioner.DistributionGroupFinder, 617 toolsFinder provisioner.ToolsFinder, 618 retryStrategy provisioner.RetryStrategy, 619 ) provisioner.ProvisionerTask { 620 w, err := provisioner.NewProvisionerTask( 621 coretesting.ControllerTag.Id(), 622 names.NewMachineTag("0"), 623 harvestingMethod, 624 s.machineGetter, 625 distributionGroupFinder, 626 toolsFinder, 627 s.modelMachinesWatcher, 628 s.machineErrorRetryWatcher, 629 s.modelMachinesProfileWatcher, 630 s.instanceBroker, 631 s.auth, 632 imagemetadata.ReleasedStream, 633 retryStrategy, 634 s.callCtx, 635 ) 636 c.Assert(err, jc.ErrorIsNil) 637 return w 638 } 639 640 func (s *ProvisionerTaskSuite) newProvisionerTaskWithBroker( 641 c *gc.C, broker environs.InstanceBroker, distributionGroups map[names.MachineTag][]string, 642 ) provisioner.ProvisionerTask { 643 task, err := provisioner.NewProvisionerTask( 644 coretesting.ControllerTag.Id(), 645 names.NewMachineTag("0"), 646 config.HarvestAll, 647 s.machineGetter, 648 &mockDistributionGroupFinder{groups: distributionGroups}, 649 mockToolsFinder{}, 650 s.modelMachinesWatcher, 651 s.machineErrorRetryWatcher, 652 s.modelMachinesProfileWatcher, 653 broker, 654 s.auth, 655 imagemetadata.ReleasedStream, 656 provisioner.NewRetryStrategy(0*time.Second, 0), 657 s.callCtx, 658 ) 659 c.Assert(err, jc.ErrorIsNil) 660 return task 661 } 662 663 type testMachineGetter struct { 664 *testing.Stub 665 666 machinesFunc func(machines ...names.MachineTag) ([]apiprovisioner.MachineResult, error) 667 machinesWithTransientErrorsFunc func() ([]apiprovisioner.MachineStatusResult, error) 668 } 669 670 func (m *testMachineGetter) Machines(machines ...names.MachineTag) ([]apiprovisioner.MachineResult, error) { 671 m.AddCall("Machines", machines) 672 return m.machinesFunc(machines...) 673 } 674 675 func (m *testMachineGetter) MachinesWithTransientErrors() ([]apiprovisioner.MachineStatusResult, error) { 676 m.AddCall("MachinesWithTransientErrors") 677 return m.machinesWithTransientErrorsFunc() 678 } 679 680 type testInstanceBroker struct { 681 *testing.Stub 682 683 callsChan chan string 684 685 allInstancesFunc func(ctx context.ProviderCallContext) ([]instances.Instance, error) 686 } 687 688 func (t *testInstanceBroker) StartInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 689 t.AddCall("StartInstance", ctx, args) 690 t.callsChan <- "StartInstance" 691 return nil, t.NextErr() 692 } 693 694 func (t *testInstanceBroker) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error { 695 t.AddCall("StopInstances", ctx, ids) 696 t.callsChan <- "StopInstances" 697 return t.NextErr() 698 } 699 700 func (t *testInstanceBroker) AllInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) { 701 t.AddCall("AllInstances", ctx) 702 t.callsChan <- "AllInstances" 703 return t.allInstancesFunc(ctx) 704 } 705 706 func (t *testInstanceBroker) MaintainInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) error { 707 t.AddCall("MaintainInstance", ctx, args) 708 t.callsChan <- "MaintainInstance" 709 return nil 710 } 711 712 type testInstance struct { 713 instances.Instance 714 id string 715 } 716 717 func (i *testInstance) Id() instance.Id { 718 return instance.Id(i.id) 719 } 720 721 type testMachine struct { 722 mu sync.Mutex 723 724 *apiprovisioner.Machine 725 id string 726 life params.Life 727 728 instance *testInstance 729 keepInstance bool 730 731 markForRemoval bool 732 constraints string 733 734 instStatusMsg string 735 } 736 737 func (m *testMachine) Id() string { 738 return m.id 739 } 740 741 func (m *testMachine) String() string { 742 return m.Id() 743 } 744 745 func (m *testMachine) Life() params.Life { 746 return m.life 747 } 748 749 func (m *testMachine) InstanceId() (instance.Id, error) { 750 return m.instance.Id(), nil 751 } 752 753 func (m *testMachine) InstanceNames() (instance.Id, string, error) { 754 instId, err := m.InstanceId() 755 return instId, "", err 756 } 757 758 func (m *testMachine) KeepInstance() (bool, error) { 759 return m.keepInstance, nil 760 } 761 762 func (m *testMachine) MarkForRemoval() error { 763 m.markForRemoval = true 764 return nil 765 } 766 767 func (m *testMachine) Tag() names.Tag { 768 return m.MachineTag() 769 } 770 771 func (m *testMachine) MachineTag() names.MachineTag { 772 return names.NewMachineTag(m.id) 773 } 774 775 func (m *testMachine) SetInstanceStatus(_ status.Status, message string, _ map[string]interface{}) error { 776 m.mu.Lock() 777 m.instStatusMsg = message 778 m.mu.Unlock() 779 return nil 780 } 781 782 func (m *testMachine) InstanceStatus() (status.Status, string, error) { 783 m.mu.Lock() 784 defer m.mu.Unlock() 785 return status.Status(""), m.instStatusMsg, nil 786 } 787 788 func (m *testMachine) SetStatus(status status.Status, info string, data map[string]interface{}) error { 789 return nil 790 } 791 792 func (m *testMachine) Status() (status.Status, string, error) { 793 return status.Status(""), "", nil 794 } 795 796 func (m *testMachine) ModelAgentVersion() (*version.Number, error) { 797 return &coretesting.FakeVersionNumber, nil 798 } 799 800 func (m *testMachine) SetInstanceInfo( 801 id instance.Id, displayName string, nonce string, characteristics *instance.HardwareCharacteristics, 802 networkConfig []params.NetworkConfig, volumes []params.Volume, 803 volumeAttachments map[string]params.VolumeAttachmentInfo, charmProfiles []string, 804 ) error { 805 return nil 806 } 807 808 func (m *testMachine) ProvisioningInfo() (*params.ProvisioningInfo, error) { 809 return ¶ms.ProvisioningInfo{ 810 ControllerConfig: coretesting.FakeControllerConfig(), 811 Series: jujuversion.SupportedLTS(), 812 Constraints: constraints.MustParse(m.constraints), 813 }, nil 814 } 815 816 type testAuthenticationProvider struct { 817 *testing.Stub 818 } 819 820 func (m *testAuthenticationProvider) SetupAuthentication( 821 machine authentication.TaggedPasswordChanger, 822 ) (*mongo.MongoInfo, *api.Info, error) { 823 m.AddCall("SetupAuthentication", machine) 824 return nil, nil, nil 825 } 826 827 // startInstanceParamsMatcher is a GoMock matcher that applies a collection of 828 // conditions to an environs.StartInstanceParams. 829 // All conditions must be true in order for a positive match. 830 type startInstanceParamsMatcher struct { 831 matchers map[string]func(environs.StartInstanceParams) bool 832 failMsg string 833 } 834 835 func (m *startInstanceParamsMatcher) Matches(params interface{}) bool { 836 siParams := params.(environs.StartInstanceParams) 837 for msg, match := range m.matchers { 838 if !match(siParams) { 839 m.failMsg = msg 840 return false 841 } 842 } 843 return true 844 } 845 846 func (m *startInstanceParamsMatcher) String() string { 847 return m.failMsg 848 } 849 850 func (m *startInstanceParamsMatcher) addMatch(msg string, match func(environs.StartInstanceParams) bool) { 851 m.matchers[msg] = match 852 } 853 854 // newAZConstraintStartInstanceParamsMatcher returns a matcher that tests 855 // whether the candidate environs.StartInstanceParams has a constraints value 856 // that includes exactly the input zones. 857 func newAZConstraintStartInstanceParamsMatcher(zones ...string) *startInstanceParamsMatcher { 858 match := func(p environs.StartInstanceParams) bool { 859 if !p.Constraints.HasZones() { 860 return false 861 } 862 cZones := *p.Constraints.Zones 863 if len(cZones) != len(zones) { 864 return false 865 } 866 for _, z := range zones { 867 found := false 868 for _, cz := range cZones { 869 if z == cz { 870 found = true 871 break 872 } 873 } 874 if !found { 875 return false 876 } 877 } 878 return true 879 } 880 return newStartInstanceParamsMatcher(map[string]func(environs.StartInstanceParams) bool{ 881 fmt.Sprint("AZ constraints:", strings.Join(zones, ", ")): match, 882 }) 883 } 884 885 func newStartInstanceParamsMatcher( 886 matchers map[string]func(environs.StartInstanceParams) bool, 887 ) *startInstanceParamsMatcher { 888 if matchers == nil { 889 matchers = make(map[string]func(environs.StartInstanceParams) bool) 890 } 891 return &startInstanceParamsMatcher{matchers: matchers} 892 }