github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/provisioner/provisioner_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provisioner_test 5 6 import ( 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils" 14 "github.com/juju/utils/arch" 15 "github.com/juju/utils/series" 16 "github.com/juju/utils/set" 17 "github.com/juju/version" 18 gc "gopkg.in/check.v1" 19 "gopkg.in/juju/names.v2" 20 21 "github.com/juju/juju/agent" 22 "github.com/juju/juju/api" 23 apiprovisioner "github.com/juju/juju/api/provisioner" 24 "github.com/juju/juju/apiserver/params" 25 apiserverprovisioner "github.com/juju/juju/apiserver/provisioner" 26 "github.com/juju/juju/constraints" 27 "github.com/juju/juju/controller/authentication" 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/environs/config" 30 "github.com/juju/juju/environs/filestorage" 31 "github.com/juju/juju/environs/imagemetadata" 32 imagetesting "github.com/juju/juju/environs/imagemetadata/testing" 33 envtesting "github.com/juju/juju/environs/testing" 34 "github.com/juju/juju/environs/tools" 35 "github.com/juju/juju/instance" 36 "github.com/juju/juju/juju/testing" 37 "github.com/juju/juju/network" 38 "github.com/juju/juju/provider/dummy" 39 "github.com/juju/juju/state" 40 "github.com/juju/juju/state/cloudimagemetadata" 41 "github.com/juju/juju/state/multiwatcher" 42 "github.com/juju/juju/status" 43 "github.com/juju/juju/storage" 44 "github.com/juju/juju/storage/poolmanager" 45 coretesting "github.com/juju/juju/testing" 46 coretools "github.com/juju/juju/tools" 47 jujuversion "github.com/juju/juju/version" 48 "github.com/juju/juju/worker" 49 "github.com/juju/juju/worker/provisioner" 50 ) 51 52 type CommonProvisionerSuite struct { 53 testing.JujuConnSuite 54 op <-chan dummy.Operation 55 cfg *config.Config 56 // defaultConstraints are used when adding a machine and then later in test assertions. 57 defaultConstraints constraints.Value 58 59 st api.Connection 60 provisioner *apiprovisioner.State 61 } 62 63 func (s *CommonProvisionerSuite) assertProvisionerObservesConfigChanges(c *gc.C, p provisioner.Provisioner) { 64 // Inject our observer into the provisioner 65 cfgObserver := make(chan *config.Config, 1) 66 provisioner.SetObserver(p, cfgObserver) 67 68 // Switch to reaping on All machines. 69 attrs := map[string]interface{}{ 70 config.ProvisionerHarvestModeKey: config.HarvestAll.String(), 71 } 72 err := s.State.UpdateModelConfig(attrs, nil, nil) 73 c.Assert(err, jc.ErrorIsNil) 74 75 s.BackingState.StartSync() 76 77 // Wait for the PA to load the new configuration. We wait for the change we expect 78 // like this because sometimes we pick up the initial harvest config (destroyed) 79 // rather than the one we change to (all). 80 received := []string{} 81 timeout := time.After(coretesting.LongWait) 82 for { 83 select { 84 case newCfg := <-cfgObserver: 85 if newCfg.ProvisionerHarvestMode().String() == config.HarvestAll.String() { 86 return 87 } 88 received = append(received, newCfg.ProvisionerHarvestMode().String()) 89 case <-time.After(coretesting.ShortWait): 90 s.BackingState.StartSync() 91 case <-timeout: 92 if len(received) == 0 { 93 c.Fatalf("PA did not action config change") 94 } else { 95 c.Fatalf("timed out waiting for config to change to '%s', received %+v", 96 config.HarvestAll.String(), received) 97 } 98 } 99 } 100 } 101 102 type ProvisionerSuite struct { 103 CommonProvisionerSuite 104 } 105 106 var _ = gc.Suite(&ProvisionerSuite{}) 107 108 func (s *CommonProvisionerSuite) SetUpSuite(c *gc.C) { 109 s.JujuConnSuite.SetUpSuite(c) 110 s.defaultConstraints = constraints.MustParse("arch=amd64 mem=4G cores=1 root-disk=8G") 111 } 112 113 func (s *CommonProvisionerSuite) SetUpTest(c *gc.C) { 114 s.JujuConnSuite.SetUpTest(c) 115 116 // We do not want to pull published image metadata for tests... 117 imagetesting.PatchOfficialDataSources(&s.CleanupSuite, "") 118 // We want an image to start test instances 119 err := s.State.CloudImageMetadataStorage.SaveMetadata([]cloudimagemetadata.Metadata{{ 120 MetadataAttributes: cloudimagemetadata.MetadataAttributes{ 121 Region: "region", 122 Series: "trusty", 123 Arch: "amd64", 124 VirtType: "", 125 RootStorageType: "", 126 Source: "test", 127 Stream: "released", 128 }, 129 Priority: 10, 130 ImageId: "-999", 131 }}) 132 c.Assert(err, jc.ErrorIsNil) 133 134 // Create the operations channel with more than enough space 135 // for those tests that don't listen on it. 136 op := make(chan dummy.Operation, 500) 137 dummy.Listen(op) 138 s.op = op 139 140 cfg, err := s.State.ModelConfig() 141 c.Assert(err, jc.ErrorIsNil) 142 s.cfg = cfg 143 144 // Create a machine for the dummy bootstrap instance, 145 // so the provisioner doesn't destroy it. 146 insts, err := s.Environ.Instances([]instance.Id{dummy.BootstrapInstanceId}) 147 c.Assert(err, jc.ErrorIsNil) 148 addrs, err := insts[0].Addresses() 149 c.Assert(err, jc.ErrorIsNil) 150 machine, err := s.State.AddOneMachine(state.MachineTemplate{ 151 Addresses: addrs, 152 Series: "quantal", 153 Nonce: agent.BootstrapNonce, 154 InstanceId: dummy.BootstrapInstanceId, 155 Jobs: []state.MachineJob{state.JobManageModel}, 156 }) 157 c.Assert(err, jc.ErrorIsNil) 158 c.Assert(machine.Id(), gc.Equals, "0") 159 160 current := version.Binary{ 161 Number: jujuversion.Current, 162 Arch: arch.HostArch(), 163 Series: series.HostSeries(), 164 } 165 err = machine.SetAgentVersion(current) 166 c.Assert(err, jc.ErrorIsNil) 167 168 password, err := utils.RandomPassword() 169 c.Assert(err, jc.ErrorIsNil) 170 err = machine.SetPassword(password) 171 c.Assert(err, jc.ErrorIsNil) 172 173 s.st = s.OpenAPIAsMachine(c, machine.Tag(), password, agent.BootstrapNonce) 174 c.Assert(s.st, gc.NotNil) 175 c.Logf("API: login as %q successful", machine.Tag()) 176 s.provisioner = apiprovisioner.NewState(s.st) 177 c.Assert(s.provisioner, gc.NotNil) 178 } 179 180 // stop stops a Worker. 181 func stop(c *gc.C, w worker.Worker) { 182 c.Assert(worker.Stop(w), jc.ErrorIsNil) 183 } 184 185 func (s *CommonProvisionerSuite) startUnknownInstance(c *gc.C, id string) instance.Instance { 186 instance, _ := testing.AssertStartInstance(c, s.Environ, s.ControllerConfig.ControllerUUID(), id) 187 select { 188 case o := <-s.op: 189 switch o := o.(type) { 190 case dummy.OpStartInstance: 191 default: 192 c.Fatalf("unexpected operation %#v", o) 193 } 194 case <-time.After(coretesting.LongWait): 195 c.Fatalf("timed out waiting for startinstance operation") 196 } 197 return instance 198 } 199 200 func (s *CommonProvisionerSuite) checkStartInstance(c *gc.C, m *state.Machine) instance.Instance { 201 return s.checkStartInstanceCustom(c, m, "pork", s.defaultConstraints, nil, nil, nil, nil, true) 202 } 203 204 func (s *CommonProvisionerSuite) checkStartInstanceCustom( 205 c *gc.C, m *state.Machine, 206 secret string, cons constraints.Value, 207 networkInfo []network.InterfaceInfo, 208 subnetsToZones map[network.Id][]string, 209 volumes []storage.Volume, 210 checkPossibleTools coretools.List, 211 waitInstanceId bool, 212 ) ( 213 inst instance.Instance, 214 ) { 215 s.BackingState.StartSync() 216 for { 217 select { 218 case o := <-s.op: 219 switch o := o.(type) { 220 case dummy.OpStartInstance: 221 inst = o.Instance 222 if waitInstanceId { 223 s.waitInstanceId(c, m, inst.Id()) 224 } 225 226 // Check the instance was started with the expected params. 227 c.Assert(o.MachineId, gc.Equals, m.Id()) 228 nonceParts := strings.SplitN(o.MachineNonce, ":", 2) 229 c.Assert(nonceParts, gc.HasLen, 2) 230 c.Assert(nonceParts[0], gc.Equals, names.NewMachineTag("0").String()) 231 c.Assert(nonceParts[1], jc.Satisfies, utils.IsValidUUIDString) 232 c.Assert(o.Secret, gc.Equals, secret) 233 c.Assert(o.SubnetsToZones, jc.DeepEquals, subnetsToZones) 234 c.Assert(o.NetworkInfo, jc.DeepEquals, networkInfo) 235 c.Assert(o.Volumes, jc.DeepEquals, volumes) 236 237 var jobs []multiwatcher.MachineJob 238 for _, job := range m.Jobs() { 239 jobs = append(jobs, job.ToParams()) 240 } 241 c.Assert(o.Jobs, jc.SameContents, jobs) 242 243 if checkPossibleTools != nil { 244 for _, t := range o.PossibleTools { 245 url := fmt.Sprintf("https://%s/model/%s/tools/%s", 246 s.st.Addr(), coretesting.ModelTag.Id(), t.Version) 247 c.Check(t.URL, gc.Equals, url) 248 t.URL = "" 249 } 250 for _, t := range checkPossibleTools { 251 t.URL = "" 252 } 253 c.Assert(o.PossibleTools, gc.DeepEquals, checkPossibleTools) 254 } 255 256 // All provisioned machines in this test suite have 257 // their hardware characteristics attributes set to 258 // the same values as the constraints due to the dummy 259 // environment being used. 260 if !constraints.IsEmpty(&cons) { 261 c.Assert(o.Constraints, gc.DeepEquals, cons) 262 hc, err := m.HardwareCharacteristics() 263 c.Assert(err, jc.ErrorIsNil) 264 c.Assert(*hc, gc.DeepEquals, instance.HardwareCharacteristics{ 265 Arch: cons.Arch, 266 Mem: cons.Mem, 267 RootDisk: cons.RootDisk, 268 CpuCores: cons.CpuCores, 269 CpuPower: cons.CpuPower, 270 Tags: cons.Tags, 271 }) 272 } 273 return 274 default: 275 c.Logf("ignoring unexpected operation %#v", o) 276 } 277 case <-time.After(2 * time.Second): 278 c.Fatalf("provisioner did not start an instance") 279 return 280 } 281 } 282 } 283 284 // checkNoOperations checks that the environ was not operated upon. 285 func (s *CommonProvisionerSuite) checkNoOperations(c *gc.C) { 286 s.BackingState.StartSync() 287 select { 288 case o := <-s.op: 289 c.Fatalf("unexpected operation %+v", o) 290 case <-time.After(coretesting.ShortWait): 291 return 292 } 293 } 294 295 // checkStopInstances checks that an instance has been stopped. 296 func (s *CommonProvisionerSuite) checkStopInstances(c *gc.C, instances ...instance.Instance) { 297 s.checkStopSomeInstances(c, instances, nil) 298 } 299 300 // checkStopSomeInstances checks that instancesToStop are stopped while instancesToKeep are not. 301 func (s *CommonProvisionerSuite) checkStopSomeInstances(c *gc.C, 302 instancesToStop []instance.Instance, instancesToKeep []instance.Instance) { 303 304 s.BackingState.StartSync() 305 instanceIdsToStop := set.NewStrings() 306 for _, instance := range instancesToStop { 307 instanceIdsToStop.Add(string(instance.Id())) 308 } 309 instanceIdsToKeep := set.NewStrings() 310 for _, instance := range instancesToKeep { 311 instanceIdsToKeep.Add(string(instance.Id())) 312 } 313 // Continue checking for stop instance calls until all the instances we 314 // are waiting on to finish, actually finish, or we time out. 315 for !instanceIdsToStop.IsEmpty() { 316 select { 317 case o := <-s.op: 318 switch o := o.(type) { 319 case dummy.OpStopInstances: 320 for _, id := range o.Ids { 321 instId := string(id) 322 instanceIdsToStop.Remove(instId) 323 if instanceIdsToKeep.Contains(instId) { 324 c.Errorf("provisioner unexpectedly stopped instance %s", instId) 325 } 326 } 327 default: 328 c.Fatalf("unexpected operation %#v", o) 329 return 330 } 331 case <-time.After(2 * time.Second): 332 c.Fatalf("provisioner did not stop an instance") 333 return 334 } 335 } 336 } 337 338 func (s *CommonProvisionerSuite) waitForWatcher(c *gc.C, w state.NotifyWatcher, name string, check func() bool) { 339 // TODO(jam): We need to grow a new method on NotifyWatcherC 340 // that calls StartSync while waiting for changes, then 341 // waitMachine and waitHardwareCharacteristics can use that 342 // instead 343 defer stop(c, w) 344 timeout := time.After(coretesting.LongWait) 345 resync := time.After(0) 346 for { 347 select { 348 case <-w.Changes(): 349 if check() { 350 return 351 } 352 case <-resync: 353 resync = time.After(coretesting.ShortWait) 354 s.BackingState.StartSync() 355 case <-timeout: 356 c.Fatalf("%v wait timed out", name) 357 } 358 } 359 } 360 361 func (s *CommonProvisionerSuite) waitHardwareCharacteristics(c *gc.C, m *state.Machine, check func() bool) { 362 w := m.WatchHardwareCharacteristics() 363 name := fmt.Sprintf("hardware characteristics for machine %v", m) 364 s.waitForWatcher(c, w, name, check) 365 } 366 367 // waitForRemovalMark waits for the supplied machine to be marked for removal. 368 func (s *CommonProvisionerSuite) waitForRemovalMark(c *gc.C, m *state.Machine) { 369 w := s.BackingState.WatchMachineRemovals() 370 name := fmt.Sprintf("machine %v marked for removal", m) 371 s.waitForWatcher(c, w, name, func() bool { 372 removals, err := s.BackingState.AllMachineRemovals() 373 c.Assert(err, jc.ErrorIsNil) 374 for _, removal := range removals { 375 if removal == m.Id() { 376 return true 377 } 378 } 379 return false 380 }) 381 } 382 383 // waitInstanceId waits until the supplied machine has an instance id, then 384 // asserts it is as expected. 385 func (s *CommonProvisionerSuite) waitInstanceId(c *gc.C, m *state.Machine, expect instance.Id) { 386 s.waitHardwareCharacteristics(c, m, func() bool { 387 if actual, err := m.InstanceId(); err == nil { 388 c.Assert(actual, gc.Equals, expect) 389 return true 390 } else if !errors.IsNotProvisioned(err) { 391 // We don't expect any errors. 392 panic(err) 393 } 394 c.Logf("machine %v is still unprovisioned", m) 395 return false 396 }) 397 } 398 399 func (s *CommonProvisionerSuite) newEnvironProvisioner(c *gc.C) provisioner.Provisioner { 400 machineTag := names.NewMachineTag("0") 401 agentConfig := s.AgentConfigForTag(c, machineTag) 402 apiState := apiprovisioner.NewState(s.st) 403 w, err := provisioner.NewEnvironProvisioner(apiState, agentConfig, s.Environ) 404 c.Assert(err, jc.ErrorIsNil) 405 return w 406 } 407 408 func (s *CommonProvisionerSuite) addMachine() (*state.Machine, error) { 409 return s.addMachineWithConstraints(s.defaultConstraints) 410 } 411 412 func (s *CommonProvisionerSuite) addMachineWithConstraints(cons constraints.Value) (*state.Machine, error) { 413 return s.BackingState.AddOneMachine(state.MachineTemplate{ 414 Series: series.LatestLts(), 415 Jobs: []state.MachineJob{state.JobHostUnits}, 416 Constraints: cons, 417 }) 418 } 419 420 func (s *CommonProvisionerSuite) enableHA(c *gc.C, n int) []*state.Machine { 421 changes, err := s.BackingState.EnableHA(n, s.defaultConstraints, series.LatestLts(), nil) 422 c.Assert(err, jc.ErrorIsNil) 423 added := make([]*state.Machine, len(changes.Added)) 424 for i, mid := range changes.Added { 425 m, err := s.BackingState.Machine(mid) 426 c.Assert(err, jc.ErrorIsNil) 427 added[i] = m 428 } 429 return added 430 } 431 432 func (s *ProvisionerSuite) TestProvisionerStartStop(c *gc.C) { 433 p := s.newEnvironProvisioner(c) 434 stop(c, p) 435 } 436 437 func (s *ProvisionerSuite) TestSimple(c *gc.C) { 438 p := s.newEnvironProvisioner(c) 439 defer stop(c, p) 440 441 // Check that an instance is provisioned when the machine is created... 442 m, err := s.addMachine() 443 c.Assert(err, jc.ErrorIsNil) 444 instance := s.checkStartInstance(c, m) 445 446 // ...and removed, along with the machine, when the machine is Dead. 447 c.Assert(m.EnsureDead(), gc.IsNil) 448 s.checkStopInstances(c, instance) 449 s.waitForRemovalMark(c, m) 450 } 451 452 func (s *ProvisionerSuite) TestConstraints(c *gc.C) { 453 // Create a machine with non-standard constraints. 454 m, err := s.addMachine() 455 c.Assert(err, jc.ErrorIsNil) 456 cons := constraints.MustParse("mem=8G arch=amd64 cores=2 root-disk=10G") 457 err = m.SetConstraints(cons) 458 c.Assert(err, jc.ErrorIsNil) 459 460 // Start a provisioner and check those constraints are used. 461 p := s.newEnvironProvisioner(c) 462 defer stop(c, p) 463 s.checkStartInstanceCustom(c, m, "pork", cons, nil, nil, nil, nil, true) 464 } 465 466 func (s *ProvisionerSuite) TestPossibleTools(c *gc.C) { 467 468 storageDir := c.MkDir() 469 s.PatchValue(&tools.DefaultBaseURL, storageDir) 470 stor, err := filestorage.NewFileStorageWriter(storageDir) 471 c.Assert(err, jc.ErrorIsNil) 472 473 // Set a current version that does not match the 474 // agent-version in the environ config. 475 currentVersion := version.MustParseBinary("1.2.3-quantal-arm64") 476 s.PatchValue(&arch.HostArch, func() string { return currentVersion.Arch }) 477 s.PatchValue(&series.HostSeries, func() string { return currentVersion.Series }) 478 s.PatchValue(&jujuversion.Current, currentVersion.Number) 479 480 // Upload some plausible matches, and some that should be filtered out. 481 compatibleVersion := version.MustParseBinary("1.2.3-quantal-amd64") 482 ignoreVersion1 := version.MustParseBinary("1.2.4-quantal-arm64") 483 ignoreVersion2 := version.MustParseBinary("1.2.3-precise-arm64") 484 availableVersions := []version.Binary{ 485 currentVersion, compatibleVersion, ignoreVersion1, ignoreVersion2, 486 } 487 envtesting.AssertUploadFakeToolsVersions(c, stor, s.cfg.AgentStream(), s.cfg.AgentStream(), availableVersions...) 488 489 // Extract the tools that we expect to actually match. 490 expectedList, err := tools.FindTools(s.Environ, -1, -1, s.cfg.AgentStream(), coretools.Filter{ 491 Number: currentVersion.Number, 492 Series: currentVersion.Series, 493 }) 494 c.Assert(err, jc.ErrorIsNil) 495 496 // Create the machine and check the tools that get passed into StartInstance. 497 machine, err := s.BackingState.AddOneMachine(state.MachineTemplate{ 498 Series: "quantal", 499 Jobs: []state.MachineJob{state.JobHostUnits}, 500 }) 501 c.Assert(err, jc.ErrorIsNil) 502 503 provisioner := s.newEnvironProvisioner(c) 504 defer stop(c, provisioner) 505 s.checkStartInstanceCustom( 506 c, machine, "pork", constraints.Value{}, 507 nil, nil, nil, expectedList, true, 508 ) 509 } 510 511 func (s *ProvisionerSuite) TestProvisionerSetsErrorStatusWhenNoToolsAreAvailable(c *gc.C) { 512 p := s.newEnvironProvisioner(c) 513 defer stop(c, p) 514 515 // Check that an instance is not provisioned when the machine is created... 516 m, err := s.BackingState.AddOneMachine(state.MachineTemplate{ 517 // We need a valid series that has no tools uploaded 518 Series: "raring", 519 Jobs: []state.MachineJob{state.JobHostUnits}, 520 Constraints: s.defaultConstraints, 521 }) 522 c.Assert(err, jc.ErrorIsNil) 523 s.checkNoOperations(c) 524 525 t0 := time.Now() 526 for time.Since(t0) < coretesting.LongWait { 527 // And check the machine status is set to error. 528 statusInfo, err := m.Status() 529 c.Assert(err, jc.ErrorIsNil) 530 if statusInfo.Status == status.Pending { 531 time.Sleep(coretesting.ShortWait) 532 continue 533 } 534 c.Assert(statusInfo.Status, gc.Equals, status.Error) 535 c.Assert(statusInfo.Message, gc.Equals, "no matching tools available") 536 break 537 } 538 539 // Restart the PA to make sure the machine is skipped again. 540 stop(c, p) 541 p = s.newEnvironProvisioner(c) 542 defer stop(c, p) 543 s.checkNoOperations(c) 544 } 545 546 func (s *ProvisionerSuite) TestProvisionerFailedStartInstanceWithInjectedCreationError(c *gc.C) { 547 // Set the retry delay to 0, and retry count to 2 to keep tests short 548 s.PatchValue(provisioner.RetryStrategyDelay, 0*time.Second) 549 s.PatchValue(provisioner.RetryStrategyCount, 2) 550 551 // create the error injection channel 552 errorInjectionChannel := make(chan error, 3) 553 554 p := s.newEnvironProvisioner(c) 555 defer stop(c, p) 556 557 // patch the dummy provider error injection channel 558 cleanup := dummy.PatchTransientErrorInjectionChannel(errorInjectionChannel) 559 defer cleanup() 560 561 retryableError := errors.New("container failed to start and was destroyed") 562 destroyError := errors.New("container failed to start and failed to destroy: manual cleanup of containers needed") 563 // send the error message three times, because the provisioner will retry twice as patched above. 564 errorInjectionChannel <- retryableError 565 errorInjectionChannel <- retryableError 566 errorInjectionChannel <- destroyError 567 568 m, err := s.addMachine() 569 c.Assert(err, jc.ErrorIsNil) 570 s.checkNoOperations(c) 571 572 t0 := time.Now() 573 for time.Since(t0) < coretesting.LongWait { 574 // And check the machine status is set to error. 575 statusInfo, err := m.Status() 576 c.Assert(err, jc.ErrorIsNil) 577 if statusInfo.Status == status.Pending { 578 time.Sleep(coretesting.ShortWait) 579 continue 580 } 581 c.Assert(statusInfo.Status, gc.Equals, status.Error) 582 // check that the status matches the error message 583 c.Assert(statusInfo.Message, gc.Equals, destroyError.Error()) 584 return 585 } 586 c.Fatal("Test took too long to complete") 587 } 588 589 func (s *ProvisionerSuite) TestProvisionerSucceedStartInstanceWithInjectedRetryableCreationError(c *gc.C) { 590 // Set the retry delay to 0, and retry count to 2 to keep tests short 591 s.PatchValue(provisioner.RetryStrategyDelay, 0*time.Second) 592 s.PatchValue(provisioner.RetryStrategyCount, 2) 593 594 // create the error injection channel 595 errorInjectionChannel := make(chan error, 1) 596 c.Assert(errorInjectionChannel, gc.NotNil) 597 598 p := s.newEnvironProvisioner(c) 599 defer stop(c, p) 600 601 // patch the dummy provider error injection channel 602 cleanup := dummy.PatchTransientErrorInjectionChannel(errorInjectionChannel) 603 defer cleanup() 604 605 // send the error message once 606 // - instance creation should succeed 607 retryableError := errors.New("container failed to start and was destroyed") 608 errorInjectionChannel <- retryableError 609 610 m, err := s.addMachine() 611 c.Assert(err, jc.ErrorIsNil) 612 s.checkStartInstance(c, m) 613 } 614 615 func (s *ProvisionerSuite) TestProvisionerStopRetryingIfDying(c *gc.C) { 616 // Create the error injection channel and inject 617 // a retryable error 618 errorInjectionChannel := make(chan error, 1) 619 620 p := s.newEnvironProvisioner(c) 621 // Don't refer the stop. We will manually stop and verify the result. 622 623 // patch the dummy provider error injection channel 624 cleanup := dummy.PatchTransientErrorInjectionChannel(errorInjectionChannel) 625 defer cleanup() 626 627 retryableError := errors.New("container failed to start and was destroyed") 628 errorInjectionChannel <- retryableError 629 630 m, err := s.addMachine() 631 c.Assert(err, jc.ErrorIsNil) 632 633 time.Sleep(coretesting.ShortWait) 634 635 stop(c, p) 636 statusInfo, err := m.Status() 637 c.Assert(err, jc.ErrorIsNil) 638 c.Assert(statusInfo.Status, gc.Equals, status.Pending) 639 s.checkNoOperations(c) 640 } 641 642 func (s *ProvisionerSuite) TestProvisioningDoesNotOccurForLXD(c *gc.C) { 643 p := s.newEnvironProvisioner(c) 644 defer stop(c, p) 645 646 // create a machine to host the container. 647 m, err := s.addMachine() 648 c.Assert(err, jc.ErrorIsNil) 649 inst := s.checkStartInstance(c, m) 650 651 // make a container on the machine we just created 652 template := state.MachineTemplate{ 653 Series: series.LatestLts(), 654 Jobs: []state.MachineJob{state.JobHostUnits}, 655 } 656 container, err := s.State.AddMachineInsideMachine(template, m.Id(), instance.LXD) 657 c.Assert(err, jc.ErrorIsNil) 658 659 // the PA should not attempt to create it 660 s.checkNoOperations(c) 661 662 // cleanup 663 c.Assert(container.EnsureDead(), gc.IsNil) 664 c.Assert(container.Remove(), gc.IsNil) 665 c.Assert(m.EnsureDead(), gc.IsNil) 666 s.checkStopInstances(c, inst) 667 s.waitForRemovalMark(c, m) 668 } 669 670 func (s *ProvisionerSuite) TestProvisioningDoesNotOccurForKVM(c *gc.C) { 671 p := s.newEnvironProvisioner(c) 672 defer stop(c, p) 673 674 // create a machine to host the container. 675 m, err := s.addMachine() 676 c.Assert(err, jc.ErrorIsNil) 677 inst := s.checkStartInstance(c, m) 678 679 // make a container on the machine we just created 680 template := state.MachineTemplate{ 681 Series: series.LatestLts(), 682 Jobs: []state.MachineJob{state.JobHostUnits}, 683 } 684 container, err := s.State.AddMachineInsideMachine(template, m.Id(), instance.KVM) 685 c.Assert(err, jc.ErrorIsNil) 686 687 // the PA should not attempt to create it 688 s.checkNoOperations(c) 689 690 // cleanup 691 c.Assert(container.EnsureDead(), gc.IsNil) 692 c.Assert(container.Remove(), gc.IsNil) 693 c.Assert(m.EnsureDead(), gc.IsNil) 694 s.checkStopInstances(c, inst) 695 s.waitForRemovalMark(c, m) 696 } 697 698 type MachineClassifySuite struct { 699 } 700 701 var _ = gc.Suite(&MachineClassifySuite{}) 702 703 type MockMachine struct { 704 life params.Life 705 status status.Status 706 id string 707 idErr error 708 ensureDeadErr error 709 statusErr error 710 } 711 712 func (m *MockMachine) Life() params.Life { 713 return m.life 714 } 715 716 func (m *MockMachine) InstanceId() (instance.Id, error) { 717 return instance.Id(m.id), m.idErr 718 } 719 720 func (m *MockMachine) EnsureDead() error { 721 return m.ensureDeadErr 722 } 723 724 func (m *MockMachine) Status() (status.Status, string, error) { 725 return m.status, "", m.statusErr 726 } 727 728 func (m *MockMachine) Id() string { 729 return m.id 730 } 731 732 type machineClassificationTest struct { 733 description string 734 life params.Life 735 status status.Status 736 idErr string 737 ensureDeadErr string 738 expectErrCode string 739 expectErrFmt string 740 statusErr string 741 classification provisioner.MachineClassification 742 } 743 744 var machineClassificationTests = []machineClassificationTest{{ 745 description: "Dead machine is dead", 746 life: params.Dead, 747 status: status.Started, 748 classification: provisioner.Dead, 749 }, { 750 description: "Dying machine can carry on dying", 751 life: params.Dying, 752 status: status.Started, 753 classification: provisioner.None, 754 }, { 755 description: "Dying unprovisioned machine is ensured dead", 756 life: params.Dying, 757 status: status.Started, 758 classification: provisioner.Dead, 759 idErr: params.CodeNotProvisioned, 760 }, { 761 description: "Can't load provisioned dying machine", 762 life: params.Dying, 763 status: status.Started, 764 classification: provisioner.None, 765 idErr: params.CodeNotFound, 766 expectErrCode: params.CodeNotFound, 767 expectErrFmt: "failed to load dying machine id:%s.*", 768 }, { 769 description: "Alive machine is not provisioned - pending", 770 life: params.Alive, 771 status: status.Pending, 772 classification: provisioner.Pending, 773 idErr: params.CodeNotProvisioned, 774 expectErrFmt: "found machine pending provisioning id:%s.*", 775 }, { 776 description: "Alive, pending machine not found", 777 life: params.Alive, 778 status: status.Pending, 779 classification: provisioner.None, 780 idErr: params.CodeNotFound, 781 expectErrCode: params.CodeNotFound, 782 expectErrFmt: "failed to load machine id:%s.*", 783 }, { 784 description: "Cannot get unprovisioned machine status", 785 life: params.Alive, 786 classification: provisioner.None, 787 statusErr: params.CodeNotFound, 788 idErr: params.CodeNotProvisioned, 789 }, { 790 description: "Dying machine fails to ensure dead", 791 life: params.Dying, 792 status: status.Started, 793 classification: provisioner.None, 794 idErr: params.CodeNotProvisioned, 795 expectErrCode: params.CodeNotFound, 796 ensureDeadErr: params.CodeNotFound, 797 expectErrFmt: "failed to ensure machine dead id:%s.*", 798 }} 799 800 var machineClassificationTestsRequireMaintenance = machineClassificationTest{ 801 description: "Machine needs maintaining", 802 life: params.Alive, 803 status: status.Started, 804 classification: provisioner.Maintain, 805 } 806 807 var machineClassificationTestsNoMaintenance = machineClassificationTest{ 808 description: "Machine doesn't need maintaining", 809 life: params.Alive, 810 status: status.Started, 811 classification: provisioner.None, 812 } 813 814 func (s *MachineClassifySuite) TestMachineClassification(c *gc.C) { 815 test := func(t machineClassificationTest, id string) { 816 // Run a sub-test from the test table 817 s2e := func(s string) error { 818 // Little helper to turn a non-empty string into a useful error for "ErrorMaches" 819 if s != "" { 820 return ¶ms.Error{Code: s} 821 } 822 return nil 823 } 824 825 c.Logf("%s: %s", id, t.description) 826 machine := MockMachine{t.life, t.status, id, s2e(t.idErr), s2e(t.ensureDeadErr), s2e(t.statusErr)} 827 classification, err := provisioner.ClassifyMachine(&machine) 828 if err != nil { 829 c.Assert(err, gc.ErrorMatches, fmt.Sprintf(t.expectErrFmt, machine.Id())) 830 } else { 831 c.Assert(err, gc.Equals, s2e(t.expectErrCode)) 832 } 833 c.Assert(classification, gc.Equals, t.classification) 834 } 835 836 machineIds := []string{"0/kvm/0", "0"} 837 for _, id := range machineIds { 838 tests := machineClassificationTests 839 if id == "0" { 840 tests = append(tests, machineClassificationTestsNoMaintenance) 841 } else { 842 tests = append(tests, machineClassificationTestsRequireMaintenance) 843 } 844 for _, t := range tests { 845 test(t, id) 846 } 847 } 848 } 849 850 func (s *ProvisionerSuite) TestProvisioningMachinesWithSpacesSuccess(c *gc.C) { 851 p := s.newEnvironProvisioner(c) 852 defer stop(c, p) 853 854 // Add the spaces used in constraints. 855 _, err := s.State.AddSpace("space1", "", nil, false) 856 c.Assert(err, jc.ErrorIsNil) 857 _, err = s.State.AddSpace("space2", "", nil, false) 858 c.Assert(err, jc.ErrorIsNil) 859 860 // Add 1 subnet into space1, and 2 into space2. 861 // Each subnet is in a matching zone (e.g "subnet-#" in "zone#"). 862 testing.AddSubnetsWithTemplate(c, s.State, 3, state.SubnetInfo{ 863 CIDR: "10.10.{{.}}.0/24", 864 ProviderId: "subnet-{{.}}", 865 AvailabilityZone: "zone{{.}}", 866 SpaceName: "{{if (eq . 0)}}space1{{else}}space2{{end}}", 867 VLANTag: 42, 868 }) 869 870 // Add and provision a machine with spaces specified. 871 cons := constraints.MustParse( 872 s.defaultConstraints.String(), "spaces=space2,^space1", 873 ) 874 // The dummy provider simulates 2 subnets per included space. 875 expectedSubnetsToZones := map[network.Id][]string{ 876 "subnet-0": []string{"zone0"}, 877 "subnet-1": []string{"zone1"}, 878 } 879 m, err := s.addMachineWithConstraints(cons) 880 c.Assert(err, jc.ErrorIsNil) 881 inst := s.checkStartInstanceCustom( 882 c, m, "pork", cons, 883 nil, 884 expectedSubnetsToZones, 885 nil, nil, true, 886 ) 887 888 // Cleanup. 889 c.Assert(m.EnsureDead(), gc.IsNil) 890 s.checkStopInstances(c, inst) 891 s.waitForRemovalMark(c, m) 892 } 893 894 func (s *ProvisionerSuite) testProvisioningFailsAndSetsErrorStatusForConstraints( 895 c *gc.C, 896 cons constraints.Value, 897 expectedErrorStatus string, 898 ) { 899 machine, err := s.addMachineWithConstraints(cons) 900 c.Assert(err, jc.ErrorIsNil) 901 902 // Start the PA. 903 p := s.newEnvironProvisioner(c) 904 defer stop(c, p) 905 906 // Expect StartInstance to fail. 907 s.checkNoOperations(c) 908 909 // Ensure machine error status was set. 910 t0 := time.Now() 911 for time.Since(t0) < coretesting.LongWait { 912 statusInfo, err := machine.Status() 913 c.Assert(err, jc.ErrorIsNil) 914 if statusInfo.Status == status.Pending { 915 time.Sleep(coretesting.ShortWait) 916 continue 917 } 918 c.Assert(statusInfo.Status, gc.Equals, status.Error) 919 c.Assert(statusInfo.Message, gc.Equals, expectedErrorStatus) 920 break 921 } 922 923 // Make sure the task didn't stop with an error 924 died := make(chan error) 925 go func() { 926 died <- p.Wait() 927 }() 928 select { 929 case <-time.After(coretesting.ShortWait): 930 case err := <-died: 931 c.Fatalf("provisioner task died unexpectedly with err: %v", err) 932 } 933 934 // Restart the PA to make sure the machine is not retried. 935 stop(c, p) 936 p = s.newEnvironProvisioner(c) 937 defer stop(c, p) 938 939 s.checkNoOperations(c) 940 } 941 942 func (s *ProvisionerSuite) TestProvisioningMachinesFailsWithUnknownSpaces(c *gc.C) { 943 cons := constraints.MustParse( 944 s.defaultConstraints.String(), "spaces=missing,ignored,^ignored-too", 945 ) 946 expectedErrorStatus := `cannot match subnets to zones: space "missing" not found` 947 s.testProvisioningFailsAndSetsErrorStatusForConstraints(c, cons, expectedErrorStatus) 948 } 949 950 func (s *ProvisionerSuite) TestProvisioningMachinesFailsWithEmptySpaces(c *gc.C) { 951 _, err := s.State.AddSpace("empty", "", nil, false) 952 c.Assert(err, jc.ErrorIsNil) 953 cons := constraints.MustParse( 954 s.defaultConstraints.String(), "spaces=empty", 955 ) 956 expectedErrorStatus := `cannot match subnets to zones: ` + 957 `cannot use space "empty" as deployment target: no subnets` 958 s.testProvisioningFailsAndSetsErrorStatusForConstraints(c, cons, expectedErrorStatus) 959 } 960 961 func (s *CommonProvisionerSuite) addMachineWithRequestedVolumes(volumes []state.MachineVolumeParams, cons constraints.Value) (*state.Machine, error) { 962 return s.BackingState.AddOneMachine(state.MachineTemplate{ 963 Series: series.LatestLts(), 964 Jobs: []state.MachineJob{state.JobHostUnits}, 965 Constraints: cons, 966 Volumes: volumes, 967 }) 968 } 969 970 func (s *ProvisionerSuite) TestProvisioningMachinesWithRequestedVolumes(c *gc.C) { 971 // Set up a persistent pool. 972 poolManager := poolmanager.New(state.NewStateSettings(s.State), s.Environ) 973 _, err := poolManager.Create("persistent-pool", "static", map[string]interface{}{"persistent": true}) 974 c.Assert(err, jc.ErrorIsNil) 975 976 p := s.newEnvironProvisioner(c) 977 defer stop(c, p) 978 979 // Add and provision a machine with volumes specified. 980 requestedVolumes := []state.MachineVolumeParams{{ 981 Volume: state.VolumeParams{Pool: "static", Size: 1024}, 982 Attachment: state.VolumeAttachmentParams{}, 983 }, { 984 Volume: state.VolumeParams{Pool: "persistent-pool", Size: 2048}, 985 Attachment: state.VolumeAttachmentParams{}, 986 }} 987 expectVolumeInfo := []storage.Volume{{ 988 names.NewVolumeTag("1"), 989 storage.VolumeInfo{ 990 Size: 1024, 991 }, 992 }, { 993 names.NewVolumeTag("2"), 994 storage.VolumeInfo{ 995 Size: 2048, 996 Persistent: true, 997 }, 998 }} 999 m, err := s.addMachineWithRequestedVolumes(requestedVolumes, s.defaultConstraints) 1000 c.Assert(err, jc.ErrorIsNil) 1001 inst := s.checkStartInstanceCustom( 1002 c, m, "pork", s.defaultConstraints, 1003 nil, nil, 1004 expectVolumeInfo, 1005 nil, true, 1006 ) 1007 1008 // Cleanup. 1009 c.Assert(m.EnsureDead(), gc.IsNil) 1010 s.checkStopInstances(c, inst) 1011 s.waitForRemovalMark(c, m) 1012 } 1013 1014 func (s *ProvisionerSuite) TestProvisioningDoesNotProvisionTheSameMachineAfterRestart(c *gc.C) { 1015 p := s.newEnvironProvisioner(c) 1016 defer stop(c, p) 1017 1018 // create a machine 1019 m, err := s.addMachine() 1020 c.Assert(err, jc.ErrorIsNil) 1021 s.checkStartInstance(c, m) 1022 1023 // restart the PA 1024 stop(c, p) 1025 p = s.newEnvironProvisioner(c) 1026 defer stop(c, p) 1027 1028 // check that there is only one machine provisioned. 1029 machines, err := s.State.AllMachines() 1030 c.Assert(err, jc.ErrorIsNil) 1031 c.Check(len(machines), gc.Equals, 2) 1032 c.Check(machines[0].Id(), gc.Equals, "0") 1033 c.Check(machines[1].CheckProvisioned("fake_nonce"), jc.IsFalse) 1034 1035 // the PA should not create it a second time 1036 s.checkNoOperations(c) 1037 } 1038 1039 func (s *ProvisionerSuite) TestDyingMachines(c *gc.C) { 1040 p := s.newEnvironProvisioner(c) 1041 defer stop(c, p) 1042 1043 // provision a machine 1044 m0, err := s.addMachine() 1045 c.Assert(err, jc.ErrorIsNil) 1046 s.checkStartInstance(c, m0) 1047 1048 // stop the provisioner and make the machine dying 1049 stop(c, p) 1050 err = m0.Destroy() 1051 c.Assert(err, jc.ErrorIsNil) 1052 1053 // add a new, dying, unprovisioned machine 1054 m1, err := s.addMachine() 1055 c.Assert(err, jc.ErrorIsNil) 1056 err = m1.Destroy() 1057 c.Assert(err, jc.ErrorIsNil) 1058 1059 // start the provisioner and wait for it to reap the useless machine 1060 p = s.newEnvironProvisioner(c) 1061 defer stop(c, p) 1062 s.checkNoOperations(c) 1063 s.waitForRemovalMark(c, m1) 1064 1065 // verify the other one's still fine 1066 err = m0.Refresh() 1067 c.Assert(err, jc.ErrorIsNil) 1068 c.Assert(m0.Life(), gc.Equals, state.Dying) 1069 } 1070 1071 type mockMachineGetter struct{} 1072 1073 func (*mockMachineGetter) Machine(names.MachineTag) (*apiprovisioner.Machine, error) { 1074 return nil, fmt.Errorf("error") 1075 } 1076 1077 func (*mockMachineGetter) MachinesWithTransientErrors() ([]*apiprovisioner.Machine, []params.StatusResult, error) { 1078 return nil, nil, fmt.Errorf("error") 1079 } 1080 1081 func (s *ProvisionerSuite) TestMachineErrorsRetainInstances(c *gc.C) { 1082 task := s.newProvisionerTask(c, config.HarvestAll, s.Environ, s.provisioner, mockToolsFinder{}) 1083 defer stop(c, task) 1084 1085 // create a machine 1086 m0, err := s.addMachine() 1087 c.Assert(err, jc.ErrorIsNil) 1088 s.checkStartInstance(c, m0) 1089 1090 // create an instance out of band 1091 s.startUnknownInstance(c, "999") 1092 1093 // start the provisioner and ensure it doesn't kill any instances if there are error getting machines 1094 task = s.newProvisionerTask( 1095 c, 1096 config.HarvestAll, 1097 s.Environ, 1098 &mockMachineGetter{}, 1099 &mockToolsFinder{}, 1100 ) 1101 defer func() { 1102 err := worker.Stop(task) 1103 c.Assert(err, gc.ErrorMatches, ".*failed to get machine.*") 1104 }() 1105 s.checkNoOperations(c) 1106 } 1107 1108 func (s *ProvisionerSuite) TestEnvironProvisionerObservesConfigChanges(c *gc.C) { 1109 p := s.newEnvironProvisioner(c) 1110 defer stop(c, p) 1111 s.assertProvisionerObservesConfigChanges(c, p) 1112 } 1113 1114 func (s *ProvisionerSuite) newProvisionerTask( 1115 c *gc.C, 1116 harvestingMethod config.HarvestMode, 1117 broker environs.InstanceBroker, 1118 machineGetter provisioner.MachineGetter, 1119 toolsFinder provisioner.ToolsFinder, 1120 ) provisioner.ProvisionerTask { 1121 1122 machineWatcher, err := s.provisioner.WatchModelMachines() 1123 c.Assert(err, jc.ErrorIsNil) 1124 retryWatcher, err := s.provisioner.WatchMachineErrorRetry() 1125 c.Assert(err, jc.ErrorIsNil) 1126 auth, err := authentication.NewAPIAuthenticator(s.provisioner) 1127 c.Assert(err, jc.ErrorIsNil) 1128 1129 retryStrategy := provisioner.NewRetryStrategy(0*time.Second, 0) 1130 1131 w, err := provisioner.NewProvisionerTask( 1132 s.ControllerConfig.ControllerUUID(), 1133 names.NewMachineTag("0"), 1134 harvestingMethod, 1135 machineGetter, 1136 toolsFinder, 1137 machineWatcher, 1138 retryWatcher, 1139 broker, 1140 auth, 1141 imagemetadata.ReleasedStream, 1142 retryStrategy, 1143 ) 1144 c.Assert(err, jc.ErrorIsNil) 1145 return w 1146 } 1147 1148 func (s *ProvisionerSuite) TestHarvestNoneReapsNothing(c *gc.C) { 1149 1150 task := s.newProvisionerTask(c, config.HarvestDestroyed, s.Environ, s.provisioner, mockToolsFinder{}) 1151 defer stop(c, task) 1152 task.SetHarvestMode(config.HarvestNone) 1153 1154 // Create a machine and an unknown instance. 1155 m0, err := s.addMachine() 1156 c.Assert(err, jc.ErrorIsNil) 1157 s.checkStartInstance(c, m0) 1158 s.startUnknownInstance(c, "999") 1159 1160 // Mark the first machine as dead. 1161 c.Assert(m0.EnsureDead(), gc.IsNil) 1162 1163 // Ensure we're doing nothing. 1164 s.checkNoOperations(c) 1165 } 1166 1167 func (s *ProvisionerSuite) TestHarvestUnknownReapsOnlyUnknown(c *gc.C) { 1168 1169 task := s.newProvisionerTask(c, 1170 config.HarvestDestroyed, 1171 s.Environ, 1172 s.provisioner, 1173 mockToolsFinder{}, 1174 ) 1175 defer stop(c, task) 1176 task.SetHarvestMode(config.HarvestUnknown) 1177 1178 // Create a machine and an unknown instance. 1179 m0, err := s.addMachine() 1180 c.Assert(err, jc.ErrorIsNil) 1181 i0 := s.checkStartInstance(c, m0) 1182 i1 := s.startUnknownInstance(c, "999") 1183 1184 // Mark the first machine as dead. 1185 c.Assert(m0.EnsureDead(), gc.IsNil) 1186 1187 // When only harvesting unknown machines, only one of the machines 1188 // is stopped. 1189 s.checkStopSomeInstances(c, []instance.Instance{i1}, []instance.Instance{i0}) 1190 s.waitForRemovalMark(c, m0) 1191 } 1192 1193 func (s *ProvisionerSuite) TestHarvestDestroyedReapsOnlyDestroyed(c *gc.C) { 1194 1195 task := s.newProvisionerTask( 1196 c, 1197 config.HarvestDestroyed, 1198 s.Environ, 1199 s.provisioner, 1200 mockToolsFinder{}, 1201 ) 1202 defer stop(c, task) 1203 1204 // Create a machine and an unknown instance. 1205 m0, err := s.addMachine() 1206 c.Assert(err, jc.ErrorIsNil) 1207 i0 := s.checkStartInstance(c, m0) 1208 i1 := s.startUnknownInstance(c, "999") 1209 1210 // Mark the first machine as dead. 1211 c.Assert(m0.EnsureDead(), gc.IsNil) 1212 1213 // When only harvesting destroyed machines, only one of the 1214 // machines is stopped. 1215 s.checkStopSomeInstances(c, []instance.Instance{i0}, []instance.Instance{i1}) 1216 s.waitForRemovalMark(c, m0) 1217 } 1218 1219 func (s *ProvisionerSuite) TestHarvestAllReapsAllTheThings(c *gc.C) { 1220 1221 task := s.newProvisionerTask(c, 1222 config.HarvestDestroyed, 1223 s.Environ, 1224 s.provisioner, 1225 mockToolsFinder{}, 1226 ) 1227 defer stop(c, task) 1228 task.SetHarvestMode(config.HarvestAll) 1229 1230 // Create a machine and an unknown instance. 1231 m0, err := s.addMachine() 1232 c.Assert(err, jc.ErrorIsNil) 1233 i0 := s.checkStartInstance(c, m0) 1234 i1 := s.startUnknownInstance(c, "999") 1235 1236 // Mark the first machine as dead. 1237 c.Assert(m0.EnsureDead(), gc.IsNil) 1238 1239 // Everything must die! 1240 s.checkStopSomeInstances(c, []instance.Instance{i0, i1}, []instance.Instance{}) 1241 s.waitForRemovalMark(c, m0) 1242 } 1243 1244 func (s *ProvisionerSuite) TestProvisionerRetriesTransientErrors(c *gc.C) { 1245 s.PatchValue(&apiserverprovisioner.ErrorRetryWaitDelay, 5*time.Millisecond) 1246 e := &mockBroker{Environ: s.Environ, retryCount: make(map[string]int)} 1247 task := s.newProvisionerTask(c, config.HarvestAll, e, s.provisioner, mockToolsFinder{}) 1248 defer stop(c, task) 1249 1250 // Provision some machines, some will be started first time, 1251 // another will require retries. 1252 m1, err := s.addMachine() 1253 c.Assert(err, jc.ErrorIsNil) 1254 s.checkStartInstance(c, m1) 1255 m2, err := s.addMachine() 1256 c.Assert(err, jc.ErrorIsNil) 1257 s.checkStartInstance(c, m2) 1258 m3, err := s.addMachine() 1259 c.Assert(err, jc.ErrorIsNil) 1260 m4, err := s.addMachine() 1261 c.Assert(err, jc.ErrorIsNil) 1262 1263 // mockBroker will fail to start machine-3 several times; 1264 // keep setting the transient flag to retry until the 1265 // instance has started. 1266 thatsAllFolks := make(chan struct{}) 1267 go func() { 1268 for { 1269 select { 1270 case <-thatsAllFolks: 1271 return 1272 case <-time.After(coretesting.ShortWait): 1273 now := time.Now() 1274 sInfo := status.StatusInfo{ 1275 Status: status.Error, 1276 Message: "info", 1277 Data: map[string]interface{}{"transient": true}, 1278 Since: &now, 1279 } 1280 err := m3.SetStatus(sInfo) 1281 c.Assert(err, jc.ErrorIsNil) 1282 } 1283 } 1284 }() 1285 s.checkStartInstance(c, m3) 1286 close(thatsAllFolks) 1287 1288 // Machine 4 is never provisioned. 1289 statusInfo, err := m4.Status() 1290 c.Assert(err, jc.ErrorIsNil) 1291 c.Assert(statusInfo.Status, gc.Equals, status.Error) 1292 _, err = m4.InstanceId() 1293 c.Assert(err, jc.Satisfies, errors.IsNotProvisioned) 1294 } 1295 1296 func (s *ProvisionerSuite) TestProvisionerObservesMachineJobs(c *gc.C) { 1297 s.PatchValue(&apiserverprovisioner.ErrorRetryWaitDelay, 5*time.Millisecond) 1298 broker := &mockBroker{Environ: s.Environ, retryCount: make(map[string]int)} 1299 task := s.newProvisionerTask(c, config.HarvestAll, broker, s.provisioner, mockToolsFinder{}) 1300 defer stop(c, task) 1301 1302 added := s.enableHA(c, 3) 1303 c.Assert(added, gc.HasLen, 2) 1304 byId := make(map[string]*state.Machine) 1305 for _, m := range added { 1306 byId[m.Id()] = m 1307 } 1308 for _, id := range broker.ids { 1309 s.checkStartInstance(c, byId[id]) 1310 } 1311 } 1312 1313 type mockBroker struct { 1314 environs.Environ 1315 retryCount map[string]int 1316 ids []string 1317 } 1318 1319 func (b *mockBroker) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 1320 // All machines except machines 3, 4 are provisioned successfully the first time. 1321 // Machines 3 is provisioned after some attempts have been made. 1322 // Machine 4 is never provisioned. 1323 id := args.InstanceConfig.MachineId 1324 // record ids so we can call checkStartInstance in the appropriate order. 1325 b.ids = append(b.ids, id) 1326 retries := b.retryCount[id] 1327 if (id != "3" && id != "4") || retries > 2 { 1328 return b.Environ.StartInstance(args) 1329 } else { 1330 b.retryCount[id] = retries + 1 1331 } 1332 return nil, fmt.Errorf("error: some error") 1333 } 1334 1335 type mockToolsFinder struct { 1336 } 1337 1338 func (f mockToolsFinder) FindTools(number version.Number, series string, a string) (coretools.List, error) { 1339 v, err := version.ParseBinary(fmt.Sprintf("%s-%s-%s", number, series, arch.HostArch())) 1340 if err != nil { 1341 return nil, err 1342 } 1343 if a != "" { 1344 v.Arch = a 1345 } 1346 return coretools.List{&coretools.Tools{Version: v}}, nil 1347 } 1348 1349 type mockAgent struct { 1350 agent.Agent 1351 config agent.Config 1352 } 1353 1354 func (mock mockAgent) CurrentConfig() agent.Config { 1355 return mock.config 1356 }