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