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