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