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