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