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