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