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