github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/uniter_test.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "runtime" 14 "strings" 15 "syscall" 16 "time" 17 18 "github.com/juju/errors" 19 jc "github.com/juju/testing/checkers" 20 ft "github.com/juju/testing/filetesting" 21 "github.com/juju/utils/clock" 22 gc "gopkg.in/check.v1" 23 corecharm "gopkg.in/juju/charm.v5" 24 25 "github.com/juju/juju/agent/tools" 26 "github.com/juju/juju/apiserver/params" 27 "github.com/juju/juju/juju/testing" 28 "github.com/juju/juju/state" 29 "github.com/juju/juju/testcharms" 30 coretesting "github.com/juju/juju/testing" 31 "github.com/juju/juju/worker/uniter" 32 "github.com/juju/juju/worker/uniter/operation" 33 ) 34 35 type UniterSuite struct { 36 coretesting.GitSuite 37 testing.JujuConnSuite 38 dataDir string 39 oldLcAll string 40 unitDir string 41 42 collectMetricsTicker *uniter.ManualTicker 43 sendMetricsTicker *uniter.ManualTicker 44 updateStatusHookTicker *uniter.ManualTicker 45 } 46 47 var _ = gc.Suite(&UniterSuite{}) 48 49 var leaseClock *coretesting.Clock 50 51 // This guarantees that we get proper platform 52 // specific error directly from their source 53 // This works on both windows and unix 54 var errNotDir = syscall.ENOTDIR.Error() 55 56 func (s *UniterSuite) SetUpSuite(c *gc.C) { 57 s.GitSuite.SetUpSuite(c) 58 s.JujuConnSuite.SetUpSuite(c) 59 s.dataDir = c.MkDir() 60 toolsDir := tools.ToolsDir(s.dataDir, "unit-u-0") 61 err := os.MkdirAll(toolsDir, 0755) 62 c.Assert(err, jc.ErrorIsNil) 63 // TODO(fwereade) GAAAAAAAAAAAAAAAAAH this is LUDICROUS. 64 cmd := exec.Command("go", "build", "github.com/juju/juju/cmd/jujud") 65 cmd.Dir = toolsDir 66 out, err := cmd.CombinedOutput() 67 c.Logf(string(out)) 68 c.Assert(err, jc.ErrorIsNil) 69 s.oldLcAll = os.Getenv("LC_ALL") 70 os.Setenv("LC_ALL", "en_US") 71 s.unitDir = filepath.Join(s.dataDir, "agents", "unit-u-0") 72 73 zone, err := time.LoadLocation("") 74 c.Assert(err, jc.ErrorIsNil) 75 now := time.Date(2030, 11, 11, 11, 11, 11, 11, zone) 76 leaseClock = coretesting.NewClock(now) 77 oldGetClock := state.GetClock 78 state.GetClock = func() clock.Clock { 79 return leaseClock 80 } 81 s.AddSuiteCleanup(func(*gc.C) { state.GetClock = oldGetClock }) 82 } 83 84 func (s *UniterSuite) TearDownSuite(c *gc.C) { 85 os.Setenv("LC_ALL", s.oldLcAll) 86 s.JujuConnSuite.TearDownSuite(c) 87 s.GitSuite.TearDownSuite(c) 88 } 89 90 func (s *UniterSuite) SetUpTest(c *gc.C) { 91 s.collectMetricsTicker = uniter.NewManualTicker() 92 s.sendMetricsTicker = uniter.NewManualTicker() 93 s.updateStatusHookTicker = uniter.NewManualTicker() 94 s.GitSuite.SetUpTest(c) 95 s.JujuConnSuite.SetUpTest(c) 96 s.PatchValue(uniter.IdleWaitTime, 1*time.Millisecond) 97 } 98 99 func (s *UniterSuite) TearDownTest(c *gc.C) { 100 s.ResetContext(c) 101 s.JujuConnSuite.TearDownTest(c) 102 s.GitSuite.TearDownTest(c) 103 } 104 105 func (s *UniterSuite) Reset(c *gc.C) { 106 s.JujuConnSuite.Reset(c) 107 s.ResetContext(c) 108 } 109 110 func (s *UniterSuite) ResetContext(c *gc.C) { 111 err := os.RemoveAll(s.unitDir) 112 c.Assert(err, jc.ErrorIsNil) 113 } 114 115 func (s *UniterSuite) runUniterTests(c *gc.C, uniterTests []uniterTest) { 116 for i, t := range uniterTests { 117 c.Logf("\ntest %d: %s\n", i, t.summary) 118 func() { 119 defer s.Reset(c) 120 env, err := s.State.Environment() 121 c.Assert(err, jc.ErrorIsNil) 122 ctx := &context{ 123 s: s, 124 st: s.State, 125 uuid: env.UUID(), 126 path: s.unitDir, 127 dataDir: s.dataDir, 128 charms: make(map[string][]byte), 129 collectMetricsTicker: s.collectMetricsTicker, 130 sendMetricsTicker: s.sendMetricsTicker, 131 updateStatusHookTicker: s.updateStatusHookTicker, 132 } 133 ctx.run(c, t.steps) 134 }() 135 } 136 } 137 138 func (s *UniterSuite) TestUniterStartup(c *gc.C) { 139 s.runUniterTests(c, []uniterTest{ 140 // Check conditions that can cause the uniter to fail to start. 141 ut( 142 "unable to create state dir", 143 writeFile{"state", 0644}, 144 createCharm{}, 145 createServiceAndUnit{}, 146 startUniter{}, 147 waitUniterDead{err: `failed to initialize uniter for "unit-u-0": .*` + errNotDir}, 148 ), ut( 149 "unknown unit", 150 // We still need to create a unit, because that's when we also 151 // connect to the API, but here we use a different service 152 // (and hence unit) name. 153 createCharm{}, 154 createServiceAndUnit{serviceName: "w"}, 155 startUniter{unitTag: "unit-u-0"}, 156 waitUniterDead{err: `failed to initialize uniter for "unit-u-0": permission denied`}, 157 ), 158 }) 159 } 160 161 func (s *UniterSuite) TestUniterBootstrap(c *gc.C) { 162 //TODO(bogdanteleaga): Fix this on windows 163 if runtime.GOOS == "windows" { 164 c.Skip("bug 1403084: currently does not work on windows") 165 } 166 s.runUniterTests(c, []uniterTest{ 167 // Check error conditions during unit bootstrap phase. 168 ut( 169 "insane deployment", 170 createCharm{}, 171 serveCharm{}, 172 writeFile{"charm", 0644}, 173 createUniter{}, 174 waitUniterDead{err: `ModeInstalling cs:quantal/wordpress-0: executing operation "install cs:quantal/wordpress-0": open .*` + errNotDir}, 175 ), ut( 176 "charm cannot be downloaded", 177 createCharm{}, 178 // don't serve charm 179 createUniter{}, 180 waitUniterDead{err: `ModeInstalling cs:quantal/wordpress-0: preparing operation "install cs:quantal/wordpress-0": failed to download charm .* 404 Not Found`}, 181 ), 182 }) 183 } 184 185 func (s *UniterSuite) TestUniterInstallHook(c *gc.C) { 186 s.runUniterTests(c, []uniterTest{ 187 ut( 188 "install hook fail and resolve", 189 startupError{"install"}, 190 verifyWaiting{}, 191 192 resolveError{state.ResolvedNoHooks}, 193 waitUnitAgent{ 194 status: params.StatusIdle, 195 }, 196 waitUnitAgent{ 197 statusGetter: unitStatusGetter, 198 status: params.StatusUnknown, 199 }, 200 waitHooks{"leader-elected", "config-changed", "start"}, 201 ), ut( 202 "install hook fail and retry", 203 startupError{"install"}, 204 verifyWaiting{}, 205 206 resolveError{state.ResolvedRetryHooks}, 207 waitUnitAgent{ 208 statusGetter: unitStatusGetter, 209 status: params.StatusError, 210 info: `hook failed: "install"`, 211 data: map[string]interface{}{ 212 "hook": "install", 213 }, 214 }, 215 waitHooks{"fail-install"}, 216 fixHook{"install"}, 217 verifyWaiting{}, 218 219 resolveError{state.ResolvedRetryHooks}, 220 waitUnitAgent{ 221 status: params.StatusIdle, 222 }, 223 waitHooks{"install", "leader-elected", "config-changed", "start"}, 224 ), 225 }) 226 } 227 228 func (s *UniterSuite) TestUniterUpdateStatusHook(c *gc.C) { 229 s.runUniterTests(c, []uniterTest{ 230 ut( 231 "update status hook runs on timer", 232 createCharm{}, 233 serveCharm{}, 234 createUniter{}, 235 waitHooks(startupHooks(false)), 236 waitUnitAgent{status: params.StatusIdle}, 237 updateStatusHookTick{}, 238 waitHooks{"update-status"}, 239 ), 240 }) 241 } 242 243 func (s *UniterSuite) TestUniterStartHook(c *gc.C) { 244 s.runUniterTests(c, []uniterTest{ 245 ut( 246 "start hook fail and resolve", 247 startupError{"start"}, 248 verifyWaiting{}, 249 250 resolveError{state.ResolvedNoHooks}, 251 waitUnitAgent{ 252 status: params.StatusIdle, 253 }, 254 waitUnitAgent{ 255 statusGetter: unitStatusGetter, 256 status: params.StatusMaintenance, 257 info: "installing charm software", 258 }, 259 waitHooks{"config-changed"}, 260 verifyRunning{}, 261 ), ut( 262 "start hook fail and retry", 263 startupError{"start"}, 264 verifyWaiting{}, 265 266 resolveError{state.ResolvedRetryHooks}, 267 waitUnitAgent{ 268 statusGetter: unitStatusGetter, 269 status: params.StatusError, 270 info: `hook failed: "start"`, 271 data: map[string]interface{}{ 272 "hook": "start", 273 }, 274 }, 275 waitHooks{"fail-start"}, 276 verifyWaiting{}, 277 278 fixHook{"start"}, 279 resolveError{state.ResolvedRetryHooks}, 280 waitUnitAgent{ 281 status: params.StatusIdle, 282 }, 283 waitHooks{"start", "config-changed"}, 284 verifyRunning{}, 285 ), 286 }) 287 } 288 289 func (s *UniterSuite) TestUniterMultipleErrors(c *gc.C) { 290 s.runUniterTests(c, []uniterTest{ 291 ut( 292 "resolved is cleared before moving on to next hook", 293 createCharm{badHooks: []string{"install", "leader-elected", "config-changed", "start"}}, 294 serveCharm{}, 295 createUniter{}, 296 waitUnitAgent{ 297 statusGetter: unitStatusGetter, 298 status: params.StatusError, 299 info: `hook failed: "install"`, 300 data: map[string]interface{}{ 301 "hook": "install", 302 }, 303 }, 304 resolveError{state.ResolvedNoHooks}, 305 waitUnitAgent{ 306 statusGetter: unitStatusGetter, 307 status: params.StatusError, 308 info: `hook failed: "leader-elected"`, 309 data: map[string]interface{}{ 310 "hook": "leader-elected", 311 }, 312 }, 313 resolveError{state.ResolvedNoHooks}, 314 waitUnitAgent{ 315 statusGetter: unitStatusGetter, 316 status: params.StatusError, 317 info: `hook failed: "config-changed"`, 318 data: map[string]interface{}{ 319 "hook": "config-changed", 320 }, 321 }, 322 resolveError{state.ResolvedNoHooks}, 323 waitUnitAgent{ 324 statusGetter: unitStatusGetter, 325 status: params.StatusError, 326 info: `hook failed: "start"`, 327 data: map[string]interface{}{ 328 "hook": "start", 329 }, 330 }, 331 ), 332 }) 333 } 334 335 func (s *UniterSuite) TestUniterConfigChangedHook(c *gc.C) { 336 s.runUniterTests(c, []uniterTest{ 337 ut( 338 "config-changed hook fail and resolve", 339 startupError{"config-changed"}, 340 verifyWaiting{}, 341 342 // Note: we'll run another config-changed as soon as we hit the 343 // started state, so the broken hook would actually prevent us 344 // from advancing at all if we didn't fix it. 345 fixHook{"config-changed"}, 346 resolveError{state.ResolvedNoHooks}, 347 waitUnitAgent{ 348 status: params.StatusIdle, 349 }, 350 waitUnitAgent{ 351 statusGetter: unitStatusGetter, 352 status: params.StatusUnknown, 353 }, 354 waitHooks{"start", "config-changed"}, 355 // If we'd accidentally retried that hook, somehow, we would get 356 // an extra config-changed as we entered started; see that we don't. 357 waitHooks{}, 358 verifyRunning{}, 359 ), ut( 360 "config-changed hook fail and retry", 361 startupError{"config-changed"}, 362 verifyWaiting{}, 363 364 resolveError{state.ResolvedRetryHooks}, 365 waitUnitAgent{ 366 statusGetter: unitStatusGetter, 367 status: params.StatusError, 368 info: `hook failed: "config-changed"`, 369 data: map[string]interface{}{ 370 "hook": "config-changed", 371 }, 372 }, 373 waitHooks{"fail-config-changed"}, 374 verifyWaiting{}, 375 376 fixHook{"config-changed"}, 377 resolveError{state.ResolvedRetryHooks}, 378 waitUnitAgent{ 379 status: params.StatusIdle, 380 }, 381 waitHooks{"config-changed", "start"}, 382 verifyRunning{}, 383 ), ut( 384 "steady state config change with config-get verification", 385 createCharm{ 386 customize: func(c *gc.C, ctx *context, path string) { 387 appendHook(c, path, "config-changed", appendConfigChanged) 388 }, 389 }, 390 serveCharm{}, 391 createUniter{}, 392 waitUnitAgent{ 393 status: params.StatusIdle, 394 }, 395 waitHooks{"install", "leader-elected", "config-changed", "start"}, 396 assertYaml{"charm/config.out", map[string]interface{}{ 397 "blog-title": "My Title", 398 }}, 399 changeConfig{"blog-title": "Goodness Gracious Me"}, 400 waitHooks{"config-changed"}, 401 verifyRunning{}, 402 assertYaml{"charm/config.out", map[string]interface{}{ 403 "blog-title": "Goodness Gracious Me", 404 }}, 405 ), 406 }) 407 } 408 409 func (s *UniterSuite) TestUniterHookSynchronisation(c *gc.C) { 410 s.runUniterTests(c, []uniterTest{ 411 ut( 412 "verify config change hook not run while lock held", 413 quickStart{}, 414 acquireHookSyncLock{}, 415 changeConfig{"blog-title": "Goodness Gracious Me"}, 416 waitHooks{}, 417 releaseHookSyncLock, 418 waitHooks{"config-changed"}, 419 ), ut( 420 "verify held lock by this unit is broken", 421 acquireHookSyncLock{"u/0:fake"}, 422 quickStart{}, 423 verifyHookSyncLockUnlocked, 424 ), ut( 425 "verify held lock by another unit is not broken", 426 acquireHookSyncLock{"u/1:fake"}, 427 // Can't use quickstart as it has a built in waitHooks. 428 createCharm{}, 429 serveCharm{}, 430 ensureStateWorker{}, 431 createServiceAndUnit{}, 432 startUniter{}, 433 waitAddresses{}, 434 waitHooks{}, 435 verifyHookSyncLockLocked, 436 releaseHookSyncLock, 437 waitUnitAgent{status: params.StatusIdle}, 438 waitHooks{"install", "leader-elected", "config-changed", "start"}, 439 ), 440 }) 441 } 442 443 func (s *UniterSuite) TestUniterDyingReaction(c *gc.C) { 444 s.runUniterTests(c, []uniterTest{ 445 // Reaction to entity deaths. 446 ut( 447 "steady state service dying", 448 quickStart{}, 449 serviceDying, 450 waitHooks{"stop"}, 451 waitUniterDead{}, 452 ), ut( 453 "steady state unit dying", 454 quickStart{}, 455 unitDying, 456 waitHooks{"stop"}, 457 waitUniterDead{}, 458 ), ut( 459 "steady state unit dead", 460 quickStart{}, 461 unitDead, 462 waitUniterDead{}, 463 waitHooks{}, 464 ), ut( 465 "hook error service dying", 466 startupError{"start"}, 467 serviceDying, 468 verifyWaiting{}, 469 fixHook{"start"}, 470 resolveError{state.ResolvedRetryHooks}, 471 waitHooks{"start", "config-changed", "stop"}, 472 waitUniterDead{}, 473 ), ut( 474 "hook error unit dying", 475 startupError{"start"}, 476 unitDying, 477 verifyWaiting{}, 478 fixHook{"start"}, 479 resolveError{state.ResolvedRetryHooks}, 480 waitHooks{"start", "config-changed", "stop"}, 481 waitUniterDead{}, 482 ), ut( 483 "hook error unit dead", 484 startupError{"start"}, 485 unitDead, 486 waitUniterDead{}, 487 waitHooks{}, 488 ), 489 }) 490 } 491 492 func (s *UniterSuite) TestUniterSteadyStateUpgrade(c *gc.C) { 493 s.runUniterTests(c, []uniterTest{ 494 // Upgrade scenarios from steady state. 495 ut( 496 "steady state upgrade", 497 quickStart{}, 498 createCharm{revision: 1}, 499 upgradeCharm{revision: 1}, 500 waitUnitAgent{ 501 status: params.StatusIdle, 502 charm: 1, 503 }, 504 waitUnitAgent{ 505 statusGetter: unitStatusGetter, 506 status: params.StatusUnknown, 507 charm: 1, 508 }, 509 waitHooks{"upgrade-charm", "config-changed"}, 510 verifyCharm{revision: 1}, 511 verifyRunning{}, 512 ), ut( 513 "steady state forced upgrade (identical behaviour)", 514 quickStart{}, 515 createCharm{revision: 1}, 516 upgradeCharm{revision: 1, forced: true}, 517 waitUnitAgent{ 518 status: params.StatusIdle, 519 charm: 1, 520 }, 521 waitUnitAgent{ 522 statusGetter: unitStatusGetter, 523 status: params.StatusUnknown, 524 charm: 1, 525 }, 526 waitHooks{"upgrade-charm", "config-changed"}, 527 verifyCharm{revision: 1}, 528 verifyRunning{}, 529 ), ut( 530 "steady state upgrade hook fail and resolve", 531 quickStart{}, 532 createCharm{revision: 1, badHooks: []string{"upgrade-charm"}}, 533 upgradeCharm{revision: 1}, 534 waitUnitAgent{ 535 statusGetter: unitStatusGetter, 536 status: params.StatusError, 537 info: `hook failed: "upgrade-charm"`, 538 data: map[string]interface{}{ 539 "hook": "upgrade-charm", 540 }, 541 charm: 1, 542 }, 543 waitHooks{"fail-upgrade-charm"}, 544 verifyCharm{revision: 1}, 545 verifyWaiting{}, 546 547 resolveError{state.ResolvedNoHooks}, 548 waitUnitAgent{ 549 status: params.StatusIdle, 550 charm: 1, 551 }, 552 waitUnitAgent{ 553 statusGetter: unitStatusGetter, 554 status: params.StatusUnknown, 555 charm: 1, 556 }, 557 waitHooks{"config-changed"}, 558 verifyRunning{}, 559 ), ut( 560 "steady state upgrade hook fail and retry", 561 quickStart{}, 562 createCharm{revision: 1, badHooks: []string{"upgrade-charm"}}, 563 upgradeCharm{revision: 1}, 564 waitUnitAgent{ 565 statusGetter: unitStatusGetter, 566 status: params.StatusError, 567 info: `hook failed: "upgrade-charm"`, 568 data: map[string]interface{}{ 569 "hook": "upgrade-charm", 570 }, 571 charm: 1, 572 }, 573 waitHooks{"fail-upgrade-charm"}, 574 verifyCharm{revision: 1}, 575 verifyWaiting{}, 576 577 resolveError{state.ResolvedRetryHooks}, 578 waitUnitAgent{ 579 statusGetter: unitStatusGetter, 580 status: params.StatusError, 581 info: `hook failed: "upgrade-charm"`, 582 data: map[string]interface{}{ 583 "hook": "upgrade-charm", 584 }, 585 charm: 1, 586 }, 587 waitHooks{"fail-upgrade-charm"}, 588 verifyWaiting{}, 589 590 fixHook{"upgrade-charm"}, 591 resolveError{state.ResolvedRetryHooks}, 592 waitUnitAgent{ 593 status: params.StatusIdle, 594 charm: 1, 595 }, 596 waitHooks{"upgrade-charm", "config-changed"}, 597 verifyRunning{}, 598 ), ut( 599 // This test does an add-relation as quickly as possible 600 // after an upgrade-charm, in the hope that the scheduler will 601 // deliver the events in the wrong order. The observed 602 // behaviour should be the same in either case. 603 "ignore unknown relations until upgrade is done", 604 quickStart{}, 605 createCharm{ 606 revision: 2, 607 customize: func(c *gc.C, ctx *context, path string) { 608 renameRelation(c, path, "db", "db2") 609 hpath := filepath.Join(path, "hooks", "db2-relation-joined") 610 ctx.writeHook(c, hpath, true) 611 }, 612 }, 613 serveCharm{}, 614 upgradeCharm{revision: 2}, 615 addRelation{}, 616 addRelationUnit{}, 617 waitHooks{"upgrade-charm", "config-changed", "db2-relation-joined mysql/0 db2:0"}, 618 verifyCharm{revision: 2}, 619 ), 620 }) 621 } 622 623 func (s *UniterSuite) TestUniterUpgradeOverwrite(c *gc.C) { 624 //TODO(bogdanteleaga): Fix this on windows 625 if runtime.GOOS == "windows" { 626 c.Skip("bug 1403084: currently does not work on windows") 627 } 628 makeTest := func(description string, content, extraChecks ft.Entries) uniterTest { 629 return ut(description, 630 createCharm{ 631 // This is the base charm which all upgrade tests start out running. 632 customize: func(c *gc.C, ctx *context, path string) { 633 ft.Entries{ 634 ft.Dir{"dir", 0755}, 635 ft.File{"file", "blah", 0644}, 636 ft.Symlink{"symlink", "file"}, 637 }.Create(c, path) 638 // Note that it creates "dir/user-file" at runtime, which may be 639 // preserved or removed depending on the test. 640 script := "echo content > dir/user-file && chmod 755 dir/user-file" 641 appendHook(c, path, "start", script) 642 }, 643 }, 644 serveCharm{}, 645 createUniter{}, 646 waitUnitAgent{ 647 status: params.StatusIdle, 648 }, 649 waitUnitAgent{ 650 statusGetter: unitStatusGetter, 651 status: params.StatusUnknown, 652 }, 653 waitHooks{"install", "leader-elected", "config-changed", "start"}, 654 655 createCharm{ 656 revision: 1, 657 customize: func(c *gc.C, _ *context, path string) { 658 content.Create(c, path) 659 }, 660 }, 661 serveCharm{}, 662 upgradeCharm{revision: 1}, 663 waitUnitAgent{ 664 status: params.StatusIdle, 665 charm: 1, 666 }, 667 waitUnitAgent{ 668 statusGetter: unitStatusGetter, 669 status: params.StatusUnknown, 670 charm: 1, 671 }, 672 waitHooks{"upgrade-charm", "config-changed"}, 673 verifyCharm{revision: 1}, 674 custom{func(c *gc.C, ctx *context) { 675 path := filepath.Join(ctx.path, "charm") 676 content.Check(c, path) 677 extraChecks.Check(c, path) 678 }}, 679 verifyRunning{}, 680 ) 681 } 682 683 s.runUniterTests(c, []uniterTest{ 684 makeTest( 685 "files overwite files, dirs, symlinks", 686 ft.Entries{ 687 ft.File{"file", "new", 0755}, 688 ft.File{"dir", "new", 0755}, 689 ft.File{"symlink", "new", 0755}, 690 }, 691 ft.Entries{ 692 ft.Removed{"dir/user-file"}, 693 }, 694 ), makeTest( 695 "symlinks overwite files, dirs, symlinks", 696 ft.Entries{ 697 ft.Symlink{"file", "new"}, 698 ft.Symlink{"dir", "new"}, 699 ft.Symlink{"symlink", "new"}, 700 }, 701 ft.Entries{ 702 ft.Removed{"dir/user-file"}, 703 }, 704 ), makeTest( 705 "dirs overwite files, symlinks; merge dirs", 706 ft.Entries{ 707 ft.Dir{"file", 0755}, 708 ft.Dir{"dir", 0755}, 709 ft.File{"dir/charm-file", "charm-content", 0644}, 710 ft.Dir{"symlink", 0755}, 711 }, 712 ft.Entries{ 713 ft.File{"dir/user-file", "content\n", 0755}, 714 }, 715 ), 716 }) 717 } 718 719 func (s *UniterSuite) TestUniterErrorStateUpgrade(c *gc.C) { 720 s.runUniterTests(c, []uniterTest{ 721 // Upgrade scenarios from error state. 722 ut( 723 "error state unforced upgrade (ignored until started state)", 724 startupError{"start"}, 725 createCharm{revision: 1}, 726 upgradeCharm{revision: 1}, 727 waitUnitAgent{ 728 statusGetter: unitStatusGetter, 729 status: params.StatusError, 730 info: `hook failed: "start"`, 731 data: map[string]interface{}{ 732 "hook": "start", 733 }, 734 }, 735 waitHooks{}, 736 verifyCharm{}, 737 verifyWaiting{}, 738 739 resolveError{state.ResolvedNoHooks}, 740 waitUnitAgent{ 741 status: params.StatusIdle, 742 charm: 1, 743 }, 744 waitUnitAgent{ 745 statusGetter: unitStatusGetter, 746 status: params.StatusMaintenance, 747 info: "installing charm software", 748 charm: 1, 749 }, 750 waitHooks{"config-changed", "upgrade-charm", "config-changed"}, 751 verifyCharm{revision: 1}, 752 verifyRunning{}, 753 ), ut( 754 "error state forced upgrade", 755 startupError{"start"}, 756 createCharm{revision: 1}, 757 upgradeCharm{revision: 1, forced: true}, 758 // It's not possible to tell directly from state when the upgrade is 759 // complete, because the new unit charm URL is set at the upgrade 760 // process's point of no return (before actually deploying, but after 761 // the charm has been downloaded and verified). However, it's still 762 // useful to wait until that point... 763 waitUnitAgent{ 764 statusGetter: unitStatusGetter, 765 status: params.StatusError, 766 info: `hook failed: "start"`, 767 data: map[string]interface{}{ 768 "hook": "start", 769 }, 770 charm: 1, 771 }, 772 // ...because the uniter *will* complete a started deployment even if 773 // we stop it from outside. So, by stopping and starting, we can be 774 // sure that the operation has completed and can safely verify that 775 // the charm state on disk is as we expect. 776 verifyWaiting{}, 777 verifyCharm{revision: 1}, 778 779 resolveError{state.ResolvedNoHooks}, 780 waitUnitAgent{ 781 status: params.StatusIdle, 782 charm: 1, 783 }, 784 waitHooks{"config-changed"}, 785 verifyRunning{}, 786 ), 787 }) 788 } 789 790 func (s *UniterSuite) TestUniterDeployerConversion(c *gc.C) { 791 coretesting.SkipIfGitNotAvailable(c) 792 793 deployerConversionTests := []uniterTest{ 794 ut( 795 "install normally, check not using git", 796 quickStart{}, 797 verifyCharm{ 798 checkFiles: ft.Entries{ft.Removed{".git"}}, 799 }, 800 ), ut( 801 "install with git, restart in steady state", 802 prepareGitUniter{[]stepper{ 803 quickStart{}, 804 verifyGitCharm{}, 805 stopUniter{}, 806 }}, 807 startUniter{}, 808 waitHooks{"config-changed"}, 809 810 // At this point, the deployer has been converted, but the 811 // charm directory itself hasn't; the *next* deployment will 812 // actually hit the charm directory and strip out the git 813 // stuff. 814 createCharm{revision: 1}, 815 upgradeCharm{revision: 1}, 816 waitHooks{"upgrade-charm", "config-changed"}, 817 waitUnitAgent{ 818 status: params.StatusIdle, 819 charm: 1, 820 }, 821 waitUnitAgent{ 822 statusGetter: unitStatusGetter, 823 status: params.StatusUnknown, 824 charm: 1, 825 }, 826 verifyCharm{ 827 revision: 1, 828 checkFiles: ft.Entries{ft.Removed{".git"}}, 829 }, 830 verifyRunning{}, 831 ), ut( 832 "install with git, get conflicted, mark resolved", 833 prepareGitUniter{[]stepper{ 834 startGitUpgradeError{}, 835 stopUniter{}, 836 }}, 837 startUniter{}, 838 839 resolveError{state.ResolvedNoHooks}, 840 waitHooks{"upgrade-charm", "config-changed"}, 841 waitUnitAgent{ 842 status: params.StatusIdle, 843 charm: 1, 844 }, 845 waitUnitAgent{ 846 statusGetter: unitStatusGetter, 847 status: params.StatusUnknown, 848 charm: 1, 849 }, 850 verifyCharm{revision: 1}, 851 verifyRunning{}, 852 853 // Due to the uncertainties around marking upgrade conflicts resolved, 854 // the charm directory again remains unconverted, although the deployer 855 // should have been fixed. Again, we check this by running another 856 // upgrade and verifying the .git dir is then removed. 857 createCharm{revision: 2}, 858 upgradeCharm{revision: 2}, 859 waitHooks{"upgrade-charm", "config-changed"}, 860 waitUnitAgent{ 861 status: params.StatusIdle, 862 charm: 2, 863 }, 864 waitUnitAgent{ 865 statusGetter: unitStatusGetter, 866 status: params.StatusUnknown, 867 charm: 2, 868 }, 869 verifyCharm{ 870 revision: 2, 871 checkFiles: ft.Entries{ft.Removed{".git"}}, 872 }, 873 verifyRunning{}, 874 ), ut( 875 "install with git, get conflicted, force an upgrade", 876 prepareGitUniter{[]stepper{ 877 startGitUpgradeError{}, 878 stopUniter{}, 879 }}, 880 startUniter{}, 881 882 createCharm{ 883 revision: 2, 884 customize: func(c *gc.C, ctx *context, path string) { 885 ft.File{"data", "OVERWRITE!", 0644}.Create(c, path) 886 }, 887 }, 888 serveCharm{}, 889 upgradeCharm{revision: 2, forced: true}, 890 waitHooks{"upgrade-charm", "config-changed"}, 891 waitUnitAgent{ 892 status: params.StatusIdle, 893 charm: 2, 894 }, 895 896 // A forced upgrade allows us to swap out the git deployer *and* 897 // the .git dir inside the charm immediately; check we did so. 898 verifyCharm{ 899 revision: 2, 900 checkFiles: ft.Entries{ 901 ft.Removed{".git"}, 902 ft.File{"data", "OVERWRITE!", 0644}, 903 }, 904 }, 905 verifyRunning{}, 906 ), 907 } 908 s.runUniterTests(c, deployerConversionTests) 909 } 910 911 func (s *UniterSuite) TestUniterUpgradeConflicts(c *gc.C) { 912 coretesting.SkipIfPPC64EL(c, "lp:1448308") 913 //TODO(bogdanteleaga): Fix this on windows 914 if runtime.GOOS == "windows" { 915 c.Skip("bug 1403084: currently does not work on windows") 916 } 917 s.runUniterTests(c, []uniterTest{ 918 // Upgrade scenarios - handling conflicts. 919 ut( 920 "upgrade: resolving doesn't help until underlying problem is fixed", 921 startUpgradeError{}, 922 resolveError{state.ResolvedNoHooks}, 923 verifyWaitingUpgradeError{revision: 1}, 924 fixUpgradeError{}, 925 resolveError{state.ResolvedNoHooks}, 926 waitHooks{"upgrade-charm", "config-changed"}, 927 waitUnitAgent{ 928 status: params.StatusIdle, 929 charm: 1, 930 }, 931 waitUnitAgent{ 932 statusGetter: unitStatusGetter, 933 status: params.StatusUnknown, 934 charm: 1, 935 }, 936 verifyCharm{revision: 1}, 937 ), ut( 938 `upgrade: forced upgrade does work without explicit resolution if underlying problem was fixed`, 939 startUpgradeError{}, 940 resolveError{state.ResolvedNoHooks}, 941 verifyWaitingUpgradeError{revision: 1}, 942 fixUpgradeError{}, 943 createCharm{revision: 2}, 944 serveCharm{}, 945 upgradeCharm{revision: 2, forced: true}, 946 waitHooks{"upgrade-charm", "config-changed"}, 947 waitUnitAgent{ 948 status: params.StatusIdle, 949 charm: 2, 950 }, 951 waitUnitAgent{ 952 statusGetter: unitStatusGetter, 953 status: params.StatusUnknown, 954 charm: 2, 955 }, 956 verifyCharm{revision: 2}, 957 ), ut( 958 "upgrade conflict service dying", 959 startUpgradeError{}, 960 serviceDying, 961 verifyWaitingUpgradeError{revision: 1}, 962 fixUpgradeError{}, 963 resolveError{state.ResolvedNoHooks}, 964 waitHooks{"upgrade-charm", "config-changed", "stop"}, 965 waitUniterDead{}, 966 ), ut( 967 "upgrade conflict unit dying", 968 startUpgradeError{}, 969 unitDying, 970 verifyWaitingUpgradeError{revision: 1}, 971 fixUpgradeError{}, 972 resolveError{state.ResolvedNoHooks}, 973 waitHooks{"upgrade-charm", "config-changed", "stop"}, 974 waitUniterDead{}, 975 ), ut( 976 "upgrade conflict unit dead", 977 startUpgradeError{}, 978 unitDead, 979 waitUniterDead{}, 980 waitHooks{}, 981 fixUpgradeError{}, 982 ), 983 }) 984 } 985 986 func (s *UniterSuite) TestUniterUpgradeGitConflicts(c *gc.C) { 987 coretesting.SkipIfGitNotAvailable(c) 988 989 // These tests are copies of the old git-deployer-related tests, to test that 990 // the uniter with the manifest-deployer work patched out still works how it 991 // used to; thus demonstrating that the *other* tests that verify manifest 992 // deployer behaviour in the presence of an old git deployer are working against 993 // an accurate representation of the base state. 994 // The only actual behaviour change is that we no longer commit changes after 995 // each hook execution; this is reflected by checking that it's dirty in a couple 996 // of places where we once checked it was not. 997 998 s.runUniterTests(c, []uniterTest{ 999 // Upgrade scenarios - handling conflicts. 1000 ugt( 1001 "upgrade: conflicting files", 1002 startGitUpgradeError{}, 1003 1004 // NOTE: this is just dumbly committing the conflicts, but AFAICT this 1005 // is the only reasonable solution; if the user tells us it's resolved 1006 // we have to take their word for it. 1007 resolveError{state.ResolvedNoHooks}, 1008 waitHooks{"upgrade-charm", "config-changed"}, 1009 waitUnitAgent{ 1010 status: params.StatusIdle, 1011 charm: 1, 1012 }, 1013 waitUnitAgent{ 1014 statusGetter: unitStatusGetter, 1015 status: params.StatusUnknown, 1016 charm: 1, 1017 }, 1018 verifyGitCharm{revision: 1}, 1019 ), ugt( 1020 `upgrade: conflicting directories`, 1021 createCharm{ 1022 customize: func(c *gc.C, ctx *context, path string) { 1023 err := os.Mkdir(filepath.Join(path, "data"), 0755) 1024 c.Assert(err, jc.ErrorIsNil) 1025 appendHook(c, path, "start", "echo DATA > data/newfile") 1026 }, 1027 }, 1028 serveCharm{}, 1029 createUniter{}, 1030 waitUnitAgent{ 1031 status: params.StatusIdle, 1032 }, 1033 waitUnitAgent{ 1034 statusGetter: unitStatusGetter, 1035 status: params.StatusUnknown, 1036 }, 1037 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1038 verifyGitCharm{dirty: true}, 1039 1040 createCharm{ 1041 revision: 1, 1042 customize: func(c *gc.C, ctx *context, path string) { 1043 data := filepath.Join(path, "data") 1044 err := ioutil.WriteFile(data, []byte("<nelson>ha ha</nelson>"), 0644) 1045 c.Assert(err, jc.ErrorIsNil) 1046 }, 1047 }, 1048 serveCharm{}, 1049 upgradeCharm{revision: 1}, 1050 waitUnitAgent{ 1051 statusGetter: unitStatusGetter, 1052 status: params.StatusError, 1053 info: "upgrade failed", 1054 charm: 1, 1055 }, 1056 verifyWaiting{}, 1057 verifyGitCharm{dirty: true}, 1058 1059 resolveError{state.ResolvedNoHooks}, 1060 waitHooks{"upgrade-charm", "config-changed"}, 1061 waitUnitAgent{ 1062 status: params.StatusIdle, 1063 charm: 1, 1064 }, 1065 verifyGitCharm{revision: 1}, 1066 ), ugt( 1067 "upgrade conflict resolved with forced upgrade", 1068 startGitUpgradeError{}, 1069 createCharm{ 1070 revision: 2, 1071 customize: func(c *gc.C, ctx *context, path string) { 1072 otherdata := filepath.Join(path, "otherdata") 1073 err := ioutil.WriteFile(otherdata, []byte("blah"), 0644) 1074 c.Assert(err, jc.ErrorIsNil) 1075 }, 1076 }, 1077 serveCharm{}, 1078 upgradeCharm{revision: 2, forced: true}, 1079 waitUnitAgent{ 1080 status: params.StatusIdle, 1081 charm: 2, 1082 }, waitUnitAgent{ 1083 statusGetter: unitStatusGetter, 1084 status: params.StatusUnknown, 1085 charm: 2, 1086 }, 1087 waitHooks{"upgrade-charm", "config-changed"}, 1088 verifyGitCharm{revision: 2}, 1089 custom{func(c *gc.C, ctx *context) { 1090 // otherdata should exist (in v2) 1091 otherdata, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "otherdata")) 1092 c.Assert(err, jc.ErrorIsNil) 1093 c.Assert(string(otherdata), gc.Equals, "blah") 1094 1095 // ignore should not (only in v1) 1096 _, err = os.Stat(filepath.Join(ctx.path, "charm", "ignore")) 1097 c.Assert(err, jc.Satisfies, os.IsNotExist) 1098 1099 // data should contain what was written in the start hook 1100 data, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "data")) 1101 c.Assert(err, jc.ErrorIsNil) 1102 c.Assert(string(data), gc.Equals, "STARTDATA\n") 1103 }}, 1104 ), ugt( 1105 "upgrade conflict service dying", 1106 startGitUpgradeError{}, 1107 serviceDying, 1108 verifyWaiting{}, 1109 resolveError{state.ResolvedNoHooks}, 1110 waitHooks{"upgrade-charm", "config-changed", "stop"}, 1111 waitUniterDead{}, 1112 ), ugt( 1113 "upgrade conflict unit dying", 1114 startGitUpgradeError{}, 1115 unitDying, 1116 verifyWaiting{}, 1117 resolveError{state.ResolvedNoHooks}, 1118 waitHooks{"upgrade-charm", "config-changed", "stop"}, 1119 waitUniterDead{}, 1120 ), ugt( 1121 "upgrade conflict unit dead", 1122 startGitUpgradeError{}, 1123 unitDead, 1124 waitUniterDead{}, 1125 waitHooks{}, 1126 ), 1127 }) 1128 } 1129 1130 func (s *UniterSuite) TestUniterRelations(c *gc.C) { 1131 waitDyingHooks := custom{func(c *gc.C, ctx *context) { 1132 // There is no ordering relationship between relation hooks and 1133 // leader-settings-changed hooks; and while we're dying we may 1134 // never get to leader-settings-changed before it's time to run 1135 // the stop (as we might not react to a config change in time). 1136 // It's actually clearer to just list the possible orders: 1137 possibles := [][]string{{ 1138 "leader-settings-changed", 1139 "db-relation-departed mysql/0 db:0", 1140 "db-relation-broken db:0", 1141 "stop", 1142 }, { 1143 "db-relation-departed mysql/0 db:0", 1144 "leader-settings-changed", 1145 "db-relation-broken db:0", 1146 "stop", 1147 }, { 1148 "db-relation-departed mysql/0 db:0", 1149 "db-relation-broken db:0", 1150 "leader-settings-changed", 1151 "stop", 1152 }, { 1153 "db-relation-departed mysql/0 db:0", 1154 "db-relation-broken db:0", 1155 "stop", 1156 }} 1157 unchecked := ctx.hooksCompleted[len(ctx.hooks):] 1158 for _, possible := range possibles { 1159 if ok, _ := jc.DeepEqual(unchecked, possible); ok { 1160 return 1161 } 1162 } 1163 c.Fatalf("unexpected hooks: %v", unchecked) 1164 }} 1165 s.runUniterTests(c, []uniterTest{ 1166 // Relations. 1167 ut( 1168 "simple joined/changed/departed", 1169 quickStartRelation{}, 1170 addRelationUnit{}, 1171 waitHooks{ 1172 "db-relation-joined mysql/1 db:0", 1173 "db-relation-changed mysql/1 db:0", 1174 }, 1175 changeRelationUnit{"mysql/0"}, 1176 waitHooks{"db-relation-changed mysql/0 db:0"}, 1177 removeRelationUnit{"mysql/1"}, 1178 waitHooks{"db-relation-departed mysql/1 db:0"}, 1179 verifyRunning{}, 1180 ), ut( 1181 "relation becomes dying; unit is not last remaining member", 1182 quickStartRelation{}, 1183 relationDying, 1184 waitHooks{ 1185 "db-relation-departed mysql/0 db:0", 1186 "db-relation-broken db:0", 1187 }, 1188 verifyRunning{}, 1189 relationState{life: state.Dying}, 1190 removeRelationUnit{"mysql/0"}, 1191 verifyRunning{}, 1192 relationState{removed: true}, 1193 verifyRunning{}, 1194 ), ut( 1195 "relation becomes dying; unit is last remaining member", 1196 quickStartRelation{}, 1197 removeRelationUnit{"mysql/0"}, 1198 waitHooks{"db-relation-departed mysql/0 db:0"}, 1199 relationDying, 1200 waitHooks{"db-relation-broken db:0"}, 1201 verifyRunning{}, 1202 relationState{removed: true}, 1203 verifyRunning{}, 1204 ), ut( 1205 "service becomes dying while in a relation", 1206 quickStartRelation{}, 1207 serviceDying, 1208 waitUniterDead{}, 1209 waitDyingHooks, 1210 relationState{life: state.Dying}, 1211 removeRelationUnit{"mysql/0"}, 1212 relationState{removed: true}, 1213 ), ut( 1214 "unit becomes dying while in a relation", 1215 quickStartRelation{}, 1216 unitDying, 1217 waitUniterDead{}, 1218 waitDyingHooks, 1219 relationState{life: state.Alive}, 1220 removeRelationUnit{"mysql/0"}, 1221 relationState{life: state.Alive}, 1222 ), ut( 1223 "unit becomes dead while in a relation", 1224 quickStartRelation{}, 1225 unitDead, 1226 waitUniterDead{}, 1227 waitHooks{}, 1228 // TODO BUG(?): the unit doesn't leave the scope, leaving the relation 1229 // unkillable without direct intervention. I'm pretty sure it's not a 1230 // uniter bug -- it should be the responsibility of `juju remove-unit 1231 // --force` to cause the unit to leave any relation scopes it may be 1232 // in -- but it's worth noting here all the same. 1233 ), ut( 1234 "unknown local relation dir is removed", 1235 quickStartRelation{}, 1236 stopUniter{}, 1237 custom{func(c *gc.C, ctx *context) { 1238 ft.Dir{"state/relations/90210", 0755}.Create(c, ctx.path) 1239 }}, 1240 startUniter{}, 1241 waitHooks{"config-changed"}, 1242 custom{func(c *gc.C, ctx *context) { 1243 ft.Removed{"state/relations/90210"}.Check(c, ctx.path) 1244 }}, 1245 ), ut( 1246 "all relations are available to config-changed on bounce, even if state dir is missing", 1247 createCharm{ 1248 customize: func(c *gc.C, ctx *context, path string) { 1249 script := uniterRelationsCustomizeScript 1250 appendHook(c, path, "config-changed", script) 1251 }, 1252 }, 1253 serveCharm{}, 1254 createUniter{}, 1255 waitUnitAgent{ 1256 status: params.StatusIdle, 1257 }, 1258 waitUnitAgent{ 1259 statusGetter: unitStatusGetter, 1260 status: params.StatusUnknown, 1261 }, 1262 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1263 addRelation{waitJoin: true}, 1264 stopUniter{}, 1265 custom{func(c *gc.C, ctx *context) { 1266 // Check the state dir was created, and remove it. 1267 path := fmt.Sprintf("state/relations/%d", ctx.relation.Id()) 1268 ft.Dir{path, 0755}.Check(c, ctx.path) 1269 ft.Removed{path}.Create(c, ctx.path) 1270 1271 // Check that config-changed didn't record any relations, because 1272 // they shouldn't been available until after the start hook. 1273 ft.File{"charm/relations.out", "", 0644}.Check(c, ctx.path) 1274 }}, 1275 startUniter{}, 1276 waitHooks{"config-changed"}, 1277 custom{func(c *gc.C, ctx *context) { 1278 // Check the state dir was recreated. 1279 path := fmt.Sprintf("state/relations/%d", ctx.relation.Id()) 1280 ft.Dir{path, 0755}.Check(c, ctx.path) 1281 1282 // Check that config-changed did record the joined relations. 1283 data := fmt.Sprintf("db:%d\n", ctx.relation.Id()) 1284 ft.File{"charm/relations.out", data, 0644}.Check(c, ctx.path) 1285 }}, 1286 ), 1287 }) 1288 } 1289 1290 func (s *UniterSuite) TestUniterRelationErrors(c *gc.C) { 1291 s.runUniterTests(c, []uniterTest{ 1292 ut( 1293 "hook error during join of a relation", 1294 startupRelationError{"db-relation-joined"}, 1295 waitUnitAgent{ 1296 statusGetter: unitStatusGetter, 1297 status: params.StatusError, 1298 info: `hook failed: "db-relation-joined"`, 1299 data: map[string]interface{}{ 1300 "hook": "db-relation-joined", 1301 "relation-id": 0, 1302 "remote-unit": "mysql/0", 1303 }, 1304 }, 1305 ), ut( 1306 "hook error during change of a relation", 1307 startupRelationError{"db-relation-changed"}, 1308 waitUnitAgent{ 1309 statusGetter: unitStatusGetter, 1310 status: params.StatusError, 1311 info: `hook failed: "db-relation-changed"`, 1312 data: map[string]interface{}{ 1313 "hook": "db-relation-changed", 1314 "relation-id": 0, 1315 "remote-unit": "mysql/0", 1316 }, 1317 }, 1318 ), ut( 1319 "hook error after a unit departed", 1320 startupRelationError{"db-relation-departed"}, 1321 waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"}, 1322 removeRelationUnit{"mysql/0"}, 1323 waitUnitAgent{ 1324 statusGetter: unitStatusGetter, 1325 status: params.StatusError, 1326 info: `hook failed: "db-relation-departed"`, 1327 data: map[string]interface{}{ 1328 "hook": "db-relation-departed", 1329 "relation-id": 0, 1330 "remote-unit": "mysql/0", 1331 }, 1332 }, 1333 ), 1334 ut( 1335 "hook error after a relation died", 1336 startupRelationError{"db-relation-broken"}, 1337 waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"}, 1338 relationDying, 1339 waitUnitAgent{ 1340 statusGetter: unitStatusGetter, 1341 status: params.StatusError, 1342 info: `hook failed: "db-relation-broken"`, 1343 data: map[string]interface{}{ 1344 "hook": "db-relation-broken", 1345 "relation-id": 0, 1346 }, 1347 }, 1348 ), 1349 }) 1350 } 1351 1352 func (s *UniterSuite) TestUniterMeterStatusChanged(c *gc.C) { 1353 s.runUniterTests(c, []uniterTest{ 1354 ut( 1355 "meter status event triggered by unit meter status change", 1356 quickStart{}, 1357 changeMeterStatus{"AMBER", "Investigate charm."}, 1358 waitHooks{"meter-status-changed"}, 1359 ), 1360 }) 1361 } 1362 1363 func (s *UniterSuite) TestUniterSendMetricsBeforeDying(c *gc.C) { 1364 s.runUniterTests(c, []uniterTest{ 1365 ut( 1366 "metrics must be sent before the unit is destroyed", 1367 createCharm{ 1368 customize: func(c *gc.C, ctx *context, path string) { 1369 ctx.writeMetricsYaml(c, path) 1370 }, 1371 }, 1372 serveCharm{}, 1373 createUniter{}, 1374 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1375 addMetrics{[]string{"5", "42"}}, 1376 unitDying, 1377 waitUniterDead{}, 1378 checkStateMetrics{number: 1, values: []string{"5", "42"}}, 1379 ), 1380 }) 1381 } 1382 1383 func (s *UniterSuite) TestUniterCollectMetrics(c *gc.C) { 1384 s.runUniterTests(c, []uniterTest{ 1385 ut( 1386 "collect-metrics event triggered by manual timer", 1387 createCharm{ 1388 customize: func(c *gc.C, ctx *context, path string) { 1389 ctx.writeMetricsYaml(c, path) 1390 }, 1391 }, 1392 serveCharm{}, 1393 createUniter{}, 1394 waitUnitAgent{status: params.StatusIdle}, 1395 waitUnitAgent{ 1396 statusGetter: unitStatusGetter, 1397 status: params.StatusUnknown, 1398 }, 1399 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1400 verifyCharm{}, 1401 collectMetricsTick{}, 1402 waitHooks{"collect-metrics"}, 1403 ), ut( 1404 "collect-metrics resumed after hook error", 1405 startupErrorWithCustomCharm{ 1406 badHook: "config-changed", 1407 customize: func(c *gc.C, ctx *context, path string) { 1408 ctx.writeMetricsYaml(c, path) 1409 }, 1410 }, 1411 collectMetricsTick{expectFail: true}, 1412 fixHook{"config-changed"}, 1413 resolveError{state.ResolvedRetryHooks}, 1414 waitUnitAgent{ 1415 status: params.StatusIdle, 1416 }, 1417 waitUnitAgent{ 1418 statusGetter: unitStatusGetter, 1419 status: params.StatusUnknown, 1420 }, 1421 waitHooks{"config-changed", "start"}, 1422 collectMetricsTick{}, 1423 waitHooks{"collect-metrics"}, 1424 verifyRunning{}, 1425 ), 1426 ut( 1427 "collect-metrics state maintained during uniter restart", 1428 startupErrorWithCustomCharm{ 1429 badHook: "config-changed", 1430 customize: func(c *gc.C, ctx *context, path string) { 1431 ctx.writeMetricsYaml(c, path) 1432 }, 1433 }, 1434 collectMetricsTick{expectFail: true}, 1435 fixHook{"config-changed"}, 1436 stopUniter{}, 1437 startUniter{}, 1438 resolveError{state.ResolvedRetryHooks}, 1439 waitUnitAgent{ 1440 status: params.StatusIdle, 1441 }, 1442 waitUnitAgent{ 1443 statusGetter: unitStatusGetter, 1444 status: params.StatusUnknown, 1445 }, 1446 waitHooks{"config-changed", "start"}, 1447 collectMetricsTick{}, 1448 waitHooks{"collect-metrics"}, 1449 verifyRunning{}, 1450 ), ut( 1451 "collect-metrics event not triggered for non-metered charm", 1452 quickStart{}, 1453 collectMetricsTick{expectFail: true}, 1454 waitHooks{}, 1455 ), 1456 }) 1457 } 1458 1459 func (s *UniterSuite) TestUniterSendMetrics(c *gc.C) { 1460 s.runUniterTests(c, []uniterTest{ 1461 ut( 1462 "send metrics event triggered by manual timer", 1463 createCharm{ 1464 customize: func(c *gc.C, ctx *context, path string) { 1465 ctx.writeMetricsYaml(c, path) 1466 }, 1467 }, 1468 serveCharm{}, 1469 createUniter{}, 1470 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1471 addMetrics{[]string{"15", "17"}}, 1472 sendMetricsTick{}, 1473 checkStateMetrics{number: 1, values: []string{"17", "15"}}, 1474 ), ut( 1475 "send-metrics resumed after hook error", 1476 startupErrorWithCustomCharm{ 1477 badHook: "config-changed", 1478 customize: func(c *gc.C, ctx *context, path string) { 1479 ctx.writeMetricsYaml(c, path) 1480 }, 1481 }, 1482 addMetrics{[]string{"15"}}, 1483 sendMetricsTick{expectFail: true}, 1484 fixHook{"config-changed"}, 1485 resolveError{state.ResolvedRetryHooks}, 1486 waitHooks{"config-changed", "start"}, 1487 addMetrics{[]string{"17"}}, 1488 sendMetricsTick{}, 1489 checkStateMetrics{number: 2, values: []string{"15", "17"}}, 1490 verifyRunning{}, 1491 ), ut( 1492 "send-metrics state maintained during uniter restart", 1493 startupErrorWithCustomCharm{ 1494 badHook: "config-changed", 1495 customize: func(c *gc.C, ctx *context, path string) { 1496 ctx.writeMetricsYaml(c, path) 1497 }, 1498 }, 1499 collectMetricsTick{expectFail: true}, 1500 addMetrics{[]string{"13"}}, 1501 sendMetricsTick{expectFail: true}, 1502 fixHook{"config-changed"}, 1503 stopUniter{}, 1504 startUniter{}, 1505 resolveError{state.ResolvedRetryHooks}, 1506 waitHooks{"config-changed", "start"}, 1507 collectMetricsTick{}, 1508 waitHooks{"collect-metrics"}, 1509 addMetrics{[]string{"21"}}, 1510 sendMetricsTick{}, 1511 checkStateMetrics{number: 2, values: []string{"13", "21"}}, 1512 verifyRunning{}, 1513 ), ut( 1514 "collect-metrics event not triggered for non-metered charm", 1515 quickStart{}, 1516 collectMetricsTick{expectFail: true}, 1517 addMetrics{[]string{"21"}}, 1518 sendMetricsTick{expectFail: true}, 1519 waitHooks{}, 1520 checkStateMetrics{number: 0}, 1521 ), 1522 }) 1523 } 1524 1525 func (s *UniterSuite) TestActionEvents(c *gc.C) { 1526 s.runUniterTests(c, []uniterTest{ 1527 ut( 1528 "simple action event: defined in actions.yaml, no args", 1529 createCharm{ 1530 customize: func(c *gc.C, ctx *context, path string) { 1531 ctx.writeAction(c, path, "action-log") 1532 ctx.writeActionsYaml(c, path, "action-log") 1533 }, 1534 }, 1535 serveCharm{}, 1536 ensureStateWorker{}, 1537 createServiceAndUnit{}, 1538 startUniter{}, 1539 waitAddresses{}, 1540 waitUnitAgent{status: params.StatusIdle}, 1541 waitUnitAgent{ 1542 statusGetter: unitStatusGetter, 1543 status: params.StatusUnknown, 1544 }, 1545 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1546 verifyCharm{}, 1547 addAction{"action-log", nil}, 1548 waitActionResults{[]actionResult{{ 1549 name: "action-log", 1550 results: map[string]interface{}{}, 1551 status: params.ActionCompleted, 1552 }}}, 1553 waitUnitAgent{status: params.StatusIdle}, 1554 waitUnitAgent{ 1555 statusGetter: unitStatusGetter, 1556 status: params.StatusUnknown, 1557 }, 1558 ), ut( 1559 "action-fail causes the action to fail with a message", 1560 createCharm{ 1561 customize: func(c *gc.C, ctx *context, path string) { 1562 ctx.writeAction(c, path, "action-log-fail") 1563 ctx.writeActionsYaml(c, path, "action-log-fail") 1564 }, 1565 }, 1566 serveCharm{}, 1567 ensureStateWorker{}, 1568 createServiceAndUnit{}, 1569 startUniter{}, 1570 waitAddresses{}, 1571 waitUnitAgent{status: params.StatusIdle}, 1572 waitUnitAgent{ 1573 statusGetter: unitStatusGetter, 1574 status: params.StatusUnknown, 1575 }, 1576 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1577 verifyCharm{}, 1578 addAction{"action-log-fail", nil}, 1579 waitActionResults{[]actionResult{{ 1580 name: "action-log-fail", 1581 results: map[string]interface{}{ 1582 "foo": "still works", 1583 }, 1584 message: "I'm afraid I can't let you do that, Dave.", 1585 status: params.ActionFailed, 1586 }}}, 1587 waitUnitAgent{status: params.StatusIdle}, waitUnitAgent{ 1588 statusGetter: unitStatusGetter, 1589 status: params.StatusUnknown, 1590 }, 1591 ), ut( 1592 "action-fail with the wrong arguments fails but is not an error", 1593 createCharm{ 1594 customize: func(c *gc.C, ctx *context, path string) { 1595 ctx.writeAction(c, path, "action-log-fail-error") 1596 ctx.writeActionsYaml(c, path, "action-log-fail-error") 1597 }, 1598 }, 1599 serveCharm{}, 1600 ensureStateWorker{}, 1601 createServiceAndUnit{}, 1602 startUniter{}, 1603 waitAddresses{}, 1604 waitUnitAgent{status: params.StatusIdle}, 1605 waitUnitAgent{ 1606 statusGetter: unitStatusGetter, 1607 status: params.StatusUnknown, 1608 }, 1609 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1610 verifyCharm{}, 1611 addAction{"action-log-fail-error", nil}, 1612 waitActionResults{[]actionResult{{ 1613 name: "action-log-fail-error", 1614 results: map[string]interface{}{ 1615 "foo": "still works", 1616 }, 1617 message: "A real message", 1618 status: params.ActionFailed, 1619 }}}, 1620 waitUnitAgent{status: params.StatusIdle}, 1621 waitUnitAgent{ 1622 statusGetter: unitStatusGetter, 1623 status: params.StatusUnknown, 1624 }, 1625 ), ut( 1626 "actions with correct params passed are not an error", 1627 createCharm{ 1628 customize: func(c *gc.C, ctx *context, path string) { 1629 ctx.writeAction(c, path, "snapshot") 1630 ctx.writeActionsYaml(c, path, "snapshot") 1631 }, 1632 }, 1633 serveCharm{}, 1634 ensureStateWorker{}, 1635 createServiceAndUnit{}, 1636 startUniter{}, 1637 waitAddresses{}, 1638 waitUnitAgent{status: params.StatusIdle}, 1639 waitUnitAgent{ 1640 statusGetter: unitStatusGetter, 1641 status: params.StatusUnknown, 1642 }, 1643 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1644 verifyCharm{}, 1645 addAction{ 1646 name: "snapshot", 1647 params: map[string]interface{}{"outfile": "foo.bar"}, 1648 }, 1649 waitActionResults{[]actionResult{{ 1650 name: "snapshot", 1651 results: map[string]interface{}{ 1652 "outfile": map[string]interface{}{ 1653 "name": "snapshot-01.tar", 1654 "size": map[string]interface{}{ 1655 "magnitude": "10.3", 1656 "units": "GB", 1657 }, 1658 }, 1659 "completion": "yes", 1660 }, 1661 status: params.ActionCompleted, 1662 }}}, 1663 waitUnitAgent{status: params.StatusIdle}, 1664 waitUnitAgent{ 1665 statusGetter: unitStatusGetter, 1666 status: params.StatusUnknown, 1667 }, 1668 ), ut( 1669 "actions with incorrect params passed are not an error but fail", 1670 createCharm{ 1671 customize: func(c *gc.C, ctx *context, path string) { 1672 ctx.writeAction(c, path, "snapshot") 1673 ctx.writeActionsYaml(c, path, "snapshot") 1674 }, 1675 }, 1676 serveCharm{}, 1677 ensureStateWorker{}, 1678 createServiceAndUnit{}, 1679 startUniter{}, 1680 waitAddresses{}, 1681 waitUnitAgent{status: params.StatusIdle}, 1682 waitUnitAgent{ 1683 statusGetter: unitStatusGetter, 1684 status: params.StatusUnknown, 1685 }, 1686 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1687 verifyCharm{}, 1688 addAction{ 1689 name: "snapshot", 1690 params: map[string]interface{}{"outfile": 2}, 1691 }, 1692 waitActionResults{[]actionResult{{ 1693 name: "snapshot", 1694 results: map[string]interface{}{}, 1695 status: params.ActionFailed, 1696 message: `cannot run "snapshot" action: validation failed: (root).outfile : must be of type string, given 2`, 1697 }}}, 1698 waitUnitAgent{status: params.StatusIdle}, 1699 waitUnitAgent{ 1700 statusGetter: unitStatusGetter, 1701 status: params.StatusUnknown, 1702 }, 1703 ), ut( 1704 "actions not defined in actions.yaml fail without causing a uniter error", 1705 createCharm{ 1706 customize: func(c *gc.C, ctx *context, path string) { 1707 ctx.writeAction(c, path, "snapshot") 1708 }, 1709 }, 1710 serveCharm{}, 1711 ensureStateWorker{}, 1712 createServiceAndUnit{}, 1713 startUniter{}, 1714 waitAddresses{}, 1715 waitUnitAgent{status: params.StatusIdle}, 1716 waitUnitAgent{ 1717 statusGetter: unitStatusGetter, 1718 status: params.StatusUnknown, 1719 }, 1720 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1721 verifyCharm{}, 1722 addAction{"snapshot", map[string]interface{}{"outfile": "foo.bar"}}, 1723 waitActionResults{[]actionResult{{ 1724 name: "snapshot", 1725 results: map[string]interface{}{}, 1726 status: params.ActionFailed, 1727 message: `cannot run "snapshot" action: not defined`, 1728 }}}, 1729 waitUnitAgent{status: params.StatusIdle}, 1730 waitUnitAgent{ 1731 statusGetter: unitStatusGetter, 1732 status: params.StatusUnknown, 1733 }, 1734 ), ut( 1735 "pending actions get consumed", 1736 createCharm{ 1737 customize: func(c *gc.C, ctx *context, path string) { 1738 ctx.writeAction(c, path, "action-log") 1739 ctx.writeActionsYaml(c, path, "action-log") 1740 }, 1741 }, 1742 serveCharm{}, 1743 ensureStateWorker{}, 1744 createServiceAndUnit{}, 1745 addAction{"action-log", nil}, 1746 addAction{"action-log", nil}, 1747 addAction{"action-log", nil}, 1748 startUniter{}, 1749 waitAddresses{}, 1750 waitUnitAgent{status: params.StatusIdle}, 1751 waitUnitAgent{ 1752 statusGetter: unitStatusGetter, 1753 status: params.StatusUnknown, 1754 }, 1755 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1756 verifyCharm{}, 1757 waitActionResults{[]actionResult{{ 1758 name: "action-log", 1759 results: map[string]interface{}{}, 1760 status: params.ActionCompleted, 1761 }, { 1762 name: "action-log", 1763 results: map[string]interface{}{}, 1764 status: params.ActionCompleted, 1765 }, { 1766 name: "action-log", 1767 results: map[string]interface{}{}, 1768 status: params.ActionCompleted, 1769 }}}, 1770 waitUnitAgent{status: params.StatusIdle}, 1771 waitUnitAgent{ 1772 statusGetter: unitStatusGetter, 1773 status: params.StatusUnknown, 1774 }, 1775 ), ut( 1776 "actions not implemented fail but are not errors", 1777 createCharm{ 1778 customize: func(c *gc.C, ctx *context, path string) { 1779 ctx.writeActionsYaml(c, path, "action-log") 1780 }, 1781 }, 1782 serveCharm{}, 1783 ensureStateWorker{}, 1784 createServiceAndUnit{}, 1785 startUniter{}, 1786 waitAddresses{}, 1787 waitUnitAgent{status: params.StatusIdle}, 1788 waitUnitAgent{ 1789 statusGetter: unitStatusGetter, 1790 status: params.StatusUnknown, 1791 }, 1792 waitHooks{"install", "leader-elected", "config-changed", "start"}, 1793 verifyCharm{}, 1794 addAction{"action-log", nil}, 1795 waitActionResults{[]actionResult{{ 1796 name: "action-log", 1797 results: map[string]interface{}{}, 1798 status: params.ActionFailed, 1799 message: `action not implemented on unit "u/0"`, 1800 }}}, 1801 waitUnitAgent{status: params.StatusIdle}, 1802 waitUnitAgent{ 1803 statusGetter: unitStatusGetter, 1804 status: params.StatusUnknown, 1805 }, 1806 ), ut( 1807 "actions may run from ModeHookError, but do not clear the error", 1808 startupErrorWithCustomCharm{ 1809 badHook: "start", 1810 customize: func(c *gc.C, ctx *context, path string) { 1811 ctx.writeAction(c, path, "action-log") 1812 ctx.writeActionsYaml(c, path, "action-log") 1813 }, 1814 }, 1815 addAction{"action-log", nil}, 1816 waitUnitAgent{ 1817 statusGetter: unitStatusGetter, 1818 status: params.StatusError, 1819 info: `hook failed: "start"`, 1820 data: map[string]interface{}{ 1821 "hook": "start", 1822 }, 1823 }, 1824 waitActionResults{[]actionResult{{ 1825 name: "action-log", 1826 results: map[string]interface{}{}, 1827 status: params.ActionCompleted, 1828 }}}, 1829 waitUnitAgent{ 1830 statusGetter: unitStatusGetter, 1831 status: params.StatusError, 1832 info: `hook failed: "start"`, 1833 data: map[string]interface{}{"hook": "start"}, 1834 }, 1835 verifyWaiting{}, 1836 resolveError{state.ResolvedNoHooks}, 1837 waitUnitAgent{status: params.StatusIdle}, 1838 waitUnitAgent{ 1839 statusGetter: unitStatusGetter, 1840 status: params.StatusMaintenance, 1841 info: "installing charm software", 1842 }, 1843 ), 1844 }) 1845 } 1846 1847 func (s *UniterSuite) TestUniterSubordinates(c *gc.C) { 1848 s.runUniterTests(c, []uniterTest{ 1849 // Subordinates. 1850 ut( 1851 "unit becomes dying while subordinates exist", 1852 quickStart{}, 1853 addSubordinateRelation{"juju-info"}, 1854 waitSubordinateExists{"logging/0"}, 1855 unitDying, 1856 waitSubordinateDying{}, 1857 waitHooks{"stop"}, 1858 verifyWaiting{}, 1859 removeSubordinate{}, 1860 waitUniterDead{}, 1861 ), ut( 1862 "new subordinate becomes necessary while old one is dying", 1863 quickStart{}, 1864 addSubordinateRelation{"juju-info"}, 1865 waitSubordinateExists{"logging/0"}, 1866 removeSubordinateRelation{"juju-info"}, 1867 // The subordinate Uniter would usually set Dying in this situation. 1868 subordinateDying, 1869 addSubordinateRelation{"logging-dir"}, 1870 verifyRunning{}, 1871 removeSubordinate{}, 1872 waitSubordinateExists{"logging/1"}, 1873 ), 1874 }) 1875 } 1876 1877 func (s *UniterSuite) TestSubordinateDying(c *gc.C) { 1878 // Create a test context for later use. 1879 ctx := &context{ 1880 s: s, 1881 st: s.State, 1882 path: filepath.Join(s.dataDir, "agents", "unit-u-0"), 1883 dataDir: s.dataDir, 1884 charms: make(map[string][]byte), 1885 collectMetricsTicker: s.collectMetricsTicker, 1886 sendMetricsTicker: s.sendMetricsTicker, 1887 updateStatusHookTicker: s.updateStatusHookTicker, 1888 } 1889 1890 addStateServerMachine(c, ctx.st) 1891 1892 // Create the subordinate service. 1893 dir := testcharms.Repo.ClonedDir(c.MkDir(), "logging") 1894 curl, err := corecharm.ParseURL("cs:quantal/logging") 1895 c.Assert(err, jc.ErrorIsNil) 1896 curl = curl.WithRevision(dir.Revision()) 1897 step(c, ctx, addCharm{dir, curl}) 1898 ctx.svc = s.AddTestingService(c, "u", ctx.sch) 1899 1900 // Create the principal service and add a relation. 1901 wps := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 1902 wpu, err := wps.AddUnit() 1903 c.Assert(err, jc.ErrorIsNil) 1904 eps, err := s.State.InferEndpoints("wordpress", "u") 1905 c.Assert(err, jc.ErrorIsNil) 1906 rel, err := s.State.AddRelation(eps...) 1907 c.Assert(err, jc.ErrorIsNil) 1908 assertAssignUnit(c, s.State, wpu) 1909 1910 // Create the subordinate unit by entering scope as the principal. 1911 wpru, err := rel.Unit(wpu) 1912 c.Assert(err, jc.ErrorIsNil) 1913 err = wpru.EnterScope(nil) 1914 c.Assert(err, jc.ErrorIsNil) 1915 ctx.unit, err = s.State.Unit("u/0") 1916 c.Assert(err, jc.ErrorIsNil) 1917 ctx.apiLogin(c) 1918 1919 // Run the actual test. 1920 ctx.run(c, []stepper{ 1921 serveCharm{}, 1922 startUniter{}, 1923 waitAddresses{}, 1924 custom{func(c *gc.C, ctx *context) { 1925 c.Assert(rel.Destroy(), gc.IsNil) 1926 }}, 1927 waitUniterDead{}, 1928 }) 1929 } 1930 1931 func (s *UniterSuite) TestReboot(c *gc.C) { 1932 s.runUniterTests(c, []uniterTest{ 1933 ut( 1934 "test that juju-reboot disabled in actions", 1935 createCharm{ 1936 customize: func(c *gc.C, ctx *context, path string) { 1937 ctx.writeAction(c, path, "action-reboot") 1938 ctx.writeActionsYaml(c, path, "action-reboot") 1939 }, 1940 }, 1941 serveCharm{}, 1942 ensureStateWorker{}, 1943 createServiceAndUnit{}, 1944 addAction{"action-reboot", nil}, 1945 startUniter{}, 1946 waitAddresses{}, 1947 waitUnitAgent{ 1948 status: params.StatusIdle, 1949 }, 1950 waitUnitAgent{ 1951 statusGetter: unitStatusGetter, 1952 status: params.StatusUnknown, 1953 }, 1954 waitActionResults{[]actionResult{{ 1955 name: "action-reboot", 1956 results: map[string]interface{}{ 1957 "reboot-delayed": "good", 1958 "reboot-now": "good", 1959 }, 1960 status: params.ActionCompleted, 1961 }}}, 1962 ), ut( 1963 "test that juju-reboot finishes hook, and reboots", 1964 createCharm{ 1965 customize: func(c *gc.C, ctx *context, path string) { 1966 hpath := filepath.Join(path, "hooks", "install") 1967 ctx.writeExplicitHook(c, hpath, rebootHook) 1968 }, 1969 }, 1970 serveCharm{}, 1971 ensureStateWorker{}, 1972 createServiceAndUnit{}, 1973 startUniter{}, 1974 waitAddresses{}, 1975 waitUniterDead{err: "machine needs to reboot"}, 1976 waitHooks{"install"}, 1977 startUniter{}, 1978 waitUnitAgent{ 1979 status: params.StatusIdle, 1980 }, 1981 waitUnitAgent{ 1982 statusGetter: unitStatusGetter, 1983 status: params.StatusUnknown, 1984 }, 1985 waitHooks{"leader-elected", "config-changed", "start"}, 1986 ), ut( 1987 "test that juju-reboot --now kills hook and exits", 1988 createCharm{ 1989 customize: func(c *gc.C, ctx *context, path string) { 1990 hpath := filepath.Join(path, "hooks", "install") 1991 ctx.writeExplicitHook(c, hpath, rebootNowHook) 1992 }, 1993 }, 1994 serveCharm{}, 1995 ensureStateWorker{}, 1996 createServiceAndUnit{}, 1997 startUniter{}, 1998 waitAddresses{}, 1999 waitUniterDead{err: "machine needs to reboot"}, 2000 waitHooks{"install"}, 2001 startUniter{}, 2002 waitUnitAgent{ 2003 status: params.StatusIdle, 2004 }, 2005 waitUnitAgent{ 2006 statusGetter: unitStatusGetter, 2007 status: params.StatusUnknown, 2008 }, 2009 waitHooks{"install", "leader-elected", "config-changed", "start"}, 2010 ), ut( 2011 "test juju-reboot will not happen if hook errors out", 2012 createCharm{ 2013 customize: func(c *gc.C, ctx *context, path string) { 2014 hpath := filepath.Join(path, "hooks", "install") 2015 ctx.writeExplicitHook(c, hpath, badRebootHook) 2016 }, 2017 }, 2018 serveCharm{}, 2019 ensureStateWorker{}, 2020 createServiceAndUnit{}, 2021 startUniter{}, 2022 waitAddresses{}, 2023 waitUnitAgent{ 2024 statusGetter: unitStatusGetter, 2025 status: params.StatusError, 2026 info: fmt.Sprintf(`hook failed: "install"`), 2027 }, 2028 ), 2029 }) 2030 } 2031 2032 func (s *UniterSuite) TestRebootFromJujuRun(c *gc.C) { 2033 //TODO(bogdanteleaga): Fix this on windows 2034 if runtime.GOOS == "windows" { 2035 c.Skip("bug 1403084: currently does not work on windows") 2036 } 2037 s.runUniterTests(c, []uniterTest{ 2038 ut( 2039 "test juju-reboot", 2040 quickStart{}, 2041 runCommands{"juju-reboot"}, 2042 waitUniterDead{err: "machine needs to reboot"}, 2043 startUniter{}, 2044 waitHooks{"config-changed"}, 2045 ), ut( 2046 "test juju-reboot with bad hook", 2047 startupError{"install"}, 2048 runCommands{"juju-reboot"}, 2049 waitUniterDead{err: "machine needs to reboot"}, 2050 startUniter{}, 2051 waitHooks{}, 2052 ), ut( 2053 "test juju-reboot --now", 2054 quickStart{}, 2055 runCommands{"juju-reboot --now"}, 2056 waitUniterDead{err: "machine needs to reboot"}, 2057 startUniter{}, 2058 waitHooks{"config-changed"}, 2059 ), ut( 2060 "test juju-reboot --now with bad hook", 2061 startupError{"install"}, 2062 runCommands{"juju-reboot --now"}, 2063 waitUniterDead{err: "machine needs to reboot"}, 2064 startUniter{}, 2065 waitHooks{}, 2066 ), 2067 }) 2068 } 2069 2070 func (s *UniterSuite) TestLeadership(c *gc.C) { 2071 s.runUniterTests(c, []uniterTest{ 2072 ut( 2073 "hook tools when leader", 2074 quickStart{}, 2075 runCommands{"leader-set foo=bar baz=qux"}, 2076 verifyLeaderSettings{"foo": "bar", "baz": "qux"}, 2077 ), ut( 2078 "hook tools when not leader", 2079 quickStart{minion: true}, 2080 runCommands{leadershipScript}, 2081 ), ut( 2082 "leader-elected triggers when elected", 2083 quickStart{minion: true}, 2084 forceLeader{}, 2085 waitHooks{"leader-elected"}, 2086 ), ut( 2087 "leader-settings-changed triggers when leader settings change", 2088 quickStart{minion: true}, 2089 setLeaderSettings{"ping": "pong"}, 2090 waitHooks{"leader-settings-changed"}, 2091 ), ut( 2092 "leader-settings-changed triggers when bounced", 2093 quickStart{minion: true}, 2094 verifyRunning{minion: true}, 2095 ), ut( 2096 "leader-settings-changed triggers when deposed (while stopped)", 2097 quickStart{}, 2098 stopUniter{}, 2099 forceMinion{}, 2100 verifyRunning{minion: true}, 2101 ), 2102 }) 2103 } 2104 2105 func (s *UniterSuite) TestLeadershipUnexpectedDepose(c *gc.C) { 2106 s.PatchValue(uniter.LeadershipGuarantee, 2*coretesting.ShortWait) 2107 s.runUniterTests(c, []uniterTest{ 2108 ut( 2109 // NOTE: this is a strange and ugly test, intended to detect what 2110 // *would* happen if the uniter suddenly failed to renew its lease; 2111 // it depends on an artificially shortened tracker refresh time to 2112 // run in a reasonable amount of time. 2113 "leader-settings-changed triggers when deposed (while running)", 2114 quickStart{}, 2115 forceMinion{}, 2116 waitHooks{"leader-settings-changed"}, 2117 ), 2118 }) 2119 } 2120 2121 func (s *UniterSuite) TestStorage(c *gc.C) { 2122 // appendStorageMetadata customises the wordpress charm's metadata, 2123 // adding a "wp-content" filesystem store. We do it here rather 2124 // than in the charm itself to avoid modifying all of the other 2125 // scenarios. 2126 appendStorageMetadata := func(c *gc.C, ctx *context, path string) { 2127 f, err := os.OpenFile(filepath.Join(path, "metadata.yaml"), os.O_RDWR|os.O_APPEND, 0644) 2128 c.Assert(err, jc.ErrorIsNil) 2129 defer func() { 2130 err := f.Close() 2131 c.Assert(err, jc.ErrorIsNil) 2132 }() 2133 _, err = io.WriteString(f, "storage:\n wp-content:\n type: filesystem\n") 2134 c.Assert(err, jc.ErrorIsNil) 2135 } 2136 s.runUniterTests(c, []uniterTest{ 2137 ut( 2138 "test that storage-attached is called", 2139 createCharm{customize: appendStorageMetadata}, 2140 serveCharm{}, 2141 ensureStateWorker{}, 2142 createServiceAndUnit{}, 2143 provisionStorage{}, 2144 startUniter{}, 2145 waitAddresses{}, 2146 waitHooks{"wp-content-storage-attached"}, 2147 waitHooks(startupHooks(false)), 2148 ), ut( 2149 "test that storage-detaching is called before stop", 2150 createCharm{customize: appendStorageMetadata}, 2151 serveCharm{}, 2152 ensureStateWorker{}, 2153 createServiceAndUnit{}, 2154 provisionStorage{}, 2155 startUniter{}, 2156 waitAddresses{}, 2157 waitHooks{"wp-content-storage-attached"}, 2158 waitHooks(startupHooks(false)), 2159 unitDying, 2160 waitHooks{"leader-settings-changed"}, 2161 // "stop" hook is not called until storage is detached 2162 waitHooks{"wp-content-storage-detaching", "stop"}, 2163 verifyStorageDetached{}, 2164 waitUniterDead{}, 2165 ), ut( 2166 "test that storage-detaching is called only if previously attached", 2167 createCharm{customize: appendStorageMetadata}, 2168 serveCharm{}, 2169 ensureStateWorker{}, 2170 createServiceAndUnit{}, 2171 // provision and destroy the storage before the uniter starts, 2172 // to ensure it never sees the storage as attached 2173 provisionStorage{}, 2174 destroyStorageAttachment{}, 2175 startUniter{}, 2176 waitHooks(startupHooks(false)), 2177 unitDying, 2178 // storage-detaching is not called because it was never attached 2179 waitHooks{"stop"}, 2180 verifyStorageDetached{}, 2181 waitUniterDead{}, 2182 ), ut( 2183 "test that delay-provisioned storage does not block forever", 2184 createCharm{customize: appendStorageMetadata}, 2185 serveCharm{}, 2186 ensureStateWorker{}, 2187 createServiceAndUnit{}, 2188 startUniter{}, 2189 // no hooks should be run, as storage isn't provisioned 2190 waitHooks{}, 2191 provisionStorage{}, 2192 waitHooks{"wp-content-storage-attached"}, 2193 waitHooks(startupHooks(false)), 2194 ), ut( 2195 "test that unprovisioned storage does not block unit termination", 2196 createCharm{customize: appendStorageMetadata}, 2197 serveCharm{}, 2198 ensureStateWorker{}, 2199 createServiceAndUnit{}, 2200 startUniter{}, 2201 // no hooks should be run, as storage isn't provisioned 2202 waitHooks{}, 2203 unitDying, 2204 // TODO(axw) should we really be running startup hooks 2205 // when the unit is dying? 2206 waitHooks(startupHooks(true)), 2207 waitHooks{"stop"}, 2208 waitUniterDead{}, 2209 ), 2210 // TODO(axw) test that storage-attached is run for new 2211 // storage attachments before upgrade-charm is run. This 2212 // requires additions to state to add storage when a charm 2213 // is upgraded. 2214 }) 2215 } 2216 2217 type mockExecutor struct { 2218 operation.Executor 2219 } 2220 2221 func (m *mockExecutor) Run(op operation.Operation) error { 2222 // want to allow charm unpacking to occur 2223 if strings.HasPrefix(op.String(), "install") { 2224 return m.Executor.Run(op) 2225 } 2226 // but hooks should error 2227 return errors.New("some error occurred") 2228 } 2229 2230 func (s *UniterSuite) TestOperationErrorReported(c *gc.C) { 2231 executorFunc := func(stateFilePath string, getInstallCharm func() (*corecharm.URL, error), acquireLock func(string) (func() error, error)) (operation.Executor, error) { 2232 e, err := operation.NewExecutor(stateFilePath, getInstallCharm, acquireLock) 2233 c.Assert(err, jc.ErrorIsNil) 2234 return &mockExecutor{e}, nil 2235 } 2236 s.runUniterTests(c, []uniterTest{ 2237 ut( 2238 "error running operations are reported", 2239 createCharm{}, 2240 serveCharm{}, 2241 createUniter{executorFunc: executorFunc}, 2242 waitUnitAgent{ 2243 status: params.StatusFailed, 2244 info: "run install hook", 2245 }, 2246 expectError{".*some error occurred.*"}, 2247 ), 2248 }) 2249 }