github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/util_test.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 "reflect" 13 "runtime" 14 "strconv" 15 "strings" 16 "sync" 17 "time" 18 19 "github.com/juju/clock" 20 "github.com/juju/errors" 21 "github.com/juju/mutex" 22 "github.com/juju/proxy" 23 gt "github.com/juju/testing" 24 jc "github.com/juju/testing/checkers" 25 ft "github.com/juju/testing/filetesting" 26 "github.com/juju/utils" 27 utilexec "github.com/juju/utils/exec" 28 gc "gopkg.in/check.v1" 29 corecharm "gopkg.in/juju/charm.v6" 30 "gopkg.in/juju/names.v2" 31 "gopkg.in/juju/worker.v1" 32 goyaml "gopkg.in/yaml.v2" 33 34 "github.com/juju/juju/api" 35 apiuniter "github.com/juju/juju/api/uniter" 36 "github.com/juju/juju/core/leadership" 37 corelease "github.com/juju/juju/core/lease" 38 "github.com/juju/juju/core/machinelock" 39 "github.com/juju/juju/core/status" 40 "github.com/juju/juju/juju/sockets" 41 "github.com/juju/juju/juju/testing" 42 "github.com/juju/juju/network" 43 "github.com/juju/juju/resource/resourcetesting" 44 "github.com/juju/juju/state" 45 "github.com/juju/juju/state/storage" 46 "github.com/juju/juju/testcharms" 47 coretesting "github.com/juju/juju/testing" 48 jworker "github.com/juju/juju/worker" 49 "github.com/juju/juju/worker/fortress" 50 "github.com/juju/juju/worker/uniter" 51 "github.com/juju/juju/worker/uniter/operation" 52 "github.com/juju/juju/worker/uniter/remotestate" 53 ) 54 55 // worstCase is used for timeouts when timing out 56 // will fail the test. Raising this value should 57 // not affect the overall running time of the tests 58 // unless they fail. 59 const worstCase = coretesting.LongWait 60 61 // Assign the unit to a provisioned machine with dummy addresses set. 62 func assertAssignUnit(c *gc.C, st *state.State, u *state.Unit) { 63 err := u.AssignToNewMachine() 64 c.Assert(err, jc.ErrorIsNil) 65 mid, err := u.AssignedMachineId() 66 c.Assert(err, jc.ErrorIsNil) 67 machine, err := st.Machine(mid) 68 c.Assert(err, jc.ErrorIsNil) 69 err = machine.SetProvisioned("i-exist", "", "fake_nonce", nil) 70 c.Assert(err, jc.ErrorIsNil) 71 err = machine.SetProviderAddresses(network.Address{ 72 Type: network.IPv4Address, 73 Scope: network.ScopeCloudLocal, 74 Value: "private.address.example.com", 75 }, network.Address{ 76 Type: network.IPv4Address, 77 Scope: network.ScopePublic, 78 Value: "public.address.example.com", 79 }) 80 c.Assert(err, jc.ErrorIsNil) 81 } 82 83 type context struct { 84 uuid string 85 path string 86 dataDir string 87 s *UniterSuite 88 st *state.State 89 api *apiuniter.State 90 apiConn api.Connection 91 leaseManager corelease.Manager 92 leaderTracker *mockLeaderTracker 93 charmDirGuard *mockCharmDirGuard 94 charms map[string][]byte 95 hooks []string 96 sch *state.Charm 97 application *state.Application 98 unit *state.Unit 99 uniter *uniter.Uniter 100 relatedSvc *state.Application 101 relation *state.Relation 102 relationUnits map[string]*state.RelationUnit 103 subordinate *state.Unit 104 updateStatusHookTicker *manualTicker 105 err string 106 107 wg sync.WaitGroup 108 mu sync.Mutex 109 hooksCompleted []string 110 } 111 112 var _ uniter.UniterExecutionObserver = (*context)(nil) 113 114 // HookCompleted implements the UniterExecutionObserver interface. 115 func (ctx *context) HookCompleted(hookName string) { 116 ctx.mu.Lock() 117 ctx.hooksCompleted = append(ctx.hooksCompleted, hookName) 118 ctx.mu.Unlock() 119 } 120 121 // HookFailed implements the UniterExecutionObserver interface. 122 func (ctx *context) HookFailed(hookName string) { 123 ctx.mu.Lock() 124 ctx.hooksCompleted = append(ctx.hooksCompleted, "fail-"+hookName) 125 ctx.mu.Unlock() 126 } 127 128 func (ctx *context) setExpectedError(err string) { 129 ctx.mu.Lock() 130 ctx.err = err 131 ctx.mu.Unlock() 132 } 133 134 func (ctx *context) run(c *gc.C, steps []stepper) { 135 defer func() { 136 if ctx.uniter != nil { 137 err := worker.Stop(ctx.uniter) 138 if ctx.err == "" { 139 if errors.Cause(err) == mutex.ErrCancelled { 140 // This can happen if the uniter lock acquire was 141 // temporarily blocked by test code holding the 142 // lock (like in waitHooks). The acquire call is 143 // delaying but then gets cancelled, and that 144 // error bubbles up to here. 145 // lp:1635664 146 c.Logf("ignoring lock acquire cancelled by stop") 147 return 148 } 149 c.Assert(err, jc.ErrorIsNil) 150 } else { 151 c.Assert(err, gc.ErrorMatches, ctx.err) 152 } 153 } 154 }() 155 for i, s := range steps { 156 c.Logf("step %d:\n", i) 157 step(c, ctx, s) 158 } 159 } 160 161 func (ctx *context) apiLogin(c *gc.C) { 162 password, err := utils.RandomPassword() 163 c.Assert(err, jc.ErrorIsNil) 164 err = ctx.unit.SetPassword(password) 165 c.Assert(err, jc.ErrorIsNil) 166 apiConn := ctx.s.OpenAPIAs(c, ctx.unit.Tag(), password) 167 c.Assert(apiConn, gc.NotNil) 168 c.Logf("API: login as %q successful", ctx.unit.Tag()) 169 api, err := apiConn.Uniter() 170 c.Assert(err, jc.ErrorIsNil) 171 c.Assert(api, gc.NotNil) 172 ctx.api = api 173 ctx.apiConn = apiConn 174 ctx.leaderTracker = newMockLeaderTracker(ctx) 175 ctx.leaderTracker.setLeader(c, true) 176 } 177 178 func (ctx *context) writeExplicitHook(c *gc.C, path string, contents string) { 179 err := ioutil.WriteFile(path+cmdSuffix, []byte(contents), 0755) 180 c.Assert(err, jc.ErrorIsNil) 181 } 182 183 func (ctx *context) writeHook(c *gc.C, path string, good bool) { 184 hook := badHook 185 if good { 186 hook = goodHook 187 } 188 content := fmt.Sprintf(hook, filepath.Base(path)) 189 ctx.writeExplicitHook(c, path, content) 190 } 191 192 func (ctx *context) writeAction(c *gc.C, path, name string) { 193 actionPath := filepath.Join(path, "actions", name) 194 action := actions[name] 195 err := ioutil.WriteFile(actionPath+cmdSuffix, []byte(action), 0755) 196 c.Assert(err, jc.ErrorIsNil) 197 } 198 199 func (ctx *context) writeActionsYaml(c *gc.C, path string, names ...string) { 200 var actionsYaml = map[string]string{ 201 "base": "", 202 "snapshot": ` 203 snapshot: 204 description: Take a snapshot of the database. 205 params: 206 outfile: 207 description: "The file to write out to." 208 type: string 209 required: ["outfile"] 210 `[1:], 211 "action-log": ` 212 action-log: 213 `[1:], 214 "action-log-fail": ` 215 action-log-fail: 216 `[1:], 217 "action-log-fail-error": ` 218 action-log-fail-error: 219 `[1:], 220 "action-reboot": ` 221 action-reboot: 222 `[1:], 223 } 224 actionsYamlPath := filepath.Join(path, "actions.yaml") 225 var actionsYamlFull string 226 // Build an appropriate actions.yaml 227 if names[0] != "base" { 228 names = append([]string{"base"}, names...) 229 } 230 for _, name := range names { 231 actionsYamlFull = strings.Join( 232 []string{actionsYamlFull, actionsYaml[name]}, "\n") 233 } 234 err := ioutil.WriteFile(actionsYamlPath, []byte(actionsYamlFull), 0755) 235 c.Assert(err, jc.ErrorIsNil) 236 } 237 238 func (ctx *context) matchHooks(c *gc.C) (match bool, overshoot bool) { 239 ctx.mu.Lock() 240 defer ctx.mu.Unlock() 241 c.Logf("actual hooks: %#v", ctx.hooksCompleted) 242 c.Logf("expected hooks: %#v", ctx.hooks) 243 if len(ctx.hooksCompleted) < len(ctx.hooks) { 244 return false, false 245 } 246 for i, e := range ctx.hooks { 247 if ctx.hooksCompleted[i] != e { 248 return false, false 249 } 250 } 251 return true, len(ctx.hooksCompleted) > len(ctx.hooks) 252 } 253 254 type uniterTest struct { 255 summary string 256 steps []stepper 257 } 258 259 func ut(summary string, steps ...stepper) uniterTest { 260 return uniterTest{summary, steps} 261 } 262 263 type stepper interface { 264 step(c *gc.C, ctx *context) 265 } 266 267 func step(c *gc.C, ctx *context, s stepper) { 268 c.Logf("%#v", s) 269 s.step(c, ctx) 270 } 271 272 type ensureStateWorker struct{} 273 274 func (s ensureStateWorker) step(c *gc.C, ctx *context) { 275 addresses, err := ctx.st.Addresses() 276 if err != nil || len(addresses) == 0 { 277 addControllerMachine(c, ctx.st) 278 } 279 } 280 281 func addControllerMachine(c *gc.C, st *state.State) { 282 // The AddControllerMachine call will update the API host ports 283 // to made-up addresses. We need valid addresses so that the uniter 284 // can download charms from the API server. 285 apiHostPorts, err := st.APIHostPortsForClients() 286 c.Assert(err, gc.IsNil) 287 testing.AddControllerMachine(c, st) 288 err = st.SetAPIHostPorts(apiHostPorts) 289 c.Assert(err, gc.IsNil) 290 } 291 292 type createCharm struct { 293 revision int 294 badHooks []string 295 customize func(*gc.C, *context, string) 296 } 297 298 var ( 299 baseCharmHooks = []string{ 300 "install", "start", "config-changed", "upgrade-charm", "stop", 301 "db-relation-joined", "db-relation-changed", "db-relation-departed", 302 "db-relation-broken", "meter-status-changed", "collect-metrics", "update-status", 303 } 304 leaderCharmHooks = []string{ 305 "leader-elected", "leader-deposed", "leader-settings-changed", 306 } 307 storageCharmHooks = []string{ 308 "wp-content-storage-attached", "wp-content-storage-detaching", 309 } 310 ) 311 312 func startupHooks(minion bool) []string { 313 leaderHook := "leader-elected" 314 if minion { 315 leaderHook = "leader-settings-changed" 316 } 317 return []string{"install", leaderHook, "config-changed", "start"} 318 } 319 320 func (s createCharm) step(c *gc.C, ctx *context) { 321 base := testcharms.Repo.ClonedDirPath(c.MkDir(), "wordpress") 322 323 allCharmHooks := baseCharmHooks 324 allCharmHooks = append(allCharmHooks, leaderCharmHooks...) 325 allCharmHooks = append(allCharmHooks, storageCharmHooks...) 326 327 for _, name := range allCharmHooks { 328 path := filepath.Join(base, "hooks", name) 329 good := true 330 for _, bad := range s.badHooks { 331 if name == bad { 332 good = false 333 } 334 } 335 ctx.writeHook(c, path, good) 336 } 337 if s.customize != nil { 338 s.customize(c, ctx, base) 339 } 340 dir, err := corecharm.ReadCharmDir(base) 341 c.Assert(err, jc.ErrorIsNil) 342 err = dir.SetDiskRevision(s.revision) 343 c.Assert(err, jc.ErrorIsNil) 344 step(c, ctx, addCharm{dir, curl(s.revision)}) 345 } 346 347 type addCharm struct { 348 dir *corecharm.CharmDir 349 curl *corecharm.URL 350 } 351 352 func (s addCharm) step(c *gc.C, ctx *context) { 353 var buf bytes.Buffer 354 err := s.dir.ArchiveTo(&buf) 355 c.Assert(err, jc.ErrorIsNil) 356 body := buf.Bytes() 357 hash, _, err := utils.ReadSHA256(&buf) 358 c.Assert(err, jc.ErrorIsNil) 359 360 storagePath := fmt.Sprintf("/charms/%s/%d", s.dir.Meta().Name, s.dir.Revision()) 361 ctx.charms[storagePath] = body 362 info := state.CharmInfo{ 363 Charm: s.dir, 364 ID: s.curl, 365 StoragePath: storagePath, 366 SHA256: hash, 367 } 368 369 ctx.sch, err = ctx.st.AddCharm(info) 370 c.Assert(err, jc.ErrorIsNil) 371 } 372 373 type serveCharm struct{} 374 375 func (s serveCharm) step(c *gc.C, ctx *context) { 376 storage := storage.NewStorage(ctx.st.ModelUUID(), ctx.st.MongoSession()) 377 for storagePath, data := range ctx.charms { 378 err := storage.Put(storagePath, bytes.NewReader(data), int64(len(data))) 379 c.Assert(err, jc.ErrorIsNil) 380 delete(ctx.charms, storagePath) 381 } 382 } 383 384 type createApplicationAndUnit struct { 385 applicationName string 386 storage map[string]state.StorageConstraints 387 } 388 389 func (csau createApplicationAndUnit) step(c *gc.C, ctx *context) { 390 if csau.applicationName == "" { 391 csau.applicationName = "u" 392 } 393 sch, err := ctx.st.Charm(curl(0)) 394 c.Assert(err, jc.ErrorIsNil) 395 app := ctx.s.AddTestingApplicationWithStorage(c, csau.applicationName, sch, csau.storage) 396 unit, err := app.AddUnit(state.AddUnitParams{}) 397 c.Assert(err, jc.ErrorIsNil) 398 399 // Assign the unit to a provisioned machine to match expected state. 400 assertAssignUnit(c, ctx.st, unit) 401 ctx.application = app 402 ctx.unit = unit 403 404 ctx.apiLogin(c) 405 } 406 407 type createUniter struct { 408 minion bool 409 executorFunc uniter.NewExecutorFunc 410 translateResolverErr func(error) error 411 } 412 413 func (s createUniter) step(c *gc.C, ctx *context) { 414 step(c, ctx, ensureStateWorker{}) 415 step(c, ctx, createApplicationAndUnit{}) 416 if s.minion { 417 step(c, ctx, forceMinion{}) 418 } 419 step(c, ctx, startUniter{ 420 newExecutorFunc: s.executorFunc, 421 translateResolverErr: s.translateResolverErr, 422 }) 423 step(c, ctx, waitAddresses{}) 424 } 425 426 type waitAddresses struct{} 427 428 func (waitAddresses) step(c *gc.C, ctx *context) { 429 timeout := time.After(worstCase) 430 for { 431 select { 432 case <-timeout: 433 c.Fatalf("timed out waiting for unit addresses") 434 case <-time.After(coretesting.ShortWait): 435 err := ctx.unit.Refresh() 436 if err != nil { 437 c.Fatalf("unit refresh failed: %v", err) 438 } 439 // GZ 2013-07-10: Hardcoded values from dummy environ 440 // special cased here, questionable. 441 private, _ := ctx.unit.PrivateAddress() 442 if private.Value != "private.address.example.com" { 443 continue 444 } 445 public, _ := ctx.unit.PublicAddress() 446 if public.Value != "public.address.example.com" { 447 continue 448 } 449 return 450 } 451 } 452 } 453 454 type startUniter struct { 455 unitTag string 456 newExecutorFunc uniter.NewExecutorFunc 457 translateResolverErr func(error) error 458 } 459 460 func (s startUniter) step(c *gc.C, ctx *context) { 461 if s.unitTag == "" { 462 s.unitTag = "unit-u-0" 463 } 464 if ctx.uniter != nil { 465 panic("don't start two uniters!") 466 } 467 if ctx.api == nil { 468 panic("API connection not established") 469 } 470 tag, err := names.ParseUnitTag(s.unitTag) 471 if err != nil { 472 panic(err.Error()) 473 } 474 downloader := api.NewCharmDownloader(ctx.apiConn) 475 operationExecutor := operation.NewExecutor 476 if s.newExecutorFunc != nil { 477 operationExecutor = s.newExecutorFunc 478 } 479 480 uniterParams := uniter.UniterParams{ 481 UniterFacade: ctx.api, 482 UnitTag: tag, 483 LeadershipTracker: ctx.leaderTracker, 484 CharmDirGuard: ctx.charmDirGuard, 485 DataDir: ctx.dataDir, 486 Downloader: downloader, 487 MachineLock: processLock, 488 UpdateStatusSignal: ctx.updateStatusHookTicker.ReturnTimer(), 489 NewOperationExecutor: operationExecutor, 490 TranslateResolverErr: s.translateResolverErr, 491 Observer: ctx, 492 // TODO(axw) 2015-11-02 #1512191 493 // update tests that rely on timing to advance clock 494 // appropriately. 495 Clock: clock.WallClock, 496 } 497 ctx.uniter, err = uniter.NewUniter(&uniterParams) 498 c.Assert(err, jc.ErrorIsNil) 499 } 500 501 type waitUniterDead struct { 502 err string 503 } 504 505 func (s waitUniterDead) step(c *gc.C, ctx *context) { 506 if s.err != "" { 507 err := s.waitDead(c, ctx) 508 c.Assert(err, gc.ErrorMatches, s.err) 509 return 510 } 511 512 // In the default case, we're waiting for worker.ErrTerminateAgent, but 513 // the path to that error can be tricky. If the unit becomes Dead at an 514 // inconvenient time, unrelated calls can fail -- as they should -- but 515 // not be detected as worker.ErrTerminateAgent. In this case, we restart 516 // the uniter and check that it fails as expected when starting up; this 517 // mimics the behaviour of the unit agent and verifies that the UA will, 518 // eventually, see the correct error and respond appropriately. 519 err := s.waitDead(c, ctx) 520 if err != jworker.ErrTerminateAgent { 521 step(c, ctx, startUniter{}) 522 err = s.waitDead(c, ctx) 523 } 524 c.Assert(err, gc.Equals, jworker.ErrTerminateAgent) 525 err = ctx.unit.Refresh() 526 c.Assert(err, jc.ErrorIsNil) 527 c.Assert(ctx.unit.Life(), gc.Equals, state.Dead) 528 } 529 530 func (s waitUniterDead) waitDead(c *gc.C, ctx *context) error { 531 u := ctx.uniter 532 ctx.uniter = nil 533 534 wait := make(chan error, 1) 535 go func() { 536 wait <- u.Wait() 537 }() 538 539 ctx.s.BackingState.StartSync() 540 select { 541 case err := <-wait: 542 return err 543 case <-time.After(worstCase): 544 u.Kill() 545 c.Fatalf("uniter still alive") 546 } 547 panic("unreachable") 548 } 549 550 type stopUniter struct { 551 err string 552 } 553 554 func (s stopUniter) step(c *gc.C, ctx *context) { 555 u := ctx.uniter 556 if u == nil { 557 c.Logf("uniter not started, skipping stopUniter{}") 558 return 559 } 560 ctx.uniter = nil 561 err := worker.Stop(u) 562 if s.err == "" { 563 c.Assert(err, jc.ErrorIsNil) 564 } else { 565 c.Assert(err, gc.ErrorMatches, s.err) 566 } 567 } 568 569 type verifyWaiting struct{} 570 571 func (s verifyWaiting) step(c *gc.C, ctx *context) { 572 step(c, ctx, stopUniter{}) 573 step(c, ctx, startUniter{}) 574 step(c, ctx, waitHooks{}) 575 } 576 577 type verifyRunning struct { 578 minion bool 579 } 580 581 func (s verifyRunning) step(c *gc.C, ctx *context) { 582 step(c, ctx, stopUniter{}) 583 step(c, ctx, startUniter{}) 584 var hooks []string 585 if s.minion { 586 hooks = append(hooks, "leader-settings-changed") 587 } 588 // We don't expect config-changed to always run on agent restart 589 // anymore. 590 step(c, ctx, waitHooks(hooks)) 591 } 592 593 type startupErrorWithCustomCharm struct { 594 badHook string 595 customize func(*gc.C, *context, string) 596 } 597 598 func (s startupErrorWithCustomCharm) step(c *gc.C, ctx *context) { 599 step(c, ctx, createCharm{ 600 badHooks: []string{s.badHook}, 601 customize: s.customize, 602 }) 603 step(c, ctx, serveCharm{}) 604 step(c, ctx, createUniter{}) 605 step(c, ctx, waitUnitAgent{ 606 statusGetter: unitStatusGetter, 607 status: status.Error, 608 info: fmt.Sprintf(`hook failed: %q`, s.badHook), 609 }) 610 for _, hook := range startupHooks(false) { 611 if hook == s.badHook { 612 step(c, ctx, waitHooks{"fail-" + hook}) 613 break 614 } 615 step(c, ctx, waitHooks{hook}) 616 } 617 step(c, ctx, verifyCharm{}) 618 } 619 620 type startupError struct { 621 badHook string 622 } 623 624 func (s startupError) step(c *gc.C, ctx *context) { 625 step(c, ctx, createCharm{badHooks: []string{s.badHook}}) 626 step(c, ctx, serveCharm{}) 627 step(c, ctx, createUniter{}) 628 step(c, ctx, waitUnitAgent{ 629 statusGetter: unitStatusGetter, 630 status: status.Error, 631 info: fmt.Sprintf(`hook failed: %q`, s.badHook), 632 }) 633 for _, hook := range startupHooks(false) { 634 if hook == s.badHook { 635 step(c, ctx, waitHooks{"fail-" + hook}) 636 break 637 } 638 step(c, ctx, waitHooks{hook}) 639 } 640 step(c, ctx, verifyCharm{}) 641 } 642 643 type createDownloads struct{} 644 645 func (s createDownloads) step(c *gc.C, ctx *context) { 646 dir := downloadDir(ctx) 647 c.Assert(os.MkdirAll(dir, 0775), jc.ErrorIsNil) 648 c.Assert( 649 ioutil.WriteFile(filepath.Join(dir, "foo"), []byte("bar"), 0775), 650 jc.ErrorIsNil, 651 ) 652 } 653 654 type verifyDownloadsCleared struct{} 655 656 func (s verifyDownloadsCleared) step(c *gc.C, ctx *context) { 657 files, err := ioutil.ReadDir(downloadDir(ctx)) 658 c.Assert(err, jc.ErrorIsNil) 659 c.Check(files, gc.HasLen, 0) 660 } 661 662 func downloadDir(ctx *context) string { 663 paths := uniter.NewPaths(ctx.dataDir, ctx.unit.UnitTag()) 664 return filepath.Join(paths.State.BundlesDir, "downloads") 665 } 666 667 type quickStart struct { 668 minion bool 669 } 670 671 func (s quickStart) step(c *gc.C, ctx *context) { 672 step(c, ctx, createCharm{}) 673 step(c, ctx, serveCharm{}) 674 step(c, ctx, createUniter{minion: s.minion}) 675 step(c, ctx, waitUnitAgent{status: status.Idle}) 676 step(c, ctx, waitHooks(startupHooks(s.minion))) 677 step(c, ctx, verifyCharm{}) 678 } 679 680 type quickStartRelation struct{} 681 682 func (s quickStartRelation) step(c *gc.C, ctx *context) { 683 step(c, ctx, quickStart{}) 684 step(c, ctx, addRelation{}) 685 step(c, ctx, addRelationUnit{}) 686 step(c, ctx, waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"}) 687 step(c, ctx, verifyRunning{}) 688 } 689 690 type startupRelationError struct { 691 badHook string 692 } 693 694 func (s startupRelationError) step(c *gc.C, ctx *context) { 695 step(c, ctx, createCharm{badHooks: []string{s.badHook}}) 696 step(c, ctx, serveCharm{}) 697 step(c, ctx, createUniter{}) 698 step(c, ctx, waitUnitAgent{status: status.Idle}) 699 step(c, ctx, waitHooks(startupHooks(false))) 700 step(c, ctx, verifyCharm{}) 701 step(c, ctx, addRelation{}) 702 step(c, ctx, addRelationUnit{}) 703 } 704 705 type resolveError struct { 706 resolved state.ResolvedMode 707 } 708 709 func (s resolveError) step(c *gc.C, ctx *context) { 710 err := ctx.unit.SetResolved(s.resolved) 711 c.Assert(err, jc.ErrorIsNil) 712 } 713 714 type statusfunc func() (status.StatusInfo, error) 715 716 type statusfuncGetter func(ctx *context) statusfunc 717 718 var unitStatusGetter = func(ctx *context) statusfunc { 719 return func() (status.StatusInfo, error) { 720 return ctx.unit.Status() 721 } 722 } 723 724 var agentStatusGetter = func(ctx *context) statusfunc { 725 return func() (status.StatusInfo, error) { 726 return ctx.unit.AgentStatus() 727 } 728 } 729 730 type waitUnitAgent struct { 731 statusGetter func(ctx *context) statusfunc 732 status status.Status 733 info string 734 data map[string]interface{} 735 charm int 736 resolved state.ResolvedMode 737 } 738 739 func (s waitUnitAgent) step(c *gc.C, ctx *context) { 740 if s.statusGetter == nil { 741 s.statusGetter = agentStatusGetter 742 } 743 timeout := time.After(worstCase) 744 for { 745 ctx.s.BackingState.StartSync() 746 select { 747 case <-time.After(coretesting.ShortWait): 748 err := ctx.unit.Refresh() 749 if err != nil { 750 c.Fatalf("cannot refresh unit: %v", err) 751 } 752 resolved := ctx.unit.Resolved() 753 if resolved != s.resolved { 754 c.Logf("want resolved mode %q, got %q; still waiting", s.resolved, resolved) 755 continue 756 } 757 url, ok := ctx.unit.CharmURL() 758 if !ok || *url != *curl(s.charm) { 759 var got string 760 if ok { 761 got = url.String() 762 } 763 c.Logf("want unit charm %q, got %q; still waiting", curl(s.charm), got) 764 continue 765 } 766 statusInfo, err := s.statusGetter(ctx)() 767 c.Assert(err, jc.ErrorIsNil) 768 if string(statusInfo.Status) != string(s.status) { 769 c.Logf("want unit status %q, got %q; still waiting", s.status, statusInfo.Status) 770 continue 771 } 772 if statusInfo.Message != s.info { 773 c.Logf("want unit status info %q, got %q; still waiting", s.info, statusInfo.Message) 774 continue 775 } 776 if s.data != nil { 777 if len(statusInfo.Data) != len(s.data) { 778 c.Logf("want %d status data value(s), got %d; still waiting", len(s.data), len(statusInfo.Data)) 779 continue 780 } 781 for key, value := range s.data { 782 if statusInfo.Data[key] != value { 783 c.Logf("want status data value %q for key %q, got %q; still waiting", 784 value, key, statusInfo.Data[key]) 785 continue 786 } 787 } 788 } 789 return 790 case <-timeout: 791 c.Fatalf("never reached desired status") 792 } 793 } 794 } 795 796 type waitHooks []string 797 798 func (s waitHooks) step(c *gc.C, ctx *context) { 799 if len(s) == 0 { 800 // Give unwanted hooks a moment to run... 801 ctx.s.BackingState.StartSync() 802 time.Sleep(coretesting.ShortWait) 803 } 804 ctx.hooks = append(ctx.hooks, s...) 805 c.Logf("waiting for hooks: %#v", ctx.hooks) 806 match, overshoot := ctx.matchHooks(c) 807 if overshoot && len(s) == 0 { 808 c.Fatalf("ran more hooks than expected") 809 } 810 waitExecutionLockReleased := func() { 811 timeout := make(chan struct{}) 812 go func() { 813 <-time.After(worstCase) 814 close(timeout) 815 }() 816 releaser, err := processLock.Acquire(machinelock.Spec{ 817 Worker: "uniter-test", 818 Comment: "waitHooks", 819 Cancel: timeout, 820 }) 821 if err != nil { 822 c.Fatalf("failed to acquire execution lock: %v", err) 823 } 824 releaser() 825 } 826 if match { 827 if len(s) > 0 { 828 // only check for lock release if there were hooks 829 // run; hooks *not* running may be due to the lock 830 // being held. 831 waitExecutionLockReleased() 832 } 833 return 834 } 835 timeout := time.After(worstCase) 836 for { 837 ctx.s.BackingState.StartSync() 838 select { 839 case <-time.After(coretesting.ShortWait): 840 if match, _ = ctx.matchHooks(c); match { 841 waitExecutionLockReleased() 842 return 843 } 844 case <-timeout: 845 c.Fatalf("never got expected hooks") 846 } 847 } 848 } 849 850 type actionResult struct { 851 name string 852 results map[string]interface{} 853 status string 854 message string 855 } 856 857 type waitActionResults struct { 858 expectedResults []actionResult 859 } 860 861 func (s waitActionResults) step(c *gc.C, ctx *context) { 862 m, err := ctx.st.Model() 863 c.Assert(err, jc.ErrorIsNil) 864 resultsWatcher := m.WatchActionResults() 865 defer func() { 866 c.Assert(resultsWatcher.Stop(), gc.IsNil) 867 }() 868 timeout := time.After(worstCase) 869 for { 870 ctx.s.BackingState.StartSync() 871 select { 872 case <-time.After(coretesting.ShortWait): 873 continue 874 case <-timeout: 875 c.Fatalf("timed out waiting for action results") 876 case changes, ok := <-resultsWatcher.Changes(): 877 c.Logf("Got changes: %#v", changes) 878 c.Assert(ok, jc.IsTrue) 879 stateActionResults, err := ctx.unit.CompletedActions() 880 c.Assert(err, jc.ErrorIsNil) 881 if len(stateActionResults) != len(s.expectedResults) { 882 continue 883 } 884 actualResults := make([]actionResult, len(stateActionResults)) 885 for i, result := range stateActionResults { 886 results, message := result.Results() 887 actualResults[i] = actionResult{ 888 name: result.Name(), 889 results: results, 890 status: string(result.Status()), 891 message: message, 892 } 893 } 894 assertActionResultsMatch(c, actualResults, s.expectedResults) 895 return 896 } 897 } 898 } 899 900 func assertActionResultsMatch(c *gc.C, actualIn []actionResult, expectIn []actionResult) { 901 matches := 0 902 desiredMatches := len(actualIn) 903 c.Assert(len(actualIn), gc.Equals, len(expectIn)) 904 findMatch: 905 for _, expectedItem := range expectIn { 906 // find expectedItem in actualIn 907 for j, actualItem := range actualIn { 908 // If we find a match, remove both items from their 909 // respective slices, increment match count, and restart. 910 if reflect.DeepEqual(actualItem, expectedItem) { 911 actualIn = append(actualIn[:j], actualIn[j+1:]...) 912 matches++ 913 continue findMatch 914 } 915 } 916 // if we finish the whole thing without finding a match, we failed. 917 c.Assert(actualIn, jc.DeepEquals, expectIn) 918 } 919 920 c.Assert(matches, gc.Equals, desiredMatches) 921 } 922 923 type verifyNoActionResults struct{} 924 925 func (s verifyNoActionResults) step(c *gc.C, ctx *context) { 926 time.Sleep(coretesting.ShortWait) 927 result, err := ctx.unit.CompletedActions() 928 c.Assert(err, jc.ErrorIsNil) 929 c.Assert(result, gc.HasLen, 0) 930 } 931 932 type fixHook struct { 933 name string 934 } 935 936 func (s fixHook) step(c *gc.C, ctx *context) { 937 path := filepath.Join(ctx.path, "charm", "hooks", s.name) 938 ctx.writeHook(c, path, true) 939 } 940 941 type changeMeterStatus struct { 942 code string 943 info string 944 } 945 946 func (s changeMeterStatus) step(c *gc.C, ctx *context) { 947 err := ctx.unit.SetMeterStatus(s.code, s.info) 948 c.Assert(err, jc.ErrorIsNil) 949 } 950 951 type updateStatusHookTick struct{} 952 953 func (s updateStatusHookTick) step(c *gc.C, ctx *context) { 954 err := ctx.updateStatusHookTicker.Tick() 955 c.Assert(err, jc.ErrorIsNil) 956 } 957 958 type changeConfig map[string]interface{} 959 960 func (s changeConfig) step(c *gc.C, ctx *context) { 961 err := ctx.application.UpdateCharmConfig(corecharm.Settings(s)) 962 c.Assert(err, jc.ErrorIsNil) 963 } 964 965 type addAction struct { 966 name string 967 params map[string]interface{} 968 } 969 970 func (s addAction) step(c *gc.C, ctx *context) { 971 m, err := ctx.st.Model() 972 c.Assert(err, jc.ErrorIsNil) 973 _, err = m.EnqueueAction(ctx.unit.Tag(), s.name, s.params) 974 c.Assert(err, jc.ErrorIsNil) 975 } 976 977 type upgradeCharm struct { 978 revision int 979 forced bool 980 } 981 982 func (s upgradeCharm) step(c *gc.C, ctx *context) { 983 curl := curl(s.revision) 984 sch, err := ctx.st.Charm(curl) 985 c.Assert(err, jc.ErrorIsNil) 986 cfg := state.SetCharmConfig{ 987 Charm: sch, 988 ForceUnits: s.forced, 989 } 990 // Make sure we upload the charm before changing it in the DB. 991 serveCharm{}.step(c, ctx) 992 err = ctx.application.SetCharmProfile(sch.URL().String()) 993 c.Assert(err, jc.ErrorIsNil) 994 err = ctx.application.SetCharm(cfg) 995 c.Assert(err, jc.ErrorIsNil) 996 } 997 998 type verifyCharm struct { 999 revision int 1000 attemptedRevision int 1001 checkFiles ft.Entries 1002 } 1003 1004 func (s verifyCharm) step(c *gc.C, ctx *context) { 1005 s.checkFiles.Check(c, filepath.Join(ctx.path, "charm")) 1006 path := filepath.Join(ctx.path, "charm", "revision") 1007 content, err := ioutil.ReadFile(path) 1008 c.Assert(err, jc.ErrorIsNil) 1009 c.Assert(string(content), gc.Equals, strconv.Itoa(s.revision)) 1010 checkRevision := s.revision 1011 if s.attemptedRevision > checkRevision { 1012 checkRevision = s.attemptedRevision 1013 } 1014 err = ctx.unit.Refresh() 1015 c.Assert(err, jc.ErrorIsNil) 1016 url, ok := ctx.unit.CharmURL() 1017 c.Assert(ok, jc.IsTrue) 1018 c.Assert(url, gc.DeepEquals, curl(checkRevision)) 1019 } 1020 1021 type pushResource struct{} 1022 1023 func (s pushResource) step(c *gc.C, ctx *context) { 1024 opened := resourcetesting.NewResource(c, >.Stub{}, "data", ctx.unit.ApplicationName(), "the bytes") 1025 1026 res, err := ctx.st.Resources() 1027 c.Assert(err, jc.ErrorIsNil) 1028 _, err = res.SetResource( 1029 ctx.unit.ApplicationName(), 1030 opened.Username, 1031 opened.Resource.Resource, 1032 opened.ReadCloser, 1033 ) 1034 c.Assert(err, jc.ErrorIsNil) 1035 } 1036 1037 type startUpgradeError struct{} 1038 1039 func (s startUpgradeError) step(c *gc.C, ctx *context) { 1040 steps := []stepper{ 1041 createCharm{ 1042 customize: func(c *gc.C, ctx *context, path string) { 1043 appendHook(c, path, "start", "chmod 555 $CHARM_DIR") 1044 }, 1045 }, 1046 serveCharm{}, 1047 createUniter{}, 1048 waitUnitAgent{ 1049 status: status.Idle, 1050 }, 1051 waitHooks(startupHooks(false)), 1052 verifyCharm{}, 1053 1054 createCharm{revision: 1}, 1055 serveCharm{}, 1056 upgradeCharm{revision: 1}, 1057 waitUnitAgent{ 1058 statusGetter: unitStatusGetter, 1059 status: status.Error, 1060 info: "upgrade failed", 1061 charm: 1, 1062 }, 1063 verifyWaiting{}, 1064 verifyCharm{attemptedRevision: 1}, 1065 } 1066 for _, s_ := range steps { 1067 step(c, ctx, s_) 1068 } 1069 } 1070 1071 type verifyWaitingUpgradeError struct { 1072 revision int 1073 } 1074 1075 func (s verifyWaitingUpgradeError) step(c *gc.C, ctx *context) { 1076 verifyCharmSteps := []stepper{ 1077 waitUnitAgent{ 1078 statusGetter: unitStatusGetter, 1079 status: status.Error, 1080 info: "upgrade failed", 1081 charm: s.revision, 1082 }, 1083 verifyCharm{attemptedRevision: s.revision}, 1084 } 1085 verifyWaitingSteps := []stepper{ 1086 stopUniter{}, 1087 custom{func(c *gc.C, ctx *context) { 1088 // By setting status to Idle, and waiting for the restarted uniter 1089 // to reset the error status, we can avoid a race in which a subsequent 1090 // fixUpgradeError lands just before the restarting uniter retries the 1091 // upgrade; and thus puts us in an unexpected state for future steps. 1092 now := time.Now() 1093 sInfo := status.StatusInfo{ 1094 Status: status.Idle, 1095 Message: "", 1096 Since: &now, 1097 } 1098 err := ctx.unit.SetAgentStatus(sInfo) 1099 c.Check(err, jc.ErrorIsNil) 1100 }}, 1101 startUniter{}, 1102 } 1103 allSteps := append(verifyCharmSteps, verifyWaitingSteps...) 1104 allSteps = append(allSteps, verifyCharmSteps...) 1105 for _, s_ := range allSteps { 1106 step(c, ctx, s_) 1107 } 1108 } 1109 1110 type fixUpgradeError struct{} 1111 1112 func (s fixUpgradeError) step(c *gc.C, ctx *context) { 1113 charmPath := filepath.Join(ctx.path, "charm") 1114 err := os.Chmod(charmPath, 0755) 1115 c.Assert(err, jc.ErrorIsNil) 1116 } 1117 1118 type addRelation struct { 1119 waitJoin bool 1120 } 1121 1122 func (s addRelation) step(c *gc.C, ctx *context) { 1123 if ctx.relation != nil { 1124 panic("don't add two relations!") 1125 } 1126 if ctx.relatedSvc == nil { 1127 ctx.relatedSvc = ctx.s.AddTestingApplication(c, "mysql", ctx.s.AddTestingCharm(c, "mysql")) 1128 } 1129 eps, err := ctx.st.InferEndpoints("u", "mysql") 1130 c.Assert(err, jc.ErrorIsNil) 1131 ctx.relation, err = ctx.st.AddRelation(eps...) 1132 c.Assert(err, jc.ErrorIsNil) 1133 ctx.relationUnits = map[string]*state.RelationUnit{} 1134 if !s.waitJoin { 1135 return 1136 } 1137 1138 // It's hard to do this properly (watching scope) without perturbing other tests. 1139 ru, err := ctx.relation.Unit(ctx.unit) 1140 c.Assert(err, jc.ErrorIsNil) 1141 timeout := time.After(worstCase) 1142 for { 1143 c.Logf("waiting to join relation") 1144 select { 1145 case <-timeout: 1146 c.Fatalf("failed to join relation") 1147 case <-time.After(coretesting.ShortWait): 1148 inScope, err := ru.InScope() 1149 c.Assert(err, jc.ErrorIsNil) 1150 if inScope { 1151 return 1152 } 1153 } 1154 } 1155 } 1156 1157 type addRelationUnit struct{} 1158 1159 func (s addRelationUnit) step(c *gc.C, ctx *context) { 1160 u, err := ctx.relatedSvc.AddUnit(state.AddUnitParams{}) 1161 c.Assert(err, jc.ErrorIsNil) 1162 ru, err := ctx.relation.Unit(u) 1163 c.Assert(err, jc.ErrorIsNil) 1164 err = ru.EnterScope(nil) 1165 c.Assert(err, jc.ErrorIsNil) 1166 ctx.relationUnits[u.Name()] = ru 1167 } 1168 1169 type changeRelationUnit struct { 1170 name string 1171 } 1172 1173 func (s changeRelationUnit) step(c *gc.C, ctx *context) { 1174 settings, err := ctx.relationUnits[s.name].Settings() 1175 c.Assert(err, jc.ErrorIsNil) 1176 key := "madness?" 1177 raw, _ := settings.Get(key) 1178 val, _ := raw.(string) 1179 if val == "" { 1180 val = "this is juju" 1181 } else { 1182 val += "u" 1183 } 1184 settings.Set(key, val) 1185 _, err = settings.Write() 1186 c.Assert(err, jc.ErrorIsNil) 1187 } 1188 1189 type removeRelationUnit struct { 1190 name string 1191 } 1192 1193 func (s removeRelationUnit) step(c *gc.C, ctx *context) { 1194 err := ctx.relationUnits[s.name].LeaveScope() 1195 c.Assert(err, jc.ErrorIsNil) 1196 ctx.relationUnits[s.name] = nil 1197 } 1198 1199 type relationState struct { 1200 removed bool 1201 life state.Life 1202 } 1203 1204 func (s relationState) step(c *gc.C, ctx *context) { 1205 err := ctx.relation.Refresh() 1206 if s.removed { 1207 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1208 return 1209 } 1210 c.Assert(err, jc.ErrorIsNil) 1211 c.Assert(ctx.relation.Life(), gc.Equals, s.life) 1212 1213 } 1214 1215 type addSubordinateRelation struct { 1216 ifce string 1217 } 1218 1219 func (s addSubordinateRelation) step(c *gc.C, ctx *context) { 1220 if _, err := ctx.st.Application("logging"); errors.IsNotFound(err) { 1221 ctx.s.AddTestingApplication(c, "logging", ctx.s.AddTestingCharm(c, "logging")) 1222 } 1223 eps, err := ctx.st.InferEndpoints("logging", "u:"+s.ifce) 1224 c.Assert(err, jc.ErrorIsNil) 1225 _, err = ctx.st.AddRelation(eps...) 1226 c.Assert(err, jc.ErrorIsNil) 1227 } 1228 1229 type removeSubordinateRelation struct { 1230 ifce string 1231 } 1232 1233 func (s removeSubordinateRelation) step(c *gc.C, ctx *context) { 1234 eps, err := ctx.st.InferEndpoints("logging", "u:"+s.ifce) 1235 c.Assert(err, jc.ErrorIsNil) 1236 rel, err := ctx.st.EndpointsRelation(eps...) 1237 c.Assert(err, jc.ErrorIsNil) 1238 err = rel.Destroy() 1239 c.Assert(err, jc.ErrorIsNil) 1240 } 1241 1242 type waitSubordinateExists struct { 1243 name string 1244 } 1245 1246 func (s waitSubordinateExists) step(c *gc.C, ctx *context) { 1247 timeout := time.After(worstCase) 1248 for { 1249 ctx.s.BackingState.StartSync() 1250 select { 1251 case <-timeout: 1252 c.Fatalf("subordinate was not created") 1253 case <-time.After(coretesting.ShortWait): 1254 var err error 1255 ctx.subordinate, err = ctx.st.Unit(s.name) 1256 if errors.IsNotFound(err) { 1257 continue 1258 } 1259 c.Assert(err, jc.ErrorIsNil) 1260 return 1261 } 1262 } 1263 } 1264 1265 type waitSubordinateDying struct{} 1266 1267 func (waitSubordinateDying) step(c *gc.C, ctx *context) { 1268 timeout := time.After(worstCase) 1269 for { 1270 ctx.s.BackingState.StartSync() 1271 select { 1272 case <-timeout: 1273 c.Fatalf("subordinate was not made Dying") 1274 case <-time.After(coretesting.ShortWait): 1275 err := ctx.subordinate.Refresh() 1276 c.Assert(err, jc.ErrorIsNil) 1277 if ctx.subordinate.Life() != state.Dying { 1278 continue 1279 } 1280 } 1281 break 1282 } 1283 } 1284 1285 type removeSubordinate struct{} 1286 1287 func (removeSubordinate) step(c *gc.C, ctx *context) { 1288 err := ctx.subordinate.EnsureDead() 1289 c.Assert(err, jc.ErrorIsNil) 1290 err = ctx.subordinate.Remove() 1291 c.Assert(err, jc.ErrorIsNil) 1292 ctx.subordinate = nil 1293 } 1294 1295 type assertYaml struct { 1296 path string 1297 expect map[string]interface{} 1298 } 1299 1300 func (s assertYaml) step(c *gc.C, ctx *context) { 1301 data, err := ioutil.ReadFile(filepath.Join(ctx.path, s.path)) 1302 c.Assert(err, jc.ErrorIsNil) 1303 actual := make(map[string]interface{}) 1304 err = goyaml.Unmarshal(data, &actual) 1305 c.Assert(err, jc.ErrorIsNil) 1306 c.Assert(actual, gc.DeepEquals, s.expect) 1307 } 1308 1309 type writeFile struct { 1310 path string 1311 mode os.FileMode 1312 } 1313 1314 func (s writeFile) step(c *gc.C, ctx *context) { 1315 path := filepath.Join(ctx.path, s.path) 1316 dir := filepath.Dir(path) 1317 err := os.MkdirAll(dir, 0755) 1318 c.Assert(err, jc.ErrorIsNil) 1319 err = ioutil.WriteFile(path, nil, s.mode) 1320 c.Assert(err, jc.ErrorIsNil) 1321 } 1322 1323 type chmod struct { 1324 path string 1325 mode os.FileMode 1326 } 1327 1328 func (s chmod) step(c *gc.C, ctx *context) { 1329 path := filepath.Join(ctx.path, s.path) 1330 err := os.Chmod(path, s.mode) 1331 c.Assert(err, jc.ErrorIsNil) 1332 } 1333 1334 type custom struct { 1335 f func(*gc.C, *context) 1336 } 1337 1338 func (s custom) step(c *gc.C, ctx *context) { 1339 s.f(c, ctx) 1340 } 1341 1342 var relationDying = custom{func(c *gc.C, ctx *context) { 1343 c.Assert(ctx.relation.Destroy(), gc.IsNil) 1344 }} 1345 1346 var unitDying = custom{func(c *gc.C, ctx *context) { 1347 c.Assert(ctx.unit.Destroy(), gc.IsNil) 1348 }} 1349 1350 var unitDead = custom{func(c *gc.C, ctx *context) { 1351 c.Assert(ctx.unit.EnsureDead(), gc.IsNil) 1352 }} 1353 1354 var subordinateDying = custom{func(c *gc.C, ctx *context) { 1355 c.Assert(ctx.subordinate.Destroy(), gc.IsNil) 1356 }} 1357 1358 func curl(revision int) *corecharm.URL { 1359 return corecharm.MustParseURL("cs:quantal/wordpress").WithRevision(revision) 1360 } 1361 1362 func appendHook(c *gc.C, charm, name, data string) { 1363 path := filepath.Join(charm, "hooks", name+cmdSuffix) 1364 f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0755) 1365 c.Assert(err, jc.ErrorIsNil) 1366 defer f.Close() 1367 _, err = f.Write([]byte(data)) 1368 c.Assert(err, jc.ErrorIsNil) 1369 } 1370 1371 func renameRelation(c *gc.C, charmPath, oldName, newName string) { 1372 path := filepath.Join(charmPath, "metadata.yaml") 1373 f, err := os.Open(path) 1374 c.Assert(err, jc.ErrorIsNil) 1375 defer f.Close() 1376 meta, err := corecharm.ReadMeta(f) 1377 c.Assert(err, jc.ErrorIsNil) 1378 1379 replace := func(what map[string]corecharm.Relation) bool { 1380 for relName, relation := range what { 1381 if relName == oldName { 1382 what[newName] = relation 1383 delete(what, oldName) 1384 return true 1385 } 1386 } 1387 return false 1388 } 1389 replaced := replace(meta.Provides) || replace(meta.Requires) || replace(meta.Peers) 1390 c.Assert(replaced, gc.Equals, true, gc.Commentf("charm %q does not implement relation %q", charmPath, oldName)) 1391 1392 newmeta, err := goyaml.Marshal(meta) 1393 c.Assert(err, jc.ErrorIsNil) 1394 ioutil.WriteFile(path, newmeta, 0644) 1395 1396 f, err = os.Open(path) 1397 c.Assert(err, jc.ErrorIsNil) 1398 defer f.Close() 1399 _, err = corecharm.ReadMeta(f) 1400 c.Assert(err, jc.ErrorIsNil) 1401 } 1402 1403 type hookLock struct { 1404 releaser func() 1405 } 1406 1407 type hookStep struct { 1408 stepFunc func(*gc.C, *context) 1409 } 1410 1411 func (h *hookStep) step(c *gc.C, ctx *context) { 1412 h.stepFunc(c, ctx) 1413 } 1414 1415 func (h *hookLock) acquire() *hookStep { 1416 return &hookStep{stepFunc: func(c *gc.C, ctx *context) { 1417 releaser, err := processLock.Acquire(machinelock.Spec{ 1418 Worker: "uniter-test", 1419 Comment: "hookLock", 1420 Cancel: make(chan struct{}), // clearly suboptimal 1421 }) 1422 c.Assert(err, jc.ErrorIsNil) 1423 h.releaser = releaser 1424 }} 1425 } 1426 1427 func (h *hookLock) release() *hookStep { 1428 return &hookStep{stepFunc: func(c *gc.C, ctx *context) { 1429 c.Assert(h.releaser, gc.NotNil) 1430 h.releaser() 1431 h.releaser = nil 1432 }} 1433 } 1434 1435 type setProxySettings proxy.Settings 1436 1437 func (s setProxySettings) step(c *gc.C, ctx *context) { 1438 attrs := map[string]interface{}{ 1439 "http-proxy": s.Http, 1440 "https-proxy": s.Https, 1441 "ftp-proxy": s.Ftp, 1442 "no-proxy": s.NoProxy, 1443 } 1444 m, err := ctx.st.Model() 1445 c.Assert(err, jc.ErrorIsNil) 1446 err = m.UpdateModelConfig(attrs, nil) 1447 c.Assert(err, jc.ErrorIsNil) 1448 } 1449 1450 type relationRunCommands []string 1451 1452 func (cmds relationRunCommands) step(c *gc.C, ctx *context) { 1453 commands := strings.Join(cmds, "\n") 1454 args := uniter.RunCommandsArgs{ 1455 Commands: commands, 1456 RelationId: 0, 1457 RemoteUnitName: "", 1458 } 1459 result, err := ctx.uniter.RunCommands(args) 1460 c.Assert(err, jc.ErrorIsNil) 1461 c.Check(result.Code, gc.Equals, 0) 1462 c.Check(string(result.Stdout), gc.Equals, "") 1463 c.Check(string(result.Stderr), gc.Equals, "") 1464 } 1465 1466 type runCommands []string 1467 1468 func (cmds runCommands) step(c *gc.C, ctx *context) { 1469 commands := strings.Join(cmds, "\n") 1470 args := uniter.RunCommandsArgs{ 1471 Commands: commands, 1472 RelationId: -1, 1473 RemoteUnitName: "", 1474 } 1475 result, err := ctx.uniter.RunCommands(args) 1476 c.Assert(err, jc.ErrorIsNil) 1477 c.Check(result.Code, gc.Equals, 0) 1478 c.Check(string(result.Stdout), gc.Equals, "") 1479 c.Check(string(result.Stderr), gc.Equals, "") 1480 } 1481 1482 type asyncRunCommands []string 1483 1484 func (cmds asyncRunCommands) step(c *gc.C, ctx *context) { 1485 commands := strings.Join(cmds, "\n") 1486 args := uniter.RunCommandsArgs{ 1487 Commands: commands, 1488 RelationId: -1, 1489 RemoteUnitName: "", 1490 } 1491 1492 var socketPath string 1493 if runtime.GOOS == "windows" { 1494 socketPath = `\\.\pipe\unit-u-0-run` 1495 } else { 1496 socketPath = filepath.Join(ctx.path, "run.socket") 1497 } 1498 1499 ctx.wg.Add(1) 1500 go func() { 1501 defer ctx.wg.Done() 1502 // make sure the socket exists 1503 client, err := sockets.Dial(socketPath) 1504 // Don't use asserts in go routines. 1505 if !c.Check(err, jc.ErrorIsNil) { 1506 return 1507 } 1508 defer client.Close() 1509 1510 var result utilexec.ExecResponse 1511 err = client.Call(uniter.JujuRunEndpoint, args, &result) 1512 if c.Check(err, jc.ErrorIsNil) { 1513 c.Check(result.Code, gc.Equals, 0) 1514 c.Check(string(result.Stdout), gc.Equals, "") 1515 c.Check(string(result.Stderr), gc.Equals, "") 1516 } 1517 }() 1518 } 1519 1520 type waitContextWaitGroup struct{} 1521 1522 func (waitContextWaitGroup) step(c *gc.C, ctx *context) { 1523 ctx.wg.Wait() 1524 } 1525 1526 type forceMinion struct{} 1527 1528 func (forceMinion) step(c *gc.C, ctx *context) { 1529 ctx.leaderTracker.setLeader(c, false) 1530 } 1531 1532 type forceLeader struct{} 1533 1534 func (forceLeader) step(c *gc.C, ctx *context) { 1535 ctx.leaderTracker.setLeader(c, true) 1536 } 1537 1538 func newMockLeaderTracker(ctx *context) *mockLeaderTracker { 1539 return &mockLeaderTracker{ 1540 ctx: ctx, 1541 } 1542 } 1543 1544 type mockLeaderTracker struct { 1545 mu sync.Mutex 1546 ctx *context 1547 isLeader bool 1548 waiting []chan struct{} 1549 } 1550 1551 func (mock *mockLeaderTracker) ApplicationName() string { 1552 return mock.ctx.application.Name() 1553 } 1554 1555 func (mock *mockLeaderTracker) ClaimDuration() time.Duration { 1556 return 30 * time.Second 1557 } 1558 1559 func (mock *mockLeaderTracker) ClaimLeader() leadership.Ticket { 1560 mock.mu.Lock() 1561 defer mock.mu.Unlock() 1562 if mock.isLeader { 1563 return fastTicket{true} 1564 } 1565 return fastTicket{} 1566 } 1567 1568 func (mock *mockLeaderTracker) WaitLeader() leadership.Ticket { 1569 mock.mu.Lock() 1570 defer mock.mu.Unlock() 1571 if mock.isLeader { 1572 return fastTicket{} 1573 } 1574 return mock.waitTicket() 1575 } 1576 1577 func (mock *mockLeaderTracker) WaitMinion() leadership.Ticket { 1578 mock.mu.Lock() 1579 defer mock.mu.Unlock() 1580 if !mock.isLeader { 1581 return fastTicket{} 1582 } 1583 return mock.waitTicket() 1584 } 1585 1586 func (mock *mockLeaderTracker) waitTicket() leadership.Ticket { 1587 // very internal, expects mu to be locked already 1588 ch := make(chan struct{}) 1589 mock.waiting = append(mock.waiting, ch) 1590 return waitTicket{ch} 1591 } 1592 1593 func (mock *mockLeaderTracker) setLeader(c *gc.C, isLeader bool) { 1594 mock.mu.Lock() 1595 defer mock.mu.Unlock() 1596 if mock.isLeader == isLeader { 1597 return 1598 } 1599 if isLeader { 1600 claimer, err := mock.ctx.leaseManager.Claimer("application-leadership", mock.ctx.st.ModelUUID()) 1601 c.Assert(err, jc.ErrorIsNil) 1602 err = claimer.Claim( 1603 mock.ctx.application.Name(), mock.ctx.unit.Name(), time.Minute, 1604 ) 1605 c.Assert(err, jc.ErrorIsNil) 1606 } else { 1607 leaseClock.Advance(61 * time.Second) 1608 time.Sleep(coretesting.ShortWait) 1609 } 1610 mock.isLeader = isLeader 1611 for _, ch := range mock.waiting { 1612 close(ch) 1613 } 1614 mock.waiting = nil 1615 } 1616 1617 type waitTicket struct { 1618 ch chan struct{} 1619 } 1620 1621 func (t waitTicket) Ready() <-chan struct{} { 1622 return t.ch 1623 } 1624 1625 func (t waitTicket) Wait() bool { 1626 return false 1627 } 1628 1629 type fastTicket struct { 1630 value bool 1631 } 1632 1633 func (fastTicket) Ready() <-chan struct{} { 1634 ch := make(chan struct{}) 1635 close(ch) 1636 return ch 1637 } 1638 1639 func (t fastTicket) Wait() bool { 1640 return t.value 1641 } 1642 1643 type setLeaderSettings map[string]string 1644 1645 func (s setLeaderSettings) step(c *gc.C, ctx *context) { 1646 // We do this directly on State, not the API, so we don't have to worry 1647 // about getting an API conn for whatever unit's meant to be leader. 1648 err := ctx.application.UpdateLeaderSettings(successToken{}, s) 1649 c.Assert(err, jc.ErrorIsNil) 1650 ctx.s.BackingState.StartSync() 1651 } 1652 1653 type successToken struct{} 1654 1655 func (successToken) Check(int, interface{}) error { 1656 return nil 1657 } 1658 1659 type verifyLeaderSettings map[string]string 1660 1661 func (verify verifyLeaderSettings) step(c *gc.C, ctx *context) { 1662 actual, err := ctx.application.LeaderSettings() 1663 c.Assert(err, jc.ErrorIsNil) 1664 c.Assert(actual, jc.DeepEquals, map[string]string(verify)) 1665 } 1666 1667 type verifyFile struct { 1668 filename string 1669 content string 1670 } 1671 1672 func (verify verifyFile) fileExists() bool { 1673 _, err := os.Stat(verify.filename) 1674 return err == nil 1675 } 1676 1677 func (verify verifyFile) checkContent(c *gc.C) { 1678 content, err := ioutil.ReadFile(verify.filename) 1679 c.Assert(err, jc.ErrorIsNil) 1680 c.Assert(string(content), gc.Equals, verify.content) 1681 } 1682 1683 func (verify verifyFile) step(c *gc.C, ctx *context) { 1684 if verify.fileExists() { 1685 verify.checkContent(c) 1686 return 1687 } 1688 c.Logf("waiting for file: %s", verify.filename) 1689 timeout := time.After(worstCase) 1690 for { 1691 select { 1692 case <-time.After(coretesting.ShortWait): 1693 if verify.fileExists() { 1694 verify.checkContent(c) 1695 return 1696 } 1697 case <-timeout: 1698 c.Fatalf("file does not exist") 1699 } 1700 } 1701 } 1702 1703 // verify that the file does not exist 1704 type verifyNoFile struct { 1705 filename string 1706 } 1707 1708 func (verify verifyNoFile) step(c *gc.C, ctx *context) { 1709 c.Assert(verify.filename, jc.DoesNotExist) 1710 // Wait a short time and check again. 1711 time.Sleep(coretesting.ShortWait) 1712 c.Assert(verify.filename, jc.DoesNotExist) 1713 } 1714 1715 type mockCharmDirGuard struct{} 1716 1717 // Unlock implements fortress.Guard. 1718 func (*mockCharmDirGuard) Unlock() error { return nil } 1719 1720 // Lockdown implements fortress.Guard. 1721 func (*mockCharmDirGuard) Lockdown(_ fortress.Abort) error { return nil } 1722 1723 type provisionStorage struct{} 1724 1725 func (s provisionStorage) step(c *gc.C, ctx *context) { 1726 sb, err := state.NewStorageBackend(ctx.st) 1727 c.Assert(err, jc.ErrorIsNil) 1728 storageAttachments, err := sb.UnitStorageAttachments(ctx.unit.UnitTag()) 1729 c.Assert(err, jc.ErrorIsNil) 1730 c.Assert(storageAttachments, gc.HasLen, 1) 1731 1732 filesystem, err := sb.StorageInstanceFilesystem(storageAttachments[0].StorageInstance()) 1733 c.Assert(err, jc.ErrorIsNil) 1734 1735 filesystemInfo := state.FilesystemInfo{ 1736 Size: 1024, 1737 FilesystemId: "fs-id", 1738 } 1739 err = sb.SetFilesystemInfo(filesystem.FilesystemTag(), filesystemInfo) 1740 c.Assert(err, jc.ErrorIsNil) 1741 1742 machineId, err := ctx.unit.AssignedMachineId() 1743 c.Assert(err, jc.ErrorIsNil) 1744 1745 filesystemAttachmentInfo := state.FilesystemAttachmentInfo{ 1746 MountPoint: "/srv/wordpress/content", 1747 } 1748 err = sb.SetFilesystemAttachmentInfo( 1749 names.NewMachineTag(machineId), 1750 filesystem.FilesystemTag(), 1751 filesystemAttachmentInfo, 1752 ) 1753 c.Assert(err, jc.ErrorIsNil) 1754 } 1755 1756 type destroyStorageAttachment struct{} 1757 1758 func (s destroyStorageAttachment) step(c *gc.C, ctx *context) { 1759 sb, err := state.NewStorageBackend(ctx.st) 1760 c.Assert(err, jc.ErrorIsNil) 1761 storageAttachments, err := sb.UnitStorageAttachments(ctx.unit.UnitTag()) 1762 c.Assert(err, jc.ErrorIsNil) 1763 c.Assert(storageAttachments, gc.HasLen, 1) 1764 err = sb.DetachStorage( 1765 storageAttachments[0].StorageInstance(), 1766 ctx.unit.UnitTag(), 1767 ) 1768 c.Assert(err, jc.ErrorIsNil) 1769 } 1770 1771 type verifyStorageDetached struct{} 1772 1773 func (s verifyStorageDetached) step(c *gc.C, ctx *context) { 1774 sb, err := state.NewStorageBackend(ctx.st) 1775 c.Assert(err, jc.ErrorIsNil) 1776 storageAttachments, err := sb.UnitStorageAttachments(ctx.unit.UnitTag()) 1777 c.Assert(err, jc.ErrorIsNil) 1778 c.Assert(storageAttachments, gc.HasLen, 0) 1779 } 1780 1781 type expectError struct { 1782 err string 1783 } 1784 1785 func (s expectError) step(c *gc.C, ctx *context) { 1786 ctx.setExpectedError(s.err) 1787 } 1788 1789 // manualTicker will be used to generate collect-metrics events 1790 // in a time-independent manner for testing. 1791 type manualTicker struct { 1792 c chan time.Time 1793 } 1794 1795 // Tick sends a signal on the ticker channel. 1796 func (t *manualTicker) Tick() error { 1797 select { 1798 case t.c <- time.Now(): 1799 case <-time.After(worstCase): 1800 return fmt.Errorf("ticker channel blocked") 1801 } 1802 return nil 1803 } 1804 1805 type dummyWaiter struct { 1806 c chan time.Time 1807 } 1808 1809 func (w dummyWaiter) After() <-chan time.Time { 1810 return w.c 1811 } 1812 1813 // ReturnTimer can be used to replace the update status signal generator. 1814 func (t *manualTicker) ReturnTimer() remotestate.UpdateStatusTimerFunc { 1815 return func(_ time.Duration) remotestate.Waiter { 1816 return dummyWaiter{t.c} 1817 } 1818 } 1819 1820 func newManualTicker() *manualTicker { 1821 return &manualTicker{ 1822 c: make(chan time.Time), 1823 } 1824 } 1825 1826 // Instead of having a machine level lock that we have real contention with, 1827 // we instead fake it by creating a process lock. This will block callers within 1828 // the same process. This is necessary due to the function above to return the 1829 // machine lock. We create it once at process initialisation time and use it any 1830 // time the function is asked for. 1831 var processLock machinelock.Lock 1832 1833 func init() { 1834 processLock = &fakemachinelock{} 1835 } 1836 1837 type fakemachinelock struct { 1838 mu sync.Mutex 1839 } 1840 1841 func (f *fakemachinelock) Acquire(spec machinelock.Spec) (func(), error) { 1842 f.mu.Lock() 1843 return func() { 1844 f.mu.Unlock() 1845 }, nil 1846 } 1847 func (f *fakemachinelock) Report(opts ...machinelock.ReportOption) (string, error) { 1848 return "", nil 1849 }