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