launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/uniter_test.go (about) 1 // Copyright 2012, 2013 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 "net/rpc" 11 "net/url" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "strconv" 16 "strings" 17 stdtesting "testing" 18 "time" 19 20 errgo "launchpad.net/errgo/errors" 21 gc "launchpad.net/gocheck" 22 "launchpad.net/goyaml" 23 24 "launchpad.net/juju-core/agent/tools" 25 "launchpad.net/juju-core/charm" 26 "launchpad.net/juju-core/errors" 27 "launchpad.net/juju-core/juju/osenv" 28 "launchpad.net/juju-core/juju/testing" 29 "launchpad.net/juju-core/state" 30 "launchpad.net/juju-core/state/api" 31 "launchpad.net/juju-core/state/api/params" 32 apiuniter "launchpad.net/juju-core/state/api/uniter" 33 coretesting "launchpad.net/juju-core/testing" 34 jc "launchpad.net/juju-core/testing/checkers" 35 "launchpad.net/juju-core/utils" 36 utilexec "launchpad.net/juju-core/utils/exec" 37 "launchpad.net/juju-core/utils/fslock" 38 "launchpad.net/juju-core/worker" 39 "launchpad.net/juju-core/worker/uniter" 40 ) 41 42 // worstCase is used for timeouts when timing out 43 // will fail the test. Raising this value should 44 // not affect the overall running time of the tests 45 // unless they fail. 46 const worstCase = coretesting.LongWait 47 48 func TestPackage(t *stdtesting.T) { 49 coretesting.MgoTestPackage(t) 50 } 51 52 type UniterSuite struct { 53 coretesting.GitSuite 54 testing.JujuConnSuite 55 coretesting.HTTPSuite 56 dataDir string 57 oldLcAll string 58 unitDir string 59 60 st *api.State 61 uniter *apiuniter.State 62 } 63 64 var _ = gc.Suite(&UniterSuite{}) 65 66 func (s *UniterSuite) SetUpSuite(c *gc.C) { 67 s.JujuConnSuite.SetUpSuite(c) 68 s.HTTPSuite.SetUpSuite(c) 69 s.dataDir = c.MkDir() 70 toolsDir := tools.ToolsDir(s.dataDir, "unit-u-0") 71 err := os.MkdirAll(toolsDir, 0755) 72 c.Assert(err, gc.IsNil) 73 cmd := exec.Command("go", "build", "launchpad.net/juju-core/cmd/jujud") 74 cmd.Dir = toolsDir 75 out, err := cmd.CombinedOutput() 76 c.Logf(string(out)) 77 c.Assert(err, gc.IsNil) 78 s.oldLcAll = os.Getenv("LC_ALL") 79 os.Setenv("LC_ALL", "en_US") 80 s.unitDir = filepath.Join(s.dataDir, "agents", "unit-u-0") 81 } 82 83 func (s *UniterSuite) TearDownSuite(c *gc.C) { 84 os.Setenv("LC_ALL", s.oldLcAll) 85 s.HTTPSuite.TearDownSuite(c) 86 s.JujuConnSuite.TearDownSuite(c) 87 } 88 89 func (s *UniterSuite) SetUpTest(c *gc.C) { 90 s.GitSuite.SetUpTest(c) 91 s.JujuConnSuite.SetUpTest(c) 92 s.HTTPSuite.SetUpTest(c) 93 } 94 95 func (s *UniterSuite) TearDownTest(c *gc.C) { 96 s.ResetContext(c) 97 s.HTTPSuite.TearDownTest(c) 98 s.JujuConnSuite.TearDownTest(c) 99 s.GitSuite.TearDownTest(c) 100 } 101 102 func (s *UniterSuite) Reset(c *gc.C) { 103 s.JujuConnSuite.Reset(c) 104 s.ResetContext(c) 105 } 106 107 func (s *UniterSuite) ResetContext(c *gc.C) { 108 coretesting.Server.Flush() 109 err := os.RemoveAll(s.unitDir) 110 c.Assert(err, gc.IsNil) 111 } 112 113 func (s *UniterSuite) APILogin(c *gc.C, unit *state.Unit) { 114 password, err := utils.RandomPassword() 115 c.Assert(err, gc.IsNil) 116 err = unit.SetPassword(password) 117 c.Assert(err, gc.IsNil) 118 s.st = s.OpenAPIAs(c, unit.Tag(), password) 119 c.Assert(s.st, gc.NotNil) 120 c.Logf("API: login as %q successful", unit.Tag()) 121 s.uniter = s.st.Uniter() 122 c.Assert(s.uniter, gc.NotNil) 123 } 124 125 var _ worker.Worker = (*uniter.Uniter)(nil) 126 127 type uniterTest struct { 128 summary string 129 steps []stepper 130 } 131 132 func ut(summary string, steps ...stepper) uniterTest { 133 return uniterTest{summary, steps} 134 } 135 136 type stepper interface { 137 step(c *gc.C, ctx *context) 138 } 139 140 type context struct { 141 uuid string 142 path string 143 dataDir string 144 s *UniterSuite 145 st *state.State 146 charms coretesting.ResponseMap 147 hooks []string 148 sch *state.Charm 149 svc *state.Service 150 unit *state.Unit 151 uniter *uniter.Uniter 152 relatedSvc *state.Service 153 relation *state.Relation 154 relationUnits map[string]*state.RelationUnit 155 subordinate *state.Unit 156 157 hooksCompleted []string 158 } 159 160 var _ uniter.UniterExecutionObserver = (*context)(nil) 161 162 func (ctx *context) HookCompleted(hookName string) { 163 ctx.hooksCompleted = append(ctx.hooksCompleted, hookName) 164 } 165 166 func (ctx *context) HookFailed(hookName string) { 167 ctx.hooksCompleted = append(ctx.hooksCompleted, "fail-"+hookName) 168 } 169 170 func (ctx *context) run(c *gc.C, steps []stepper) { 171 defer func() { 172 if ctx.uniter != nil { 173 err := ctx.uniter.Stop() 174 c.Assert(err, gc.IsNil) 175 } 176 }() 177 for i, s := range steps { 178 c.Logf("step %d:\n", i) 179 step(c, ctx, s) 180 } 181 } 182 183 var goodHook = ` 184 #!/bin/bash --norc 185 juju-log $JUJU_ENV_UUID %s $JUJU_REMOTE_UNIT 186 `[1:] 187 188 var badHook = ` 189 #!/bin/bash --norc 190 juju-log $JUJU_ENV_UUID fail-%s $JUJU_REMOTE_UNIT 191 exit 1 192 `[1:] 193 194 func (ctx *context) writeHook(c *gc.C, path string, good bool) { 195 hook := badHook 196 if good { 197 hook = goodHook 198 } 199 content := fmt.Sprintf(hook, filepath.Base(path)) 200 err := ioutil.WriteFile(path, []byte(content), 0755) 201 c.Assert(err, gc.IsNil) 202 } 203 204 func (ctx *context) matchHooks(c *gc.C) (match bool, overshoot bool) { 205 c.Logf("ctx.hooksCompleted: %#v", ctx.hooksCompleted) 206 if len(ctx.hooksCompleted) < len(ctx.hooks) { 207 return false, false 208 } 209 for i, e := range ctx.hooks { 210 if ctx.hooksCompleted[i] != e { 211 return false, false 212 } 213 } 214 return true, len(ctx.hooksCompleted) > len(ctx.hooks) 215 } 216 217 var startupTests = []uniterTest{ 218 // Check conditions that can cause the uniter to fail to start. 219 ut( 220 "unable to create state dir", 221 writeFile{"state", 0644}, 222 createCharm{}, 223 createServiceAndUnit{}, 224 startUniter{}, 225 waitUniterDead{`failed to initialize uniter for "unit-u-0": .*not a directory`}, 226 ), ut( 227 "unknown unit", 228 // We still need to create a unit, because that's when we also 229 // connect to the API, but here we use a different service 230 // (and hence unit) name. 231 createCharm{}, 232 createServiceAndUnit{serviceName: "w"}, 233 startUniter{"unit-u-0"}, 234 waitUniterDead{`failed to initialize uniter for "unit-u-0": permission denied`}, 235 ), 236 } 237 238 func (s *UniterSuite) TestUniterStartup(c *gc.C) { 239 s.runUniterTests(c, startupTests) 240 } 241 242 var bootstrapTests = []uniterTest{ 243 // Check error conditions during unit bootstrap phase. 244 ut( 245 "insane deployment", 246 createCharm{}, 247 serveCharm{}, 248 writeFile{"charm", 0644}, 249 createUniter{}, 250 waitUniterDead{`ModeInstalling cs:quantal/wordpress-0: charm deployment failed: ".*charm" is not a directory`}, 251 ), ut( 252 "charm cannot be downloaded", 253 createCharm{}, 254 custom{func(c *gc.C, ctx *context) { 255 coretesting.Server.Response(404, nil, nil) 256 }}, 257 createUniter{}, 258 waitUniterDead{`ModeInstalling cs:quantal/wordpress-0: failed to download charm .* 404 Not Found`}, 259 ), 260 } 261 262 func (s *UniterSuite) TestUniterBootstrap(c *gc.C) { 263 s.runUniterTests(c, bootstrapTests) 264 } 265 266 var installHookTests = []uniterTest{ 267 ut( 268 "install hook fail and resolve", 269 startupError{"install"}, 270 verifyWaiting{}, 271 272 resolveError{state.ResolvedNoHooks}, 273 waitUnit{ 274 status: params.StatusStarted, 275 }, 276 waitHooks{"config-changed", "start"}, 277 ), ut( 278 "install hook fail and retry", 279 startupError{"install"}, 280 verifyWaiting{}, 281 282 resolveError{state.ResolvedRetryHooks}, 283 waitUnit{ 284 status: params.StatusError, 285 info: `hook failed: "install"`, 286 data: params.StatusData{ 287 "hook": "install", 288 }, 289 }, 290 waitHooks{"fail-install"}, 291 fixHook{"install"}, 292 verifyWaiting{}, 293 294 resolveError{state.ResolvedRetryHooks}, 295 waitUnit{ 296 status: params.StatusStarted, 297 }, 298 waitHooks{"install", "config-changed", "start"}, 299 ), 300 } 301 302 func (s *UniterSuite) TestUniterInstallHook(c *gc.C) { 303 s.runUniterTests(c, installHookTests) 304 } 305 306 var startHookTests = []uniterTest{ 307 ut( 308 "start hook fail and resolve", 309 startupError{"start"}, 310 verifyWaiting{}, 311 312 resolveError{state.ResolvedNoHooks}, 313 waitUnit{ 314 status: params.StatusStarted, 315 }, 316 waitHooks{"config-changed"}, 317 verifyRunning{}, 318 ), ut( 319 "start hook fail and retry", 320 startupError{"start"}, 321 verifyWaiting{}, 322 323 resolveError{state.ResolvedRetryHooks}, 324 waitUnit{ 325 status: params.StatusError, 326 info: `hook failed: "start"`, 327 data: params.StatusData{ 328 "hook": "start", 329 }, 330 }, 331 waitHooks{"fail-start"}, 332 verifyWaiting{}, 333 334 fixHook{"start"}, 335 resolveError{state.ResolvedRetryHooks}, 336 waitUnit{ 337 status: params.StatusStarted, 338 }, 339 waitHooks{"start", "config-changed"}, 340 verifyRunning{}, 341 ), 342 } 343 344 func (s *UniterSuite) TestUniterStartHook(c *gc.C) { 345 s.runUniterTests(c, startHookTests) 346 } 347 348 var multipleErrorsTests = []uniterTest{ 349 ut( 350 "resolved is cleared before moving on to next hook", 351 createCharm{badHooks: []string{"install", "config-changed", "start"}}, 352 serveCharm{}, 353 createUniter{}, 354 waitUnit{ 355 status: params.StatusError, 356 info: `hook failed: "install"`, 357 data: params.StatusData{ 358 "hook": "install", 359 }, 360 }, 361 resolveError{state.ResolvedNoHooks}, 362 waitUnit{ 363 status: params.StatusError, 364 info: `hook failed: "config-changed"`, 365 data: params.StatusData{ 366 "hook": "config-changed", 367 }, 368 }, 369 resolveError{state.ResolvedNoHooks}, 370 waitUnit{ 371 status: params.StatusError, 372 info: `hook failed: "start"`, 373 data: params.StatusData{ 374 "hook": "start", 375 }, 376 }, 377 ), 378 } 379 380 func (s *UniterSuite) TestUniterMultipleErrors(c *gc.C) { 381 s.runUniterTests(c, multipleErrorsTests) 382 } 383 384 var configChangedHookTests = []uniterTest{ 385 ut( 386 "config-changed hook fail and resolve", 387 startupError{"config-changed"}, 388 verifyWaiting{}, 389 390 // Note: we'll run another config-changed as soon as we hit the 391 // started state, so the broken hook would actually prevent us 392 // from advancing at all if we didn't fix it. 393 fixHook{"config-changed"}, 394 resolveError{state.ResolvedNoHooks}, 395 waitUnit{ 396 status: params.StatusStarted, 397 }, 398 waitHooks{"start", "config-changed"}, 399 // If we'd accidentally retried that hook, somehow, we would get 400 // an extra config-changed as we entered started; see that we don't. 401 waitHooks{}, 402 verifyRunning{}, 403 ), ut( 404 "config-changed hook fail and retry", 405 startupError{"config-changed"}, 406 verifyWaiting{}, 407 408 resolveError{state.ResolvedRetryHooks}, 409 waitUnit{ 410 status: params.StatusError, 411 info: `hook failed: "config-changed"`, 412 data: params.StatusData{ 413 "hook": "config-changed", 414 }, 415 }, 416 waitHooks{"fail-config-changed"}, 417 verifyWaiting{}, 418 419 fixHook{"config-changed"}, 420 resolveError{state.ResolvedRetryHooks}, 421 waitUnit{ 422 status: params.StatusStarted, 423 }, 424 waitHooks{"config-changed", "start"}, 425 verifyRunning{}, 426 ), 427 ut( 428 "steady state config change with config-get verification", 429 createCharm{ 430 customize: func(c *gc.C, ctx *context, path string) { 431 appendHook(c, path, "config-changed", "config-get --format yaml --output config.out") 432 }, 433 }, 434 serveCharm{}, 435 createUniter{}, 436 waitUnit{ 437 status: params.StatusStarted, 438 }, 439 waitHooks{"install", "config-changed", "start"}, 440 assertYaml{"charm/config.out", map[string]interface{}{ 441 "blog-title": "My Title", 442 }}, 443 changeConfig{"blog-title": "Goodness Gracious Me"}, 444 waitHooks{"config-changed"}, 445 verifyRunning{}, 446 assertYaml{"charm/config.out", map[string]interface{}{ 447 "blog-title": "Goodness Gracious Me", 448 }}, 449 )} 450 451 func (s *UniterSuite) TestUniterConfigChangedHook(c *gc.C) { 452 s.runUniterTests(c, configChangedHookTests) 453 } 454 455 var hookSynchronizationTests = []uniterTest{ 456 ut( 457 "verify config change hook not run while lock held", 458 quickStart{}, 459 acquireHookSyncLock{}, 460 changeConfig{"blog-title": "Goodness Gracious Me"}, 461 waitHooks{}, 462 releaseHookSyncLock, 463 waitHooks{"config-changed"}, 464 ), 465 ut( 466 "verify held lock by this unit is broken", 467 acquireHookSyncLock{"u/0:fake"}, 468 quickStart{}, 469 verifyHookSyncLockUnlocked, 470 ), 471 ut( 472 "verify held lock by another unit is not broken", 473 acquireHookSyncLock{"u/1:fake"}, 474 // Can't use quickstart as it has a built in waitHooks. 475 createCharm{}, 476 serveCharm{}, 477 ensureStateWorker{}, 478 createServiceAndUnit{}, 479 startUniter{}, 480 waitAddresses{}, 481 waitHooks{}, 482 verifyHookSyncLockLocked, 483 releaseHookSyncLock, 484 waitUnit{status: params.StatusStarted}, 485 waitHooks{"install", "config-changed", "start"}, 486 ), 487 } 488 489 func (s *UniterSuite) TestUniterHookSynchronisation(c *gc.C) { 490 s.runUniterTests(c, hookSynchronizationTests) 491 } 492 493 var dyingReactionTests = []uniterTest{ 494 // Reaction to entity deaths. 495 ut( 496 "steady state service dying", 497 quickStart{}, 498 serviceDying, 499 waitHooks{"stop"}, 500 waitUniterDead{}, 501 ), ut( 502 "steady state unit dying", 503 quickStart{}, 504 unitDying, 505 waitHooks{"stop"}, 506 waitUniterDead{}, 507 ), ut( 508 "steady state unit dead", 509 quickStart{}, 510 unitDead, 511 waitUniterDead{}, 512 waitHooks{}, 513 ), ut( 514 "hook error service dying", 515 startupError{"start"}, 516 serviceDying, 517 verifyWaiting{}, 518 fixHook{"start"}, 519 resolveError{state.ResolvedRetryHooks}, 520 waitHooks{"start", "config-changed", "stop"}, 521 waitUniterDead{}, 522 ), ut( 523 "hook error unit dying", 524 startupError{"start"}, 525 unitDying, 526 verifyWaiting{}, 527 fixHook{"start"}, 528 resolveError{state.ResolvedRetryHooks}, 529 waitHooks{"start", "config-changed", "stop"}, 530 waitUniterDead{}, 531 ), ut( 532 "hook error unit dead", 533 startupError{"start"}, 534 unitDead, 535 waitUniterDead{}, 536 waitHooks{}, 537 ), 538 } 539 540 func (s *UniterSuite) TestUniterDyingReaction(c *gc.C) { 541 s.runUniterTests(c, dyingReactionTests) 542 } 543 544 var steadyUpgradeTests = []uniterTest{ 545 // Upgrade scenarios from steady state. 546 ut( 547 "steady state upgrade", 548 quickStart{}, 549 createCharm{revision: 1}, 550 upgradeCharm{revision: 1}, 551 waitUnit{ 552 status: params.StatusStarted, 553 charm: 1, 554 }, 555 waitHooks{"upgrade-charm", "config-changed"}, 556 verifyCharm{revision: 1}, 557 verifyRunning{}, 558 ), ut( 559 "steady state forced upgrade (identical behaviour)", 560 quickStart{}, 561 createCharm{revision: 1}, 562 upgradeCharm{revision: 1, forced: true}, 563 waitUnit{ 564 status: params.StatusStarted, 565 charm: 1, 566 }, 567 waitHooks{"upgrade-charm", "config-changed"}, 568 verifyCharm{revision: 1}, 569 verifyRunning{}, 570 ), ut( 571 "steady state upgrade hook fail and resolve", 572 quickStart{}, 573 createCharm{revision: 1, badHooks: []string{"upgrade-charm"}}, 574 upgradeCharm{revision: 1}, 575 waitUnit{ 576 status: params.StatusError, 577 info: `hook failed: "upgrade-charm"`, 578 data: params.StatusData{ 579 "hook": "upgrade-charm", 580 }, 581 charm: 1, 582 }, 583 waitHooks{"fail-upgrade-charm"}, 584 verifyCharm{revision: 1}, 585 verifyWaiting{}, 586 587 resolveError{state.ResolvedNoHooks}, 588 waitUnit{ 589 status: params.StatusStarted, 590 charm: 1, 591 }, 592 waitHooks{"config-changed"}, 593 verifyRunning{}, 594 ), ut( 595 "steady state upgrade hook fail and retry", 596 quickStart{}, 597 createCharm{revision: 1, badHooks: []string{"upgrade-charm"}}, 598 upgradeCharm{revision: 1}, 599 waitUnit{ 600 status: params.StatusError, 601 info: `hook failed: "upgrade-charm"`, 602 data: params.StatusData{ 603 "hook": "upgrade-charm", 604 }, 605 charm: 1, 606 }, 607 waitHooks{"fail-upgrade-charm"}, 608 verifyCharm{revision: 1}, 609 verifyWaiting{}, 610 611 resolveError{state.ResolvedRetryHooks}, 612 waitUnit{ 613 status: params.StatusError, 614 info: `hook failed: "upgrade-charm"`, 615 data: params.StatusData{ 616 "hook": "upgrade-charm", 617 }, 618 charm: 1, 619 }, 620 waitHooks{"fail-upgrade-charm"}, 621 verifyWaiting{}, 622 623 fixHook{"upgrade-charm"}, 624 resolveError{state.ResolvedRetryHooks}, 625 waitUnit{ 626 status: params.StatusStarted, 627 charm: 1, 628 }, 629 waitHooks{"upgrade-charm", "config-changed"}, 630 verifyRunning{}, 631 ), 632 ut( 633 // This test does an add-relation as quickly as possible 634 // after an upgrade-charm, in the hope that the scheduler will 635 // deliver the events in the wrong order. The observed 636 // behaviour should be the same in either case. 637 "ignore unknown relations until upgrade is done", 638 quickStart{}, 639 createCharm{ 640 revision: 2, 641 customize: func(c *gc.C, ctx *context, path string) { 642 renameRelation(c, path, "db", "db2") 643 hpath := filepath.Join(path, "hooks", "db2-relation-joined") 644 ctx.writeHook(c, hpath, true) 645 }, 646 }, 647 serveCharm{}, 648 upgradeCharm{revision: 2}, 649 addRelation{}, 650 addRelationUnit{}, 651 waitHooks{"upgrade-charm", "config-changed", "db2-relation-joined mysql/0 db2:0"}, 652 verifyCharm{revision: 2}, 653 ), 654 } 655 656 func (s *UniterSuite) TestUniterSteadyStateUpgrade(c *gc.C) { 657 s.runUniterTests(c, steadyUpgradeTests) 658 } 659 660 var errorUpgradeTests = []uniterTest{ 661 // Upgrade scenarios from error state. 662 ut( 663 "error state unforced upgrade (ignored until started state)", 664 startupError{"start"}, 665 createCharm{revision: 1}, 666 upgradeCharm{revision: 1}, 667 waitUnit{ 668 status: params.StatusError, 669 info: `hook failed: "start"`, 670 data: params.StatusData{ 671 "hook": "start", 672 }, 673 }, 674 waitHooks{}, 675 verifyCharm{}, 676 verifyWaiting{}, 677 678 resolveError{state.ResolvedNoHooks}, 679 waitUnit{ 680 status: params.StatusStarted, 681 charm: 1, 682 }, 683 waitHooks{"config-changed", "upgrade-charm", "config-changed"}, 684 verifyCharm{revision: 1}, 685 verifyRunning{}, 686 ), ut( 687 "error state forced upgrade", 688 startupError{"start"}, 689 createCharm{revision: 1}, 690 upgradeCharm{revision: 1, forced: true}, 691 // It's not possible to tell directly from state when the upgrade is 692 // complete, because the new unit charm URL is set at the upgrade 693 // process's point of no return (before actually deploying, but after 694 // the charm has been downloaded and verified). However, it's still 695 // useful to wait until that point... 696 waitUnit{ 697 status: params.StatusError, 698 info: `hook failed: "start"`, 699 data: params.StatusData{ 700 "hook": "start", 701 }, 702 charm: 1, 703 }, 704 // ...because the uniter *will* complete a started deployment even if 705 // we stop it from outside. So, by stopping and starting, we can be 706 // sure that the operation has completed and can safely verify that 707 // the charm state on disk is as we expect. 708 verifyWaiting{}, 709 verifyCharm{revision: 1}, 710 711 resolveError{state.ResolvedNoHooks}, 712 waitUnit{ 713 status: params.StatusStarted, 714 charm: 1, 715 }, 716 waitHooks{"config-changed"}, 717 verifyRunning{}, 718 ), 719 } 720 721 func (s *UniterSuite) TestUniterErrorStateUpgrade(c *gc.C) { 722 s.runUniterTests(c, errorUpgradeTests) 723 } 724 725 var upgradeConflictsTests = []uniterTest{ 726 // Upgrade scenarios - handling conflicts. 727 ut( 728 "upgrade: conflicting files", 729 startUpgradeError{}, 730 731 // NOTE: this is just dumbly committing the conflicts, but AFAICT this 732 // is the only reasonable solution; if the user tells us it's resolved 733 // we have to take their word for it. 734 resolveError{state.ResolvedNoHooks}, 735 waitHooks{"upgrade-charm", "config-changed"}, 736 waitUnit{ 737 status: params.StatusStarted, 738 charm: 1, 739 }, 740 verifyCharm{revision: 1}, 741 ), ut( 742 `upgrade: conflicting directories`, 743 createCharm{ 744 customize: func(c *gc.C, ctx *context, path string) { 745 err := os.Mkdir(filepath.Join(path, "data"), 0755) 746 c.Assert(err, gc.IsNil) 747 appendHook(c, path, "start", "echo DATA > data/newfile") 748 }, 749 }, 750 serveCharm{}, 751 createUniter{}, 752 waitUnit{ 753 status: params.StatusStarted, 754 }, 755 waitHooks{"install", "config-changed", "start"}, 756 verifyCharm{}, 757 758 createCharm{ 759 revision: 1, 760 customize: func(c *gc.C, ctx *context, path string) { 761 data := filepath.Join(path, "data") 762 err := ioutil.WriteFile(data, []byte("<nelson>ha ha</nelson>"), 0644) 763 c.Assert(err, gc.IsNil) 764 }, 765 }, 766 serveCharm{}, 767 upgradeCharm{revision: 1}, 768 waitUnit{ 769 status: params.StatusError, 770 info: "upgrade failed", 771 charm: 1, 772 }, 773 verifyWaiting{}, 774 verifyCharm{dirty: true}, 775 776 resolveError{state.ResolvedNoHooks}, 777 waitHooks{"upgrade-charm", "config-changed"}, 778 waitUnit{ 779 status: params.StatusStarted, 780 charm: 1, 781 }, 782 verifyCharm{revision: 1}, 783 ), ut( 784 "upgrade conflict resolved with forced upgrade", 785 startUpgradeError{}, 786 createCharm{ 787 revision: 2, 788 customize: func(c *gc.C, ctx *context, path string) { 789 otherdata := filepath.Join(path, "otherdata") 790 err := ioutil.WriteFile(otherdata, []byte("blah"), 0644) 791 c.Assert(err, gc.IsNil) 792 }, 793 }, 794 serveCharm{}, 795 upgradeCharm{revision: 2, forced: true}, 796 waitUnit{ 797 status: params.StatusStarted, 798 charm: 2, 799 }, 800 waitHooks{"upgrade-charm", "config-changed"}, 801 verifyCharm{revision: 2}, 802 custom{func(c *gc.C, ctx *context) { 803 // otherdata should exist (in v2) 804 otherdata, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "otherdata")) 805 c.Assert(err, gc.IsNil) 806 c.Assert(string(otherdata), gc.Equals, "blah") 807 808 // ignore should not (only in v1) 809 _, err = os.Stat(filepath.Join(ctx.path, "charm", "ignore")) 810 c.Assert(err, jc.Satisfies, os.IsNotExist) 811 812 // data should contain what was written in the start hook 813 data, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "data")) 814 c.Assert(err, gc.IsNil) 815 c.Assert(string(data), gc.Equals, "STARTDATA\n") 816 }}, 817 ), ut( 818 "upgrade conflict service dying", 819 startUpgradeError{}, 820 serviceDying, 821 verifyWaiting{}, 822 resolveError{state.ResolvedNoHooks}, 823 waitHooks{"upgrade-charm", "config-changed", "stop"}, 824 waitUniterDead{}, 825 ), ut( 826 "upgrade conflict unit dying", 827 startUpgradeError{}, 828 unitDying, 829 verifyWaiting{}, 830 resolveError{state.ResolvedNoHooks}, 831 waitHooks{"upgrade-charm", "config-changed", "stop"}, 832 waitUniterDead{}, 833 ), ut( 834 "upgrade conflict unit dead", 835 startUpgradeError{}, 836 unitDead, 837 waitUniterDead{}, 838 waitHooks{}, 839 ), 840 } 841 842 func (s *UniterSuite) TestUniterUpgradeConflicts(c *gc.C) { 843 s.runUniterTests(c, upgradeConflictsTests) 844 } 845 846 func (s *UniterSuite) TestRunCommand(c *gc.C) { 847 testDir := c.MkDir() 848 testFile := func(name string) string { 849 return filepath.Join(testDir, name) 850 } 851 echoUnitNameToFile := func(name string) string { 852 return fmt.Sprintf("echo juju run ${JUJU_UNIT_NAME} > %s", filepath.Join(testDir, name)) 853 } 854 tests := []uniterTest{ 855 ut( 856 "run commands: environment", 857 quickStart{}, 858 runCommands{echoUnitNameToFile("run.output")}, 859 verifyFile{filepath.Join(testDir, "run.output"), "juju run u/0\n"}, 860 ), ut( 861 "run commands: jujuc commands", 862 quickStartRelation{}, 863 runCommands{ 864 fmt.Sprintf("owner-get tag > %s", testFile("jujuc.output")), 865 fmt.Sprintf("unit-get private-address >> %s", testFile("jujuc.output")), 866 fmt.Sprintf("unit-get public-address >> %s", testFile("jujuc.output")), 867 }, 868 verifyFile{ 869 testFile("jujuc.output"), 870 "user-admin\nprivate.dummy.address.example.com\npublic.dummy.address.example.com\n", 871 }, 872 ), ut( 873 "run commands: proxy settings set", 874 quickStartRelation{}, 875 setProxySettings{Http: "http", Https: "https", Ftp: "ftp"}, 876 runCommands{ 877 fmt.Sprintf("echo $http_proxy > %s", testFile("proxy.output")), 878 fmt.Sprintf("echo $HTTP_PROXY >> %s", testFile("proxy.output")), 879 fmt.Sprintf("echo $https_proxy >> %s", testFile("proxy.output")), 880 fmt.Sprintf("echo $HTTPS_PROXY >> %s", testFile("proxy.output")), 881 fmt.Sprintf("echo $ftp_proxy >> %s", testFile("proxy.output")), 882 fmt.Sprintf("echo $FTP_PROXY >> %s", testFile("proxy.output")), 883 }, 884 verifyFile{ 885 testFile("proxy.output"), 886 "http\nhttp\nhttps\nhttps\nftp\nftp\n", 887 }, 888 ), ut( 889 "run commands: async using rpc client", 890 quickStart{}, 891 asyncRunCommands{echoUnitNameToFile("run.output")}, 892 verifyFile{testFile("run.output"), "juju run u/0\n"}, 893 ), ut( 894 "run commands: waits for lock", 895 quickStart{}, 896 acquireHookSyncLock{}, 897 asyncRunCommands{echoUnitNameToFile("wait.output")}, 898 verifyNoFile{testFile("wait.output")}, 899 releaseHookSyncLock, 900 verifyFile{testFile("wait.output"), "juju run u/0\n"}, 901 ), 902 } 903 s.runUniterTests(c, tests) 904 } 905 906 var relationsTests = []uniterTest{ 907 // Relations. 908 ut( 909 "simple joined/changed/departed", 910 quickStartRelation{}, 911 addRelationUnit{}, 912 waitHooks{"db-relation-joined mysql/1 db:0", "db-relation-changed mysql/1 db:0"}, 913 changeRelationUnit{"mysql/0"}, 914 waitHooks{"db-relation-changed mysql/0 db:0"}, 915 removeRelationUnit{"mysql/1"}, 916 waitHooks{"db-relation-departed mysql/1 db:0"}, 917 verifyRunning{}, 918 ), ut( 919 "relation becomes dying; unit is not last remaining member", 920 quickStartRelation{}, 921 relationDying, 922 waitHooks{"db-relation-departed mysql/0 db:0", "db-relation-broken db:0"}, 923 verifyRunning{}, 924 relationState{life: state.Dying}, 925 removeRelationUnit{"mysql/0"}, 926 verifyRunning{}, 927 relationState{removed: true}, 928 verifyRunning{}, 929 ), ut( 930 "relation becomes dying; unit is last remaining member", 931 quickStartRelation{}, 932 removeRelationUnit{"mysql/0"}, 933 waitHooks{"db-relation-departed mysql/0 db:0"}, 934 relationDying, 935 waitHooks{"db-relation-broken db:0"}, 936 verifyRunning{}, 937 relationState{removed: true}, 938 verifyRunning{}, 939 ), ut( 940 "service becomes dying while in a relation", 941 quickStartRelation{}, 942 serviceDying, 943 waitHooks{"db-relation-departed mysql/0 db:0", "db-relation-broken db:0", "stop"}, 944 waitUniterDead{}, 945 relationState{life: state.Dying}, 946 removeRelationUnit{"mysql/0"}, 947 relationState{removed: true}, 948 ), ut( 949 "unit becomes dying while in a relation", 950 quickStartRelation{}, 951 unitDying, 952 waitHooks{"db-relation-departed mysql/0 db:0", "db-relation-broken db:0", "stop"}, 953 waitUniterDead{}, 954 relationState{life: state.Alive}, 955 removeRelationUnit{"mysql/0"}, 956 relationState{life: state.Alive}, 957 ), ut( 958 "unit becomes dead while in a relation", 959 quickStartRelation{}, 960 unitDead, 961 waitUniterDead{}, 962 waitHooks{}, 963 // TODO BUG(?): the unit doesn't leave the scope, leaving the relation 964 // unkillable without direct intervention. I'm pretty sure it's not a 965 // uniter bug -- it should be the responisbility of `juju remove-unit 966 // --force` to cause the unit to leave any relation scopes it may be 967 // in -- but it's worth noting here all the same. 968 ), 969 } 970 971 func (s *UniterSuite) TestUniterRelations(c *gc.C) { 972 s.runUniterTests(c, relationsTests) 973 } 974 975 var relationsErrorTests = []uniterTest{ 976 ut( 977 "hook error during join of a relation", 978 startupRelationError{"db-relation-joined"}, 979 waitUnit{ 980 status: params.StatusError, 981 info: `hook failed: "db-relation-joined"`, 982 data: params.StatusData{ 983 "hook": "db-relation-joined", 984 "relation-id": 0, 985 "remote-unit": "mysql/0", 986 }, 987 }, 988 ), ut( 989 "hook error during change of a relation", 990 startupRelationError{"db-relation-changed"}, 991 waitUnit{ 992 status: params.StatusError, 993 info: `hook failed: "db-relation-changed"`, 994 data: params.StatusData{ 995 "hook": "db-relation-changed", 996 "relation-id": 0, 997 "remote-unit": "mysql/0", 998 }, 999 }, 1000 ), ut( 1001 "hook error after a unit departed", 1002 startupRelationError{"db-relation-departed"}, 1003 waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"}, 1004 removeRelationUnit{"mysql/0"}, 1005 waitUnit{ 1006 status: params.StatusError, 1007 info: `hook failed: "db-relation-departed"`, 1008 data: params.StatusData{ 1009 "hook": "db-relation-departed", 1010 "relation-id": 0, 1011 "remote-unit": "mysql/0", 1012 }, 1013 }, 1014 ), 1015 ut( 1016 "hook error after a relation died", 1017 startupRelationError{"db-relation-broken"}, 1018 waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"}, 1019 relationDying, 1020 waitUnit{ 1021 status: params.StatusError, 1022 info: `hook failed: "db-relation-broken"`, 1023 data: params.StatusData{ 1024 "hook": "db-relation-broken", 1025 "relation-id": 0, 1026 }, 1027 }, 1028 ), 1029 } 1030 1031 func (s *UniterSuite) TestUniterRelationErrors(c *gc.C) { 1032 s.runUniterTests(c, relationsErrorTests) 1033 } 1034 1035 var subordinatesTests = []uniterTest{ 1036 // Subordinates. 1037 ut( 1038 "unit becomes dying while subordinates exist", 1039 quickStart{}, 1040 addSubordinateRelation{"juju-info"}, 1041 waitSubordinateExists{"logging/0"}, 1042 unitDying, 1043 waitSubordinateDying{}, 1044 waitHooks{"stop"}, 1045 verifyRunning{true}, 1046 removeSubordinate{}, 1047 waitUniterDead{}, 1048 ), ut( 1049 "new subordinate becomes necessary while old one is dying", 1050 quickStart{}, 1051 addSubordinateRelation{"juju-info"}, 1052 waitSubordinateExists{"logging/0"}, 1053 removeSubordinateRelation{"juju-info"}, 1054 // The subordinate Uniter would usually set Dying in this situation. 1055 subordinateDying, 1056 addSubordinateRelation{"logging-dir"}, 1057 verifyRunning{}, 1058 removeSubordinate{}, 1059 waitSubordinateExists{"logging/1"}, 1060 ), 1061 } 1062 1063 func (s *UniterSuite) TestUniterSubordinates(c *gc.C) { 1064 s.runUniterTests(c, subordinatesTests) 1065 } 1066 1067 func (s *UniterSuite) runUniterTests(c *gc.C, uniterTests []uniterTest) { 1068 for i, t := range uniterTests { 1069 c.Logf("\ntest %d: %s\n", i, t.summary) 1070 func() { 1071 defer s.Reset(c) 1072 env, err := s.State.Environment() 1073 c.Assert(err, gc.IsNil) 1074 ctx := &context{ 1075 s: s, 1076 st: s.State, 1077 uuid: env.UUID(), 1078 path: s.unitDir, 1079 dataDir: s.dataDir, 1080 charms: coretesting.ResponseMap{}, 1081 } 1082 ctx.run(c, t.steps) 1083 }() 1084 } 1085 } 1086 1087 func (s *UniterSuite) TestSubordinateDying(c *gc.C) { 1088 // Create a test context for later use. 1089 ctx := &context{ 1090 s: s, 1091 st: s.State, 1092 path: filepath.Join(s.dataDir, "agents", "unit-u-0"), 1093 dataDir: s.dataDir, 1094 charms: coretesting.ResponseMap{}, 1095 } 1096 1097 testing.AddStateServerMachine(c, ctx.st) 1098 1099 // Create the subordinate service. 1100 dir := coretesting.Charms.ClonedDir(c.MkDir(), "logging") 1101 curl, err := charm.ParseURL("cs:quantal/logging") 1102 c.Assert(err, gc.IsNil) 1103 curl = curl.WithRevision(dir.Revision()) 1104 step(c, ctx, addCharm{dir, curl}) 1105 ctx.svc = s.AddTestingService(c, "u", ctx.sch) 1106 1107 // Create the principal service and add a relation. 1108 wps := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1109 wpu, err := wps.AddUnit() 1110 c.Assert(err, gc.IsNil) 1111 eps, err := s.State.InferEndpoints([]string{"wordpress", "u"}) 1112 c.Assert(err, gc.IsNil) 1113 rel, err := s.State.AddRelation(eps...) 1114 c.Assert(err, gc.IsNil) 1115 1116 // Create the subordinate unit by entering scope as the principal. 1117 wpru, err := rel.Unit(wpu) 1118 c.Assert(err, gc.IsNil) 1119 err = wpru.EnterScope(nil) 1120 c.Assert(err, gc.IsNil) 1121 ctx.unit, err = s.State.Unit("u/0") 1122 c.Assert(err, gc.IsNil) 1123 1124 s.APILogin(c, ctx.unit) 1125 1126 // Run the actual test. 1127 ctx.run(c, []stepper{ 1128 serveCharm{}, 1129 startUniter{}, 1130 waitAddresses{}, 1131 custom{func(c *gc.C, ctx *context) { 1132 c.Assert(rel.Destroy(), gc.IsNil) 1133 }}, 1134 waitUniterDead{}, 1135 }) 1136 } 1137 1138 func step(c *gc.C, ctx *context, s stepper) { 1139 c.Logf("%#v", s) 1140 s.step(c, ctx) 1141 } 1142 1143 type ensureStateWorker struct { 1144 } 1145 1146 func (s ensureStateWorker) step(c *gc.C, ctx *context) { 1147 addresses, err := ctx.st.Addresses() 1148 if err != nil || len(addresses) == 0 { 1149 testing.AddStateServerMachine(c, ctx.st) 1150 } 1151 addresses, err = ctx.st.APIAddresses() 1152 c.Assert(err, gc.IsNil) 1153 c.Assert(addresses, gc.HasLen, 1) 1154 } 1155 1156 type createCharm struct { 1157 revision int 1158 badHooks []string 1159 customize func(*gc.C, *context, string) 1160 } 1161 1162 var charmHooks = []string{ 1163 "install", "start", "config-changed", "upgrade-charm", "stop", 1164 "db-relation-joined", "db-relation-changed", "db-relation-departed", 1165 "db-relation-broken", 1166 } 1167 1168 func (s createCharm) step(c *gc.C, ctx *context) { 1169 base := coretesting.Charms.ClonedDirPath(c.MkDir(), "wordpress") 1170 for _, name := range charmHooks { 1171 path := filepath.Join(base, "hooks", name) 1172 good := true 1173 for _, bad := range s.badHooks { 1174 if name == bad { 1175 good = false 1176 } 1177 } 1178 ctx.writeHook(c, path, good) 1179 } 1180 if s.customize != nil { 1181 s.customize(c, ctx, base) 1182 } 1183 dir, err := charm.ReadDir(base) 1184 c.Assert(err, gc.IsNil) 1185 err = dir.SetDiskRevision(s.revision) 1186 c.Assert(err, gc.IsNil) 1187 step(c, ctx, addCharm{dir, curl(s.revision)}) 1188 } 1189 1190 type addCharm struct { 1191 dir *charm.Dir 1192 curl *charm.URL 1193 } 1194 1195 func (s addCharm) step(c *gc.C, ctx *context) { 1196 var buf bytes.Buffer 1197 err := s.dir.BundleTo(&buf) 1198 c.Assert(err, gc.IsNil) 1199 body := buf.Bytes() 1200 hash, _, err := utils.ReadSHA256(&buf) 1201 c.Assert(err, gc.IsNil) 1202 key := fmt.Sprintf("/charms/%s/%d", s.dir.Meta().Name, s.dir.Revision()) 1203 hurl, err := url.Parse(coretesting.Server.URL + key) 1204 c.Assert(err, gc.IsNil) 1205 ctx.charms[key] = coretesting.Response{200, nil, body} 1206 ctx.sch, err = ctx.st.AddCharm(s.dir, s.curl, hurl, hash) 1207 c.Assert(err, gc.IsNil) 1208 } 1209 1210 type serveCharm struct{} 1211 1212 func (serveCharm) step(c *gc.C, ctx *context) { 1213 coretesting.Server.ResponseMap(1, ctx.charms) 1214 } 1215 1216 type createServiceAndUnit struct { 1217 serviceName string 1218 } 1219 1220 func (csau createServiceAndUnit) step(c *gc.C, ctx *context) { 1221 if csau.serviceName == "" { 1222 csau.serviceName = "u" 1223 } 1224 sch, err := ctx.st.Charm(curl(0)) 1225 c.Assert(err, gc.IsNil) 1226 svc := ctx.s.AddTestingService(c, csau.serviceName, sch) 1227 unit, err := svc.AddUnit() 1228 c.Assert(err, gc.IsNil) 1229 1230 // Assign the unit to a provisioned machine to match expected state. 1231 err = unit.AssignToNewMachine() 1232 c.Assert(err, gc.IsNil) 1233 mid, err := unit.AssignedMachineId() 1234 c.Assert(err, gc.IsNil) 1235 machine, err := ctx.st.Machine(mid) 1236 c.Assert(err, gc.IsNil) 1237 err = machine.SetProvisioned("i-exist", "fake_nonce", nil) 1238 c.Assert(err, gc.IsNil) 1239 ctx.svc = svc 1240 ctx.unit = unit 1241 1242 ctx.s.APILogin(c, unit) 1243 } 1244 1245 type createUniter struct{} 1246 1247 func (createUniter) step(c *gc.C, ctx *context) { 1248 step(c, ctx, ensureStateWorker{}) 1249 step(c, ctx, createServiceAndUnit{}) 1250 step(c, ctx, startUniter{}) 1251 step(c, ctx, waitAddresses{}) 1252 } 1253 1254 type waitAddresses struct{} 1255 1256 func (waitAddresses) step(c *gc.C, ctx *context) { 1257 timeout := time.After(worstCase) 1258 for { 1259 select { 1260 case <-timeout: 1261 c.Fatalf("timed out waiting for unit addresses") 1262 case <-time.After(coretesting.ShortWait): 1263 err := ctx.unit.Refresh() 1264 if err != nil { 1265 c.Fatalf("unit refresh failed: %v", err) 1266 } 1267 // GZ 2013-07-10: Hardcoded values from dummy environ 1268 // special cased here, questionable. 1269 private, _ := ctx.unit.PrivateAddress() 1270 if private != "private.dummy.address.example.com" { 1271 continue 1272 } 1273 public, _ := ctx.unit.PublicAddress() 1274 if public != "public.dummy.address.example.com" { 1275 continue 1276 } 1277 return 1278 } 1279 } 1280 } 1281 1282 type startUniter struct { 1283 unitTag string 1284 } 1285 1286 func (s startUniter) step(c *gc.C, ctx *context) { 1287 if s.unitTag == "" { 1288 s.unitTag = "unit-u-0" 1289 } 1290 if ctx.uniter != nil { 1291 panic("don't start two uniters!") 1292 } 1293 if ctx.s.uniter == nil { 1294 panic("API connection not established") 1295 } 1296 ctx.uniter = uniter.NewUniter(ctx.s.uniter, s.unitTag, ctx.dataDir) 1297 uniter.SetUniterObserver(ctx.uniter, ctx) 1298 } 1299 1300 type waitUniterDead struct { 1301 err string 1302 } 1303 1304 func (s waitUniterDead) step(c *gc.C, ctx *context) { 1305 if s.err != "" { 1306 err := s.waitDead(c, ctx) 1307 c.Assert(err, gc.ErrorMatches, s.err) 1308 return 1309 } 1310 // In the default case, we're waiting for worker.ErrTerminateAgent, but 1311 // the path to that error can be tricky. If the unit becomes Dead at an 1312 // inconvenient time, unrelated calls can fail -- as they should -- but 1313 // not be detected as worker.ErrTerminateAgent. In this case, we restart 1314 // the uniter and check that it fails as expected when starting up; this 1315 // mimics the behaviour of the unit agent and verifies that the UA will, 1316 // eventually, see the correct error and respond appropriately. 1317 err := s.waitDead(c, ctx) 1318 if err != worker.ErrTerminateAgent { 1319 step(c, ctx, startUniter{}) 1320 err = s.waitDead(c, ctx) 1321 } 1322 c.Assert(errgo.Cause(err), gc.Equals, worker.ErrTerminateAgent) 1323 err = ctx.unit.Refresh() 1324 c.Assert(err, gc.IsNil) 1325 c.Assert(ctx.unit.Life(), gc.Equals, state.Dead) 1326 } 1327 1328 func (s waitUniterDead) waitDead(c *gc.C, ctx *context) error { 1329 u := ctx.uniter 1330 ctx.uniter = nil 1331 timeout := time.After(worstCase) 1332 for { 1333 // The repeated StartSync is to ensure timely completion of this method 1334 // in the case(s) where a state change causes a uniter action which 1335 // causes a state change which causes a uniter action, in which case we 1336 // need more than one sync. At the moment there's only one situation 1337 // that causes this -- setting the unit's service to Dying -- but it's 1338 // not an intrinsically insane pattern of action (and helps to simplify 1339 // the filter code) so this test seems like a small price to pay. 1340 ctx.s.BackingState.StartSync() 1341 select { 1342 case <-u.Dead(): 1343 return u.Wait() 1344 case <-time.After(coretesting.ShortWait): 1345 continue 1346 case <-timeout: 1347 c.Fatalf("uniter still alive") 1348 } 1349 } 1350 } 1351 1352 type stopUniter struct { 1353 err string 1354 } 1355 1356 func (s stopUniter) step(c *gc.C, ctx *context) { 1357 u := ctx.uniter 1358 ctx.uniter = nil 1359 err := u.Stop() 1360 if s.err == "" { 1361 c.Assert(err, gc.IsNil) 1362 } else { 1363 c.Assert(err, gc.ErrorMatches, s.err) 1364 } 1365 } 1366 1367 type verifyWaiting struct{} 1368 1369 func (s verifyWaiting) step(c *gc.C, ctx *context) { 1370 step(c, ctx, stopUniter{}) 1371 step(c, ctx, startUniter{}) 1372 step(c, ctx, waitHooks{}) 1373 } 1374 1375 type verifyRunning struct { 1376 noHooks bool 1377 } 1378 1379 func (s verifyRunning) step(c *gc.C, ctx *context) { 1380 step(c, ctx, stopUniter{}) 1381 step(c, ctx, startUniter{}) 1382 if s.noHooks { 1383 step(c, ctx, waitHooks{}) 1384 } else { 1385 step(c, ctx, waitHooks{"config-changed"}) 1386 } 1387 } 1388 1389 type startupError struct { 1390 badHook string 1391 } 1392 1393 func (s startupError) step(c *gc.C, ctx *context) { 1394 step(c, ctx, createCharm{badHooks: []string{s.badHook}}) 1395 step(c, ctx, serveCharm{}) 1396 step(c, ctx, createUniter{}) 1397 step(c, ctx, waitUnit{ 1398 status: params.StatusError, 1399 info: fmt.Sprintf(`hook failed: %q`, s.badHook), 1400 }) 1401 for _, hook := range []string{"install", "config-changed", "start"} { 1402 if hook == s.badHook { 1403 step(c, ctx, waitHooks{"fail-" + hook}) 1404 break 1405 } 1406 step(c, ctx, waitHooks{hook}) 1407 } 1408 step(c, ctx, verifyCharm{}) 1409 } 1410 1411 type quickStart struct{} 1412 1413 func (s quickStart) step(c *gc.C, ctx *context) { 1414 step(c, ctx, createCharm{}) 1415 step(c, ctx, serveCharm{}) 1416 step(c, ctx, createUniter{}) 1417 step(c, ctx, waitUnit{status: params.StatusStarted}) 1418 step(c, ctx, waitHooks{"install", "config-changed", "start"}) 1419 step(c, ctx, verifyCharm{}) 1420 } 1421 1422 type quickStartRelation struct{} 1423 1424 func (s quickStartRelation) step(c *gc.C, ctx *context) { 1425 step(c, ctx, quickStart{}) 1426 step(c, ctx, addRelation{}) 1427 step(c, ctx, addRelationUnit{}) 1428 step(c, ctx, waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"}) 1429 step(c, ctx, verifyRunning{}) 1430 } 1431 1432 type startupRelationError struct { 1433 badHook string 1434 } 1435 1436 func (s startupRelationError) step(c *gc.C, ctx *context) { 1437 step(c, ctx, createCharm{badHooks: []string{s.badHook}}) 1438 step(c, ctx, serveCharm{}) 1439 step(c, ctx, createUniter{}) 1440 step(c, ctx, waitUnit{status: params.StatusStarted}) 1441 step(c, ctx, waitHooks{"install", "config-changed", "start"}) 1442 step(c, ctx, verifyCharm{}) 1443 step(c, ctx, addRelation{}) 1444 step(c, ctx, addRelationUnit{}) 1445 } 1446 1447 type resolveError struct { 1448 resolved state.ResolvedMode 1449 } 1450 1451 func (s resolveError) step(c *gc.C, ctx *context) { 1452 err := ctx.unit.SetResolved(s.resolved) 1453 c.Assert(err, gc.IsNil) 1454 } 1455 1456 type waitUnit struct { 1457 status params.Status 1458 info string 1459 data params.StatusData 1460 charm int 1461 resolved state.ResolvedMode 1462 } 1463 1464 func (s waitUnit) step(c *gc.C, ctx *context) { 1465 timeout := time.After(worstCase) 1466 for { 1467 ctx.s.BackingState.StartSync() 1468 select { 1469 case <-time.After(coretesting.ShortWait): 1470 err := ctx.unit.Refresh() 1471 if err != nil { 1472 c.Fatalf("cannot refresh unit: %v", err) 1473 } 1474 resolved := ctx.unit.Resolved() 1475 if resolved != s.resolved { 1476 c.Logf("want resolved mode %q, got %q; still waiting", s.resolved, resolved) 1477 continue 1478 } 1479 url, ok := ctx.unit.CharmURL() 1480 if !ok || *url != *curl(s.charm) { 1481 var got string 1482 if ok { 1483 got = url.String() 1484 } 1485 c.Logf("want unit charm %q, got %q; still waiting", curl(s.charm), got) 1486 continue 1487 } 1488 status, info, data, err := ctx.unit.Status() 1489 c.Assert(err, gc.IsNil) 1490 if status != s.status { 1491 c.Logf("want unit status %q, got %q; still waiting", s.status, status) 1492 continue 1493 } 1494 if info != s.info { 1495 c.Logf("want unit status info %q, got %q; still waiting", s.info, info) 1496 continue 1497 } 1498 if s.data != nil { 1499 if len(data) != len(s.data) { 1500 c.Logf("want %d unit status data value(s), got %d; still waiting", len(s.data), len(data)) 1501 continue 1502 } 1503 for key, value := range s.data { 1504 if data[key] != value { 1505 c.Logf("want unit status data value %q for key %q, got %q; still waiting", 1506 value, key, data[key]) 1507 continue 1508 } 1509 } 1510 } 1511 return 1512 case <-timeout: 1513 c.Fatalf("never reached desired status") 1514 } 1515 } 1516 } 1517 1518 type waitHooks []string 1519 1520 func (s waitHooks) step(c *gc.C, ctx *context) { 1521 if len(s) == 0 { 1522 // Give unwanted hooks a moment to run... 1523 ctx.s.BackingState.StartSync() 1524 time.Sleep(coretesting.ShortWait) 1525 } 1526 ctx.hooks = append(ctx.hooks, s...) 1527 c.Logf("waiting for hooks: %#v", ctx.hooks) 1528 match, overshoot := ctx.matchHooks(c) 1529 if overshoot && len(s) == 0 { 1530 c.Fatalf("ran more hooks than expected") 1531 } 1532 if match { 1533 return 1534 } 1535 timeout := time.After(worstCase) 1536 for { 1537 ctx.s.BackingState.StartSync() 1538 select { 1539 case <-time.After(coretesting.ShortWait): 1540 if match, _ = ctx.matchHooks(c); match { 1541 return 1542 } 1543 case <-timeout: 1544 c.Fatalf("never got expected hooks") 1545 } 1546 } 1547 } 1548 1549 type fixHook struct { 1550 name string 1551 } 1552 1553 func (s fixHook) step(c *gc.C, ctx *context) { 1554 path := filepath.Join(ctx.path, "charm", "hooks", s.name) 1555 ctx.writeHook(c, path, true) 1556 } 1557 1558 type changeConfig map[string]interface{} 1559 1560 func (s changeConfig) step(c *gc.C, ctx *context) { 1561 err := ctx.svc.UpdateConfigSettings(charm.Settings(s)) 1562 c.Assert(err, gc.IsNil) 1563 } 1564 1565 type upgradeCharm struct { 1566 revision int 1567 forced bool 1568 } 1569 1570 func (s upgradeCharm) step(c *gc.C, ctx *context) { 1571 sch, err := ctx.st.Charm(curl(s.revision)) 1572 c.Assert(err, gc.IsNil) 1573 err = ctx.svc.SetCharm(sch, s.forced) 1574 c.Assert(err, gc.IsNil) 1575 serveCharm{}.step(c, ctx) 1576 } 1577 1578 type verifyCharm struct { 1579 revision int 1580 dirty bool 1581 } 1582 1583 func (s verifyCharm) step(c *gc.C, ctx *context) { 1584 if !s.dirty { 1585 path := filepath.Join(ctx.path, "charm", "revision") 1586 content, err := ioutil.ReadFile(path) 1587 c.Assert(err, gc.IsNil) 1588 c.Assert(string(content), gc.Equals, strconv.Itoa(s.revision)) 1589 err = ctx.unit.Refresh() 1590 c.Assert(err, gc.IsNil) 1591 url, ok := ctx.unit.CharmURL() 1592 c.Assert(ok, gc.Equals, true) 1593 c.Assert(url, gc.DeepEquals, curl(s.revision)) 1594 } 1595 1596 // Before we try to check the git status, make sure expected hooks are all 1597 // complete, to prevent the test and the uniter interfering with each other. 1598 step(c, ctx, waitHooks{}) 1599 cmd := exec.Command("git", "status") 1600 cmd.Dir = filepath.Join(ctx.path, "charm") 1601 out, err := cmd.CombinedOutput() 1602 c.Assert(err, gc.IsNil) 1603 cmp := gc.Matches 1604 if s.dirty { 1605 cmp = gc.Not(gc.Matches) 1606 } 1607 c.Assert(string(out), cmp, "(# )?On branch master\nnothing to commit.*\n") 1608 } 1609 1610 type startUpgradeError struct{} 1611 1612 func (s startUpgradeError) step(c *gc.C, ctx *context) { 1613 steps := []stepper{ 1614 createCharm{ 1615 customize: func(c *gc.C, ctx *context, path string) { 1616 appendHook(c, path, "start", "echo STARTDATA > data") 1617 }, 1618 }, 1619 serveCharm{}, 1620 createUniter{}, 1621 waitUnit{ 1622 status: params.StatusStarted, 1623 }, 1624 waitHooks{"install", "config-changed", "start"}, 1625 verifyCharm{}, 1626 1627 createCharm{ 1628 revision: 1, 1629 customize: func(c *gc.C, ctx *context, path string) { 1630 data := filepath.Join(path, "data") 1631 err := ioutil.WriteFile(data, []byte("<nelson>ha ha</nelson>"), 0644) 1632 c.Assert(err, gc.IsNil) 1633 ignore := filepath.Join(path, "ignore") 1634 err = ioutil.WriteFile(ignore, []byte("anything"), 0644) 1635 c.Assert(err, gc.IsNil) 1636 }, 1637 }, 1638 serveCharm{}, 1639 upgradeCharm{revision: 1}, 1640 waitUnit{ 1641 status: params.StatusError, 1642 info: "upgrade failed", 1643 charm: 1, 1644 }, 1645 verifyWaiting{}, 1646 verifyCharm{dirty: true}, 1647 } 1648 for _, s_ := range steps { 1649 step(c, ctx, s_) 1650 } 1651 } 1652 1653 type addRelation struct { 1654 testing.JujuConnSuite 1655 } 1656 1657 func (s addRelation) step(c *gc.C, ctx *context) { 1658 if ctx.relation != nil { 1659 panic("don't add two relations!") 1660 } 1661 if ctx.relatedSvc == nil { 1662 ctx.relatedSvc = ctx.s.AddTestingService(c, "mysql", ctx.s.AddTestingCharm(c, "mysql")) 1663 } 1664 eps, err := ctx.st.InferEndpoints([]string{"u", "mysql"}) 1665 c.Assert(err, gc.IsNil) 1666 ctx.relation, err = ctx.st.AddRelation(eps...) 1667 c.Assert(err, gc.IsNil) 1668 ctx.relationUnits = map[string]*state.RelationUnit{} 1669 } 1670 1671 type addRelationUnit struct{} 1672 1673 func (s addRelationUnit) step(c *gc.C, ctx *context) { 1674 u, err := ctx.relatedSvc.AddUnit() 1675 c.Assert(err, gc.IsNil) 1676 ru, err := ctx.relation.Unit(u) 1677 c.Assert(err, gc.IsNil) 1678 err = ru.EnterScope(nil) 1679 c.Assert(err, gc.IsNil) 1680 ctx.relationUnits[u.Name()] = ru 1681 } 1682 1683 type changeRelationUnit struct { 1684 name string 1685 } 1686 1687 func (s changeRelationUnit) step(c *gc.C, ctx *context) { 1688 settings, err := ctx.relationUnits[s.name].Settings() 1689 c.Assert(err, gc.IsNil) 1690 key := "madness?" 1691 raw, _ := settings.Get(key) 1692 val, _ := raw.(string) 1693 if val == "" { 1694 val = "this is juju" 1695 } else { 1696 val += "u" 1697 } 1698 settings.Set(key, val) 1699 _, err = settings.Write() 1700 c.Assert(err, gc.IsNil) 1701 } 1702 1703 type removeRelationUnit struct { 1704 name string 1705 } 1706 1707 func (s removeRelationUnit) step(c *gc.C, ctx *context) { 1708 err := ctx.relationUnits[s.name].LeaveScope() 1709 c.Assert(err, gc.IsNil) 1710 ctx.relationUnits[s.name] = nil 1711 } 1712 1713 type relationState struct { 1714 removed bool 1715 life state.Life 1716 } 1717 1718 func (s relationState) step(c *gc.C, ctx *context) { 1719 err := ctx.relation.Refresh() 1720 if s.removed { 1721 c.Assert(err, jc.Satisfies, errors.IsNotFoundError) 1722 return 1723 } 1724 c.Assert(err, gc.IsNil) 1725 c.Assert(ctx.relation.Life(), gc.Equals, s.life) 1726 1727 } 1728 1729 type addSubordinateRelation struct { 1730 ifce string 1731 } 1732 1733 func (s addSubordinateRelation) step(c *gc.C, ctx *context) { 1734 if _, err := ctx.st.Service("logging"); errors.IsNotFoundError(err) { 1735 ctx.s.AddTestingService(c, "logging", ctx.s.AddTestingCharm(c, "logging")) 1736 } 1737 eps, err := ctx.st.InferEndpoints([]string{"logging", "u:" + s.ifce}) 1738 c.Assert(err, gc.IsNil) 1739 _, err = ctx.st.AddRelation(eps...) 1740 c.Assert(err, gc.IsNil) 1741 } 1742 1743 type removeSubordinateRelation struct { 1744 ifce string 1745 } 1746 1747 func (s removeSubordinateRelation) step(c *gc.C, ctx *context) { 1748 eps, err := ctx.st.InferEndpoints([]string{"logging", "u:" + s.ifce}) 1749 c.Assert(err, gc.IsNil) 1750 rel, err := ctx.st.EndpointsRelation(eps...) 1751 c.Assert(err, gc.IsNil) 1752 err = rel.Destroy() 1753 c.Assert(err, gc.IsNil) 1754 } 1755 1756 type waitSubordinateExists struct { 1757 name string 1758 } 1759 1760 func (s waitSubordinateExists) step(c *gc.C, ctx *context) { 1761 timeout := time.After(worstCase) 1762 for { 1763 ctx.s.BackingState.StartSync() 1764 select { 1765 case <-timeout: 1766 c.Fatalf("subordinate was not created") 1767 case <-time.After(coretesting.ShortWait): 1768 var err error 1769 ctx.subordinate, err = ctx.st.Unit(s.name) 1770 if errors.IsNotFoundError(err) { 1771 continue 1772 } 1773 c.Assert(err, gc.IsNil) 1774 return 1775 } 1776 } 1777 } 1778 1779 type waitSubordinateDying struct{} 1780 1781 func (waitSubordinateDying) step(c *gc.C, ctx *context) { 1782 timeout := time.After(worstCase) 1783 for { 1784 ctx.s.BackingState.StartSync() 1785 select { 1786 case <-timeout: 1787 c.Fatalf("subordinate was not made Dying") 1788 case <-time.After(coretesting.ShortWait): 1789 err := ctx.subordinate.Refresh() 1790 c.Assert(err, gc.IsNil) 1791 if ctx.subordinate.Life() != state.Dying { 1792 continue 1793 } 1794 } 1795 break 1796 } 1797 } 1798 1799 type removeSubordinate struct{} 1800 1801 func (removeSubordinate) step(c *gc.C, ctx *context) { 1802 err := ctx.subordinate.EnsureDead() 1803 c.Assert(err, gc.IsNil) 1804 err = ctx.subordinate.Remove() 1805 c.Assert(err, gc.IsNil) 1806 ctx.subordinate = nil 1807 } 1808 1809 type assertYaml struct { 1810 path string 1811 expect map[string]interface{} 1812 } 1813 1814 func (s assertYaml) step(c *gc.C, ctx *context) { 1815 data, err := ioutil.ReadFile(filepath.Join(ctx.path, s.path)) 1816 c.Assert(err, gc.IsNil) 1817 actual := make(map[string]interface{}) 1818 err = goyaml.Unmarshal(data, &actual) 1819 c.Assert(err, gc.IsNil) 1820 c.Assert(actual, gc.DeepEquals, s.expect) 1821 } 1822 1823 type writeFile struct { 1824 path string 1825 mode os.FileMode 1826 } 1827 1828 func (s writeFile) step(c *gc.C, ctx *context) { 1829 path := filepath.Join(ctx.path, s.path) 1830 dir := filepath.Dir(path) 1831 err := os.MkdirAll(dir, 0755) 1832 c.Assert(err, gc.IsNil) 1833 err = ioutil.WriteFile(path, nil, s.mode) 1834 c.Assert(err, gc.IsNil) 1835 } 1836 1837 type chmod struct { 1838 path string 1839 mode os.FileMode 1840 } 1841 1842 func (s chmod) step(c *gc.C, ctx *context) { 1843 path := filepath.Join(ctx.path, s.path) 1844 err := os.Chmod(path, s.mode) 1845 c.Assert(err, gc.IsNil) 1846 } 1847 1848 type custom struct { 1849 f func(*gc.C, *context) 1850 } 1851 1852 func (s custom) step(c *gc.C, ctx *context) { 1853 s.f(c, ctx) 1854 } 1855 1856 var serviceDying = custom{func(c *gc.C, ctx *context) { 1857 c.Assert(ctx.svc.Destroy(), gc.IsNil) 1858 }} 1859 1860 var relationDying = custom{func(c *gc.C, ctx *context) { 1861 c.Assert(ctx.relation.Destroy(), gc.IsNil) 1862 }} 1863 1864 var unitDying = custom{func(c *gc.C, ctx *context) { 1865 c.Assert(ctx.unit.Destroy(), gc.IsNil) 1866 }} 1867 1868 var unitDead = custom{func(c *gc.C, ctx *context) { 1869 c.Assert(ctx.unit.EnsureDead(), gc.IsNil) 1870 }} 1871 1872 var subordinateDying = custom{func(c *gc.C, ctx *context) { 1873 c.Assert(ctx.subordinate.Destroy(), gc.IsNil) 1874 }} 1875 1876 func curl(revision int) *charm.URL { 1877 return charm.MustParseURL("cs:quantal/wordpress").WithRevision(revision) 1878 } 1879 1880 func appendHook(c *gc.C, charm, name, data string) { 1881 path := filepath.Join(charm, "hooks", name) 1882 f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0755) 1883 c.Assert(err, gc.IsNil) 1884 defer f.Close() 1885 _, err = f.Write([]byte(data)) 1886 c.Assert(err, gc.IsNil) 1887 } 1888 1889 func renameRelation(c *gc.C, charmPath, oldName, newName string) { 1890 path := filepath.Join(charmPath, "metadata.yaml") 1891 f, err := os.Open(path) 1892 c.Assert(err, gc.IsNil) 1893 defer f.Close() 1894 meta, err := charm.ReadMeta(f) 1895 c.Assert(err, gc.IsNil) 1896 1897 replace := func(what map[string]charm.Relation) bool { 1898 for relName, relation := range what { 1899 if relName == oldName { 1900 what[newName] = relation 1901 delete(what, oldName) 1902 return true 1903 } 1904 } 1905 return false 1906 } 1907 replaced := replace(meta.Provides) || replace(meta.Requires) || replace(meta.Peers) 1908 c.Assert(replaced, gc.Equals, true, gc.Commentf("charm %q does not implement relation %q", charmPath, oldName)) 1909 1910 newmeta, err := goyaml.Marshal(meta) 1911 c.Assert(err, gc.IsNil) 1912 ioutil.WriteFile(path, newmeta, 0644) 1913 1914 f, err = os.Open(path) 1915 c.Assert(err, gc.IsNil) 1916 defer f.Close() 1917 meta, err = charm.ReadMeta(f) 1918 c.Assert(err, gc.IsNil) 1919 } 1920 1921 func createHookLock(c *gc.C, dataDir string) *fslock.Lock { 1922 lockDir := filepath.Join(dataDir, "locks") 1923 lock, err := fslock.NewLock(lockDir, "uniter-hook-execution") 1924 c.Assert(err, gc.IsNil) 1925 return lock 1926 } 1927 1928 type acquireHookSyncLock struct { 1929 message string 1930 } 1931 1932 func (s acquireHookSyncLock) step(c *gc.C, ctx *context) { 1933 lock := createHookLock(c, ctx.dataDir) 1934 c.Assert(lock.IsLocked(), gc.Equals, false) 1935 err := lock.Lock(s.message) 1936 c.Assert(err, gc.IsNil) 1937 } 1938 1939 var releaseHookSyncLock = custom{func(c *gc.C, ctx *context) { 1940 lock := createHookLock(c, ctx.dataDir) 1941 // Force the release. 1942 err := lock.BreakLock() 1943 c.Assert(err, gc.IsNil) 1944 }} 1945 1946 var verifyHookSyncLockUnlocked = custom{func(c *gc.C, ctx *context) { 1947 lock := createHookLock(c, ctx.dataDir) 1948 c.Assert(lock.IsLocked(), jc.IsFalse) 1949 }} 1950 1951 var verifyHookSyncLockLocked = custom{func(c *gc.C, ctx *context) { 1952 lock := createHookLock(c, ctx.dataDir) 1953 c.Assert(lock.IsLocked(), jc.IsTrue) 1954 }} 1955 1956 type setProxySettings osenv.ProxySettings 1957 1958 func (s setProxySettings) step(c *gc.C, ctx *context) { 1959 old, err := ctx.st.EnvironConfig() 1960 c.Assert(err, gc.IsNil) 1961 cfg, err := old.Apply(map[string]interface{}{ 1962 "http-proxy": s.Http, 1963 "https-proxy": s.Https, 1964 "ftp-proxy": s.Ftp, 1965 }) 1966 c.Assert(err, gc.IsNil) 1967 err = ctx.st.SetEnvironConfig(cfg, old) 1968 c.Assert(err, gc.IsNil) 1969 // wait for the new values... 1970 expected := (osenv.ProxySettings)(s) 1971 for attempt := coretesting.LongAttempt.Start(); attempt.Next(); { 1972 if ctx.uniter.GetProxyValues() == expected { 1973 // Also confirm that the values were specified for the environment. 1974 c.Assert(os.Getenv("http_proxy"), gc.Equals, expected.Http) 1975 c.Assert(os.Getenv("HTTP_PROXY"), gc.Equals, expected.Http) 1976 c.Assert(os.Getenv("https_proxy"), gc.Equals, expected.Https) 1977 c.Assert(os.Getenv("HTTPS_PROXY"), gc.Equals, expected.Https) 1978 c.Assert(os.Getenv("ftp_proxy"), gc.Equals, expected.Ftp) 1979 c.Assert(os.Getenv("FTP_PROXY"), gc.Equals, expected.Ftp) 1980 return 1981 } 1982 } 1983 c.Fatal("settings didn't get noticed by the uniter") 1984 } 1985 1986 type runCommands []string 1987 1988 func (cmds runCommands) step(c *gc.C, ctx *context) { 1989 commands := strings.Join(cmds, "\n") 1990 result, err := ctx.uniter.RunCommands(commands) 1991 c.Assert(err, gc.IsNil) 1992 c.Check(result.Code, gc.Equals, 0) 1993 c.Check(string(result.Stdout), gc.Equals, "") 1994 c.Check(string(result.Stderr), gc.Equals, "") 1995 } 1996 1997 type asyncRunCommands []string 1998 1999 func (cmds asyncRunCommands) step(c *gc.C, ctx *context) { 2000 commands := strings.Join(cmds, "\n") 2001 socketPath := filepath.Join(ctx.path, uniter.RunListenerFile) 2002 2003 go func() { 2004 // make sure the socket exists 2005 client, err := rpc.Dial("unix", socketPath) 2006 c.Assert(err, gc.IsNil) 2007 defer client.Close() 2008 2009 var result utilexec.ExecResponse 2010 err = client.Call(uniter.JujuRunEndpoint, commands, &result) 2011 c.Assert(err, gc.IsNil) 2012 c.Check(result.Code, gc.Equals, 0) 2013 c.Check(string(result.Stdout), gc.Equals, "") 2014 c.Check(string(result.Stderr), gc.Equals, "") 2015 }() 2016 } 2017 2018 type verifyFile struct { 2019 filename string 2020 content string 2021 } 2022 2023 func (verify verifyFile) fileExists() bool { 2024 _, err := os.Stat(verify.filename) 2025 return err == nil 2026 } 2027 2028 func (verify verifyFile) checkContent(c *gc.C) { 2029 content, err := ioutil.ReadFile(verify.filename) 2030 c.Assert(err, gc.IsNil) 2031 c.Assert(string(content), gc.Equals, verify.content) 2032 } 2033 2034 func (verify verifyFile) step(c *gc.C, ctx *context) { 2035 if verify.fileExists() { 2036 verify.checkContent(c) 2037 return 2038 } 2039 c.Logf("waiting for file: %s", verify.filename) 2040 timeout := time.After(worstCase) 2041 for { 2042 select { 2043 case <-time.After(coretesting.ShortWait): 2044 if verify.fileExists() { 2045 verify.checkContent(c) 2046 return 2047 } 2048 case <-timeout: 2049 c.Fatalf("file does not exist") 2050 } 2051 } 2052 } 2053 2054 // verify that the file does not exist 2055 type verifyNoFile struct { 2056 filename string 2057 } 2058 2059 func (verify verifyNoFile) step(c *gc.C, ctx *context) { 2060 c.Assert(verify.filename, jc.DoesNotExist) 2061 // Wait a short time and check again. 2062 time.Sleep(coretesting.ShortWait) 2063 c.Assert(verify.filename, jc.DoesNotExist) 2064 }