1
// Copyright 2012-2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
18
"github.com/juju/errors"
19
"github.com/juju/mutex"
20
jc "github.com/juju/testing/checkers"
21
ft "github.com/juju/testing/filetesting"
22
"github.com/juju/utils/clock"
23
gc "gopkg.in/check.v1"
24
corecharm "gopkg.in/juju/charm.v6-unstable"
26
"github.com/juju/juju/agent/tools"
27
"github.com/juju/juju/apiserver/params"
28
"github.com/juju/juju/component/all"
29
"github.com/juju/juju/juju/testing"
30
"github.com/juju/juju/state"
31
"github.com/juju/juju/status"
32
"github.com/juju/juju/testcharms"
33
coretesting "github.com/juju/juju/testing"
34
"github.com/juju/juju/worker/uniter/operation"
37
type UniterSuite struct {
44
updateStatusHookTicker *manualTicker
47
var _ = gc.Suite(&UniterSuite{})
49
var leaseClock *coretesting.Clock
51
// This guarantees that we get proper platform
52
// specific error directly from their source
53
// This works on both windows and unix
54
var errNotDir = syscall.ENOTDIR.Error()
56
func (s *UniterSuite) SetUpSuite(c *gc.C) {
57
s.GitSuite.SetUpSuite(c)
58
s.JujuConnSuite.SetUpSuite(c)
60
toolsDir := tools.ToolsDir(s.dataDir, "unit-u-0")
61
err := os.MkdirAll(toolsDir, 0755)
62
c.Assert(err, jc.ErrorIsNil)
63
// TODO(fwereade) GAAAAAAAAAAAAAAAAAH this is LUDICROUS.
64
cmd := exec.Command(jujudBuildArgs[0], jujudBuildArgs[1:]...)
66
out, err := cmd.CombinedOutput()
68
c.Assert(err, jc.ErrorIsNil)
69
s.oldLcAll = os.Getenv("LC_ALL")
70
os.Setenv("LC_ALL", "en_US")
71
s.unitDir = filepath.Join(s.dataDir, "agents", "unit-u-0")
73
zone, err := time.LoadLocation("")
74
c.Assert(err, jc.ErrorIsNil)
75
now := time.Date(2030, 11, 11, 11, 11, 11, 11, zone)
76
leaseClock = coretesting.NewClock(now)
77
oldGetClock := state.GetClock
78
state.GetClock = func() clock.Clock {
81
s.AddCleanup(func(*gc.C) { state.GetClock = oldGetClock })
82
all.RegisterForServer()
85
func (s *UniterSuite) TearDownSuite(c *gc.C) {
86
os.Setenv("LC_ALL", s.oldLcAll)
87
s.JujuConnSuite.TearDownSuite(c)
88
s.GitSuite.TearDownSuite(c)
91
func (s *UniterSuite) SetUpTest(c *gc.C) {
92
s.updateStatusHookTicker = newManualTicker()
93
s.GitSuite.SetUpTest(c)
94
s.JujuConnSuite.SetUpTest(c)
97
func (s *UniterSuite) TearDownTest(c *gc.C) {
99
s.JujuConnSuite.TearDownTest(c)
100
s.GitSuite.TearDownTest(c)
103
func (s *UniterSuite) Reset(c *gc.C) {
104
s.JujuConnSuite.Reset(c)
108
func (s *UniterSuite) ResetContext(c *gc.C) {
109
err := os.RemoveAll(s.unitDir)
110
c.Assert(err, jc.ErrorIsNil)
113
func (s *UniterSuite) runUniterTests(c *gc.C, uniterTests []uniterTest) {
114
for i, t := range uniterTests {
115
c.Logf("\ntest %d: %s\n", i, t.summary)
118
env, err := s.State.Model()
119
c.Assert(err, jc.ErrorIsNil)
126
charms: make(map[string][]byte),
127
updateStatusHookTicker: s.updateStatusHookTicker,
128
charmDirGuard: &mockCharmDirGuard{},
135
func (s *UniterSuite) TestUniterStartup(c *gc.C) {
136
s.runUniterTests(c, []uniterTest{
137
// Check conditions that can cause the uniter to fail to start.
139
"unable to create state dir",
140
writeFile{"state", 0644},
142
createServiceAndUnit{},
144
waitUniterDead{err: `failed to initialize uniter for "unit-u-0": .*` + errNotDir},
147
// We still need to create a unit, because that's when we also
148
// connect to the API, but here we use a different service
149
// (and hence unit) name.
151
createServiceAndUnit{serviceName: "w"},
152
startUniter{unitTag: "unit-u-0"},
153
waitUniterDead{err: `failed to initialize uniter for "unit-u-0": permission denied`},
158
func (s *UniterSuite) TestUniterBootstrap(c *gc.C) {
159
//TODO(bogdanteleaga): Fix this on windows
160
if runtime.GOOS == "windows" {
161
c.Skip("bug 1403084: currently does not work on windows")
163
s.runUniterTests(c, []uniterTest{
164
// Check error conditions during unit bootstrap phase.
169
writeFile{"charm", 0644},
171
waitUniterDead{err: `executing operation "install cs:quantal/wordpress-0": open .*` + errNotDir},
173
"charm cannot be downloaded",
177
waitUniterDead{err: `preparing operation "install cs:quantal/wordpress-0": failed to download charm .* not found`},
182
func (s *UniterSuite) TestUniterInstallHook(c *gc.C) {
183
s.runUniterTests(c, []uniterTest{
185
"install hook fail and resolve",
186
startupError{"install"},
189
resolveError{state.ResolvedNoHooks},
191
status: status.StatusIdle,
194
statusGetter: unitStatusGetter,
195
status: status.StatusUnknown,
197
waitHooks{"leader-elected", "config-changed", "start"},
199
"install hook fail and retry",
200
startupError{"install"},
203
resolveError{state.ResolvedRetryHooks},
205
statusGetter: unitStatusGetter,
206
status: status.StatusError,
207
info: `hook failed: "install"`,
208
data: map[string]interface{}{
212
waitHooks{"fail-install"},
216
resolveError{state.ResolvedRetryHooks},
218
status: status.StatusIdle,
220
waitHooks{"install", "leader-elected", "config-changed", "start"},
225
func (s *UniterSuite) TestUniterUpdateStatusHook(c *gc.C) {
226
s.runUniterTests(c, []uniterTest{
228
"update status hook runs on timer",
232
waitHooks(startupHooks(false)),
233
waitUnitAgent{status: status.StatusIdle},
234
updateStatusHookTick{},
235
waitHooks{"update-status"},
240
func (s *UniterSuite) TestNoUniterUpdateStatusHookInError(c *gc.C) {
241
s.runUniterTests(c, []uniterTest{
243
"update status hook doesn't run if in error",
244
startupError{"start"},
246
updateStatusHookTick{},
249
// Resolve and hook should run.
250
resolveError{state.ResolvedNoHooks},
252
status: status.StatusIdle,
255
updateStatusHookTick{},
256
waitHooks{"update-status"},
261
func (s *UniterSuite) TestUniterStartHook(c *gc.C) {
262
s.runUniterTests(c, []uniterTest{
264
"start hook fail and resolve",
265
startupError{"start"},
268
resolveError{state.ResolvedNoHooks},
270
status: status.StatusIdle,
273
statusGetter: unitStatusGetter,
274
status: status.StatusMaintenance,
275
info: "installing charm software",
277
waitHooks{"config-changed"},
280
"start hook fail and retry",
281
startupError{"start"},
284
resolveError{state.ResolvedRetryHooks},
286
statusGetter: unitStatusGetter,
287
status: status.StatusError,
288
info: `hook failed: "start"`,
289
data: map[string]interface{}{
293
waitHooks{"fail-start"},
297
resolveError{state.ResolvedRetryHooks},
299
status: status.StatusIdle,
301
waitHooks{"start", "config-changed"},
307
func (s *UniterSuite) TestUniterMultipleErrors(c *gc.C) {
308
s.runUniterTests(c, []uniterTest{
310
"resolved is cleared before moving on to next hook",
311
createCharm{badHooks: []string{"install", "leader-elected", "config-changed", "start"}},
315
statusGetter: unitStatusGetter,
316
status: status.StatusError,
317
info: `hook failed: "install"`,
318
data: map[string]interface{}{
322
resolveError{state.ResolvedNoHooks},
324
statusGetter: unitStatusGetter,
325
status: status.StatusError,
326
info: `hook failed: "leader-elected"`,
327
data: map[string]interface{}{
328
"hook": "leader-elected",
331
resolveError{state.ResolvedNoHooks},
333
statusGetter: unitStatusGetter,
334
status: status.StatusError,
335
info: `hook failed: "config-changed"`,
336
data: map[string]interface{}{
337
"hook": "config-changed",
340
resolveError{state.ResolvedNoHooks},
342
statusGetter: unitStatusGetter,
343
status: status.StatusError,
344
info: `hook failed: "start"`,
345
data: map[string]interface{}{
353
func (s *UniterSuite) TestUniterConfigChangedHook(c *gc.C) {
354
s.runUniterTests(c, []uniterTest{
356
"config-changed hook fail and resolve",
357
startupError{"config-changed"},
360
// Note: we'll run another config-changed as soon as we hit the
361
// started state, so the broken hook would actually prevent us
362
// from advancing at all if we didn't fix it.
363
fixHook{"config-changed"},
364
resolveError{state.ResolvedNoHooks},
366
status: status.StatusIdle,
369
statusGetter: unitStatusGetter,
370
status: status.StatusUnknown,
372
// TODO(axw) confirm with fwereade that this is correct.
373
// Previously we would see "start", "config-changed".
374
// I don't think we should see another config-changed,
375
// since config did not change since we resolved the
378
// If we'd accidentally retried that hook, somehow, we would get
379
// an extra config-changed as we entered started; see that we don't.
383
"config-changed hook fail and retry",
384
startupError{"config-changed"},
387
resolveError{state.ResolvedRetryHooks},
389
statusGetter: unitStatusGetter,
390
status: status.StatusError,
391
info: `hook failed: "config-changed"`,
392
data: map[string]interface{}{
393
"hook": "config-changed",
396
waitHooks{"fail-config-changed"},
399
fixHook{"config-changed"},
400
resolveError{state.ResolvedRetryHooks},
402
status: status.StatusIdle,
404
waitHooks{"config-changed", "start"},
407
"steady state config change with config-get verification",
409
customize: func(c *gc.C, ctx *context, path string) {
410
appendHook(c, path, "config-changed", appendConfigChanged)
416
status: status.StatusIdle,
418
waitHooks{"install", "leader-elected", "config-changed", "start"},
419
assertYaml{"charm/config.out", map[string]interface{}{
420
"blog-title": "My Title",
422
changeConfig{"blog-title": "Goodness Gracious Me"},
423
waitHooks{"config-changed"},
425
assertYaml{"charm/config.out", map[string]interface{}{
426
"blog-title": "Goodness Gracious Me",
432
func (s *UniterSuite) TestUniterHookSynchronisation(c *gc.C) {
434
s.runUniterTests(c, []uniterTest{
436
"verify config change hook not run while lock held",
439
changeConfig{"blog-title": "Goodness Gracious Me"},
442
waitHooks{"config-changed"},
444
"verify held lock by another unit is not broken",
446
// Can't use quickstart as it has a built in waitHooks.
450
createServiceAndUnit{},
455
waitUnitAgent{status: status.StatusIdle},
456
waitHooks{"install", "leader-elected", "config-changed", "start"},
461
func (s *UniterSuite) TestUniterDyingReaction(c *gc.C) {
462
s.runUniterTests(c, []uniterTest{
463
// Reaction to entity deaths.
465
"steady state unit dying",
468
waitHooks{"leader-settings-changed", "stop"},
471
"steady state unit dead",
477
"hook error unit dying",
478
startupError{"start"},
482
resolveError{state.ResolvedRetryHooks},
483
waitHooks{"start", "leader-settings-changed", "stop"},
486
"hook error unit dead",
487
startupError{"start"},
495
func (s *UniterSuite) TestUniterSteadyStateUpgrade(c *gc.C) {
496
s.runUniterTests(c, []uniterTest{
497
// Upgrade scenarios from steady state.
499
"steady state upgrade",
501
createCharm{revision: 1},
502
upgradeCharm{revision: 1},
504
status: status.StatusIdle,
508
statusGetter: unitStatusGetter,
509
status: status.StatusUnknown,
512
waitHooks{"upgrade-charm", "config-changed"},
513
verifyCharm{revision: 1},
519
func (s *UniterSuite) TestUniterSteadyStateUpgradeForce(c *gc.C) {
520
s.runUniterTests(c, []uniterTest{
522
"steady state forced upgrade (identical behaviour)",
524
createCharm{revision: 1},
525
upgradeCharm{revision: 1, forced: true},
527
status: status.StatusIdle,
531
statusGetter: unitStatusGetter,
532
status: status.StatusUnknown,
535
waitHooks{"upgrade-charm", "config-changed"},
536
verifyCharm{revision: 1},
542
func (s *UniterSuite) TestUniterSteadyStateUpgradeResolve(c *gc.C) {
543
s.runUniterTests(c, []uniterTest{
545
"steady state upgrade hook fail and resolve",
547
createCharm{revision: 1, badHooks: []string{"upgrade-charm"}},
548
upgradeCharm{revision: 1},
550
statusGetter: unitStatusGetter,
551
status: status.StatusError,
552
info: `hook failed: "upgrade-charm"`,
553
data: map[string]interface{}{
554
"hook": "upgrade-charm",
558
waitHooks{"fail-upgrade-charm"},
559
verifyCharm{revision: 1},
562
resolveError{state.ResolvedNoHooks},
564
status: status.StatusIdle,
568
statusGetter: unitStatusGetter,
569
status: status.StatusUnknown,
572
waitHooks{"config-changed"},
578
func (s *UniterSuite) TestUniterSteadyStateUpgradeRetry(c *gc.C) {
579
s.runUniterTests(c, []uniterTest{
581
"steady state upgrade hook fail and retry",
583
createCharm{revision: 1, badHooks: []string{"upgrade-charm"}},
584
upgradeCharm{revision: 1},
586
statusGetter: unitStatusGetter,
587
status: status.StatusError,
588
info: `hook failed: "upgrade-charm"`,
589
data: map[string]interface{}{
590
"hook": "upgrade-charm",
594
waitHooks{"fail-upgrade-charm"},
595
verifyCharm{revision: 1},
598
resolveError{state.ResolvedRetryHooks},
600
statusGetter: unitStatusGetter,
601
status: status.StatusError,
602
info: `hook failed: "upgrade-charm"`,
603
data: map[string]interface{}{
604
"hook": "upgrade-charm",
608
waitHooks{"fail-upgrade-charm"},
611
fixHook{"upgrade-charm"},
612
resolveError{state.ResolvedRetryHooks},
614
status: status.StatusIdle,
617
waitHooks{"upgrade-charm", "config-changed"},
623
func (s *UniterSuite) TestUniterSteadyStateUpgradeRelations(c *gc.C) {
624
s.runUniterTests(c, []uniterTest{
626
// This test does an add-relation as quickly as possible
627
// after an upgrade-charm, in the hope that the scheduler will
628
// deliver the events in the wrong order. The observed
629
// behaviour should be the same in either case.
630
"ignore unknown relations until upgrade is done",
634
customize: func(c *gc.C, ctx *context, path string) {
635
renameRelation(c, path, "db", "db2")
636
hpath := filepath.Join(path, "hooks", "db2-relation-joined")
637
ctx.writeHook(c, hpath, true)
641
upgradeCharm{revision: 2},
644
waitHooks{"upgrade-charm", "config-changed", "db2-relation-joined mysql/0 db2:0"},
645
verifyCharm{revision: 2},
650
func (s *UniterSuite) TestUpdateResourceCausesUpgrade(c *gc.C) {
651
// appendStorageMetadata customises the wordpress charm's metadata,
652
// adding a "wp-content" filesystem store. We do it here rather
653
// than in the charm itself to avoid modifying all of the other
655
appendResource := func(c *gc.C, ctx *context, path string) {
656
f, err := os.OpenFile(filepath.Join(path, "metadata.yaml"), os.O_RDWR|os.O_APPEND, 0644)
657
c.Assert(err, jc.ErrorIsNil)
660
c.Assert(err, jc.ErrorIsNil)
662
_, err = io.WriteString(f, `
666
filename: filename.tgz
667
comment: One line that is useful when operators need to push it.`)
668
c.Assert(err, jc.ErrorIsNil)
670
s.runUniterTests(c, []uniterTest{
672
"update resource causes upgrade",
674
// These steps are just copied from quickstart with a customized
676
createCharm{customize: appendResource},
679
waitUnitAgent{status: status.StatusIdle},
680
waitHooks(startupHooks(false)),
684
waitHooks{"upgrade-charm", "config-changed"},
689
func (s *UniterSuite) TestUniterUpgradeOverwrite(c *gc.C) {
690
//TODO(bogdanteleaga): Fix this on windows
691
if runtime.GOOS == "windows" {
692
c.Skip("bug 1403084: currently does not work on windows")
694
makeTest := func(description string, content, extraChecks ft.Entries) uniterTest {
695
return ut(description,
697
// This is the base charm which all upgrade tests start out running.
698
customize: func(c *gc.C, ctx *context, path string) {
701
ft.File{"file", "blah", 0644},
702
ft.Symlink{"symlink", "file"},
704
// Note that it creates "dir/user-file" at runtime, which may be
705
// preserved or removed depending on the test.
706
script := "echo content > dir/user-file && chmod 755 dir/user-file"
707
appendHook(c, path, "start", script)
713
status: status.StatusIdle,
716
statusGetter: unitStatusGetter,
717
status: status.StatusUnknown,
719
waitHooks{"install", "leader-elected", "config-changed", "start"},
723
customize: func(c *gc.C, _ *context, path string) {
724
content.Create(c, path)
728
upgradeCharm{revision: 1},
730
status: status.StatusIdle,
734
statusGetter: unitStatusGetter,
735
status: status.StatusUnknown,
738
waitHooks{"upgrade-charm", "config-changed"},
739
verifyCharm{revision: 1},
740
custom{func(c *gc.C, ctx *context) {
741
path := filepath.Join(ctx.path, "charm")
742
content.Check(c, path)
743
extraChecks.Check(c, path)
749
s.runUniterTests(c, []uniterTest{
751
"files overwite files, dirs, symlinks",
753
ft.File{"file", "new", 0755},
754
ft.File{"dir", "new", 0755},
755
ft.File{"symlink", "new", 0755},
758
ft.Removed{"dir/user-file"},
761
"symlinks overwite files, dirs, symlinks",
763
ft.Symlink{"file", "new"},
764
ft.Symlink{"dir", "new"},
765
ft.Symlink{"symlink", "new"},
768
ft.Removed{"dir/user-file"},
771
"dirs overwite files, symlinks; merge dirs",
773
ft.Dir{"file", 0755},
775
ft.File{"dir/charm-file", "charm-content", 0644},
776
ft.Dir{"symlink", 0755},
779
ft.File{"dir/user-file", "content\n", 0755},
785
func (s *UniterSuite) TestUniterErrorStateUnforcedUpgrade(c *gc.C) {
786
s.runUniterTests(c, []uniterTest{
787
// Upgrade scenarios from error state.
789
"error state unforced upgrade (ignored until started state)",
790
startupError{"start"},
791
createCharm{revision: 1},
792
upgradeCharm{revision: 1},
794
statusGetter: unitStatusGetter,
795
status: status.StatusError,
796
info: `hook failed: "start"`,
797
data: map[string]interface{}{
805
resolveError{state.ResolvedNoHooks},
807
status: status.StatusIdle,
811
statusGetter: unitStatusGetter,
812
status: status.StatusMaintenance,
813
info: "installing charm software",
816
waitHooks{"upgrade-charm", "config-changed"},
817
verifyCharm{revision: 1},
822
func (s *UniterSuite) TestUniterErrorStateForcedUpgrade(c *gc.C) {
823
s.runUniterTests(c, []uniterTest{
825
"error state forced upgrade",
826
startupError{"start"},
827
createCharm{revision: 1},
828
upgradeCharm{revision: 1, forced: true},
829
// It's not possible to tell directly from state when the upgrade is
830
// complete, because the new unit charm URL is set at the upgrade
831
// process's point of no return (before actually deploying, but after
832
// the charm has been downloaded and verified). However, it's still
833
// useful to wait until that point...
835
statusGetter: unitStatusGetter,
836
status: status.StatusError,
837
info: `hook failed: "start"`,
838
data: map[string]interface{}{
843
// ...because the uniter *will* complete a started deployment even if
844
// we stop it from outside. So, by stopping and starting, we can be
845
// sure that the operation has completed and can safely verify that
846
// the charm state on disk is as we expect.
848
verifyCharm{revision: 1},
850
resolveError{state.ResolvedNoHooks},
852
status: status.StatusIdle,
855
waitHooks{"config-changed"},
861
func (s *UniterSuite) TestUniterDeployerConversion(c *gc.C) {
862
coretesting.SkipIfGitNotAvailable(c)
864
deployerConversionTests := []uniterTest{
866
"install normally, check not using git",
869
checkFiles: ft.Entries{ft.Removed{".git"}},
872
"install with git, restart in steady state",
873
prepareGitUniter{[]stepper{
879
waitHooks{"config-changed"},
881
// At this point, the deployer has been converted, but the
882
// charm directory itself hasn't; the *next* deployment will
883
// actually hit the charm directory and strip out the git
885
createCharm{revision: 1},
886
upgradeCharm{revision: 1},
887
waitHooks{"upgrade-charm", "config-changed"},
889
status: status.StatusIdle,
893
statusGetter: unitStatusGetter,
894
status: status.StatusUnknown,
899
checkFiles: ft.Entries{ft.Removed{".git"}},
903
"install with git, get conflicted, mark resolved",
904
prepareGitUniter{[]stepper{
905
startGitUpgradeError{},
910
resolveError{state.ResolvedNoHooks},
911
waitHooks{"upgrade-charm", "config-changed"},
913
status: status.StatusIdle,
917
statusGetter: unitStatusGetter,
918
status: status.StatusUnknown,
921
verifyCharm{revision: 1},
924
// Due to the uncertainties around marking upgrade conflicts resolved,
925
// the charm directory again remains unconverted, although the deployer
926
// should have been fixed. Again, we check this by running another
927
// upgrade and verifying the .git dir is then removed.
928
createCharm{revision: 2},
929
upgradeCharm{revision: 2},
930
waitHooks{"upgrade-charm", "config-changed"},
932
status: status.StatusIdle,
936
statusGetter: unitStatusGetter,
937
status: status.StatusUnknown,
942
checkFiles: ft.Entries{ft.Removed{".git"}},
946
"install with git, get conflicted, force an upgrade",
947
prepareGitUniter{[]stepper{
948
startGitUpgradeError{},
955
customize: func(c *gc.C, ctx *context, path string) {
956
ft.File{"data", "OVERWRITE!", 0644}.Create(c, path)
960
upgradeCharm{revision: 2, forced: true},
961
waitHooks{"upgrade-charm", "config-changed"},
963
status: status.StatusIdle,
967
// A forced upgrade allows us to swap out the git deployer *and*
968
// the .git dir inside the charm immediately; check we did so.
971
checkFiles: ft.Entries{
973
ft.File{"data", "OVERWRITE!", 0644},
979
s.runUniterTests(c, deployerConversionTests)
982
func (s *UniterSuite) TestUniterUpgradeConflicts(c *gc.C) {
983
coretesting.SkipIfPPC64EL(c, "lp:1448308")
984
//TODO(bogdanteleaga): Fix this on windows
985
if runtime.GOOS == "windows" {
986
c.Skip("bug 1403084: currently does not work on windows")
988
s.runUniterTests(c, []uniterTest{
989
// Upgrade scenarios - handling conflicts.
991
"upgrade: resolving doesn't help until underlying problem is fixed",
993
resolveError{state.ResolvedNoHooks},
994
verifyWaitingUpgradeError{revision: 1},
996
resolveError{state.ResolvedNoHooks},
997
waitHooks{"upgrade-charm", "config-changed"},
999
status: status.StatusIdle,
1003
statusGetter: unitStatusGetter,
1004
status: status.StatusUnknown,
1007
verifyCharm{revision: 1},
1009
`upgrade: forced upgrade does work without explicit resolution if underlying problem was fixed`,
1010
startUpgradeError{},
1011
resolveError{state.ResolvedNoHooks},
1012
verifyWaitingUpgradeError{revision: 1},
1014
createCharm{revision: 2},
1016
upgradeCharm{revision: 2, forced: true},
1017
waitHooks{"upgrade-charm", "config-changed"},
1019
status: status.StatusIdle,
1023
statusGetter: unitStatusGetter,
1024
status: status.StatusUnknown,
1027
verifyCharm{revision: 2},
1029
"upgrade conflict unit dying",
1030
startUpgradeError{},
1032
verifyWaitingUpgradeError{revision: 1},
1034
resolveError{state.ResolvedNoHooks},
1035
waitHooks{"upgrade-charm", "config-changed", "leader-settings-changed", "stop"},
1038
"upgrade conflict unit dead",
1039
startUpgradeError{},
1048
func (s *UniterSuite) TestUniterUpgradeGitConflicts(c *gc.C) {
1049
coretesting.SkipIfGitNotAvailable(c)
1051
// These tests are copies of the old git-deployer-related tests, to test that
1052
// the uniter with the manifest-deployer work patched out still works how it
1053
// used to; thus demonstrating that the *other* tests that verify manifest
1054
// deployer behaviour in the presence of an old git deployer are working against
1055
// an accurate representation of the base state.
1056
// The only actual behaviour change is that we no longer commit changes after
1057
// each hook execution; this is reflected by checking that it's dirty in a couple
1058
// of places where we once checked it was not.
1060
s.runUniterTests(c, []uniterTest{
1061
// Upgrade scenarios - handling conflicts.
1063
"upgrade: conflicting files",
1064
startGitUpgradeError{},
1066
// NOTE: this is just dumbly committing the conflicts, but AFAICT this
1067
// is the only reasonable solution; if the user tells us it's resolved
1068
// we have to take their word for it.
1069
resolveError{state.ResolvedNoHooks},
1070
waitHooks{"upgrade-charm", "config-changed"},
1072
status: status.StatusIdle,
1076
statusGetter: unitStatusGetter,
1077
status: status.StatusUnknown,
1080
verifyGitCharm{revision: 1},
1082
`upgrade: conflicting directories`,
1084
customize: func(c *gc.C, ctx *context, path string) {
1085
err := os.Mkdir(filepath.Join(path, "data"), 0755)
1086
c.Assert(err, jc.ErrorIsNil)
1087
appendHook(c, path, "start", "echo DATA > data/newfile")
1093
status: status.StatusIdle,
1096
statusGetter: unitStatusGetter,
1097
status: status.StatusUnknown,
1099
waitHooks{"install", "leader-elected", "config-changed", "start"},
1100
verifyGitCharm{dirty: true},
1104
customize: func(c *gc.C, ctx *context, path string) {
1105
data := filepath.Join(path, "data")
1106
err := ioutil.WriteFile(data, []byte("<nelson>ha ha</nelson>"), 0644)
1107
c.Assert(err, jc.ErrorIsNil)
1111
upgradeCharm{revision: 1},
1113
statusGetter: unitStatusGetter,
1114
status: status.StatusError,
1115
info: "upgrade failed",
1119
verifyGitCharm{dirty: true},
1121
resolveError{state.ResolvedNoHooks},
1122
waitHooks{"upgrade-charm", "config-changed"},
1124
status: status.StatusIdle,
1127
verifyGitCharm{revision: 1},
1129
"upgrade conflict resolved with forced upgrade",
1130
startGitUpgradeError{},
1133
customize: func(c *gc.C, ctx *context, path string) {
1134
otherdata := filepath.Join(path, "otherdata")
1135
err := ioutil.WriteFile(otherdata, []byte("blah"), 0644)
1136
c.Assert(err, jc.ErrorIsNil)
1140
upgradeCharm{revision: 2, forced: true},
1142
status: status.StatusIdle,
1145
statusGetter: unitStatusGetter,
1146
status: status.StatusUnknown,
1149
waitHooks{"upgrade-charm", "config-changed"},
1150
verifyGitCharm{revision: 2},
1151
custom{func(c *gc.C, ctx *context) {
1152
// otherdata should exist (in v2)
1153
otherdata, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "otherdata"))
1154
c.Assert(err, jc.ErrorIsNil)
1155
c.Assert(string(otherdata), gc.Equals, "blah")
1157
// ignore should not (only in v1)
1158
_, err = os.Stat(filepath.Join(ctx.path, "charm", "ignore"))
1159
c.Assert(err, jc.Satisfies, os.IsNotExist)
1161
// data should contain what was written in the start hook
1162
data, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "data"))
1163
c.Assert(err, jc.ErrorIsNil)
1164
c.Assert(string(data), gc.Equals, "STARTDATA\n")
1167
"upgrade conflict unit dying",
1168
startGitUpgradeError{},
1171
resolveError{state.ResolvedNoHooks},
1172
waitHooks{"upgrade-charm", "config-changed", "leader-settings-changed", "stop"},
1175
"upgrade conflict unit dead",
1176
startGitUpgradeError{},
1184
func (s *UniterSuite) TestUniterRelations(c *gc.C) {
1185
waitDyingHooks := custom{func(c *gc.C, ctx *context) {
1186
// There is no ordering relationship between relation hooks and
1187
// leader-settings-changed hooks; and while we're dying we may
1188
// never get to leader-settings-changed before it's time to run
1189
// the stop (as we might not react to a config change in time).
1190
// It's actually clearer to just list the possible orders:
1191
possibles := [][]string{{
1192
"leader-settings-changed",
1193
"db-relation-departed mysql/0 db:0",
1194
"db-relation-broken db:0",
1197
"db-relation-departed mysql/0 db:0",
1198
"leader-settings-changed",
1199
"db-relation-broken db:0",
1202
"db-relation-departed mysql/0 db:0",
1203
"db-relation-broken db:0",
1204
"leader-settings-changed",
1207
"db-relation-departed mysql/0 db:0",
1208
"db-relation-broken db:0",
1211
unchecked := ctx.hooksCompleted[len(ctx.hooks):]
1212
for _, possible := range possibles {
1213
if ok, _ := jc.DeepEqual(unchecked, possible); ok {
1217
c.Fatalf("unexpected hooks: %v", unchecked)
1219
s.runUniterTests(c, []uniterTest{
1222
"simple joined/changed/departed",
1223
quickStartRelation{},
1226
"db-relation-joined mysql/1 db:0",
1227
"db-relation-changed mysql/1 db:0",
1229
changeRelationUnit{"mysql/0"},
1230
waitHooks{"db-relation-changed mysql/0 db:0"},
1231
removeRelationUnit{"mysql/1"},
1232
waitHooks{"db-relation-departed mysql/1 db:0"},
1235
"relation becomes dying; unit is not last remaining member",
1236
quickStartRelation{},
1239
"db-relation-departed mysql/0 db:0",
1240
"db-relation-broken db:0",
1243
relationState{life: state.Dying},
1244
removeRelationUnit{"mysql/0"},
1246
relationState{removed: true},
1249
"relation becomes dying; unit is last remaining member",
1250
quickStartRelation{},
1251
removeRelationUnit{"mysql/0"},
1252
waitHooks{"db-relation-departed mysql/0 db:0"},
1254
waitHooks{"db-relation-broken db:0"},
1256
relationState{removed: true},
1259
"unit becomes dying while in a relation",
1260
quickStartRelation{},
1264
relationState{life: state.Alive},
1265
removeRelationUnit{"mysql/0"},
1266
relationState{life: state.Alive},
1268
"unit becomes dead while in a relation",
1269
quickStartRelation{},
1273
// TODO BUG(?): the unit doesn't leave the scope, leaving the relation
1274
// unkillable without direct intervention. I'm pretty sure it's not a
1275
// uniter bug -- it should be the responsibility of `juju remove-unit
1276
// --force` to cause the unit to leave any relation scopes it may be
1277
// in -- but it's worth noting here all the same.
1279
"unknown local relation dir is removed",
1280
quickStartRelation{},
1282
custom{func(c *gc.C, ctx *context) {
1283
ft.Dir{"state/relations/90210", 0755}.Create(c, ctx.path)
1286
waitHooks{"config-changed"},
1287
custom{func(c *gc.C, ctx *context) {
1288
ft.Removed{"state/relations/90210"}.Check(c, ctx.path)
1291
"all relations are available to config-changed on bounce, even if state dir is missing",
1293
customize: func(c *gc.C, ctx *context, path string) {
1294
script := uniterRelationsCustomizeScript
1295
appendHook(c, path, "config-changed", script)
1301
status: status.StatusIdle,
1304
statusGetter: unitStatusGetter,
1305
status: status.StatusUnknown,
1307
waitHooks{"install", "leader-elected", "config-changed", "start"},
1308
addRelation{waitJoin: true},
1310
custom{func(c *gc.C, ctx *context) {
1311
// Check the state dir was created, and remove it.
1312
path := fmt.Sprintf("state/relations/%d", ctx.relation.Id())
1313
ft.Dir{path, 0755}.Check(c, ctx.path)
1314
ft.Removed{path}.Create(c, ctx.path)
1316
// Check that config-changed didn't record any relations, because
1317
// they shouldn't been available until after the start hook.
1318
ft.File{"charm/relations.out", "", 0644}.Check(c, ctx.path)
1321
waitHooks{"config-changed"},
1322
custom{func(c *gc.C, ctx *context) {
1323
// Check the state dir was recreated.
1324
path := fmt.Sprintf("state/relations/%d", ctx.relation.Id())
1325
ft.Dir{path, 0755}.Check(c, ctx.path)
1327
// Check that config-changed did record the joined relations.
1328
data := fmt.Sprintf("db:%d\n", ctx.relation.Id())
1329
ft.File{"charm/relations.out", data, 0644}.Check(c, ctx.path)
1335
func (s *UniterSuite) TestUniterRelationErrors(c *gc.C) {
1336
s.runUniterTests(c, []uniterTest{
1338
"hook error during join of a relation",
1339
startupRelationError{"db-relation-joined"},
1341
statusGetter: unitStatusGetter,
1342
status: status.StatusError,
1343
info: `hook failed: "db-relation-joined"`,
1344
data: map[string]interface{}{
1345
"hook": "db-relation-joined",
1347
"remote-unit": "mysql/0",
1351
"hook error during change of a relation",
1352
startupRelationError{"db-relation-changed"},
1354
statusGetter: unitStatusGetter,
1355
status: status.StatusError,
1356
info: `hook failed: "db-relation-changed"`,
1357
data: map[string]interface{}{
1358
"hook": "db-relation-changed",
1360
"remote-unit": "mysql/0",
1364
"hook error after a unit departed",
1365
startupRelationError{"db-relation-departed"},
1366
waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"},
1367
removeRelationUnit{"mysql/0"},
1369
statusGetter: unitStatusGetter,
1370
status: status.StatusError,
1371
info: `hook failed: "db-relation-departed"`,
1372
data: map[string]interface{}{
1373
"hook": "db-relation-departed",
1375
"remote-unit": "mysql/0",
1380
"hook error after a relation died",
1381
startupRelationError{"db-relation-broken"},
1382
waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"},
1385
statusGetter: unitStatusGetter,
1386
status: status.StatusError,
1387
info: `hook failed: "db-relation-broken"`,
1388
data: map[string]interface{}{
1389
"hook": "db-relation-broken",
1397
func (s *UniterSuite) TestActionEvents(c *gc.C) {
1398
s.runUniterTests(c, []uniterTest{
1400
"simple action event: defined in actions.yaml, no args",
1402
customize: func(c *gc.C, ctx *context, path string) {
1403
ctx.writeAction(c, path, "action-log")
1404
ctx.writeActionsYaml(c, path, "action-log")
1408
ensureStateWorker{},
1409
createServiceAndUnit{},
1412
waitUnitAgent{status: status.StatusIdle},
1414
statusGetter: unitStatusGetter,
1415
status: status.StatusUnknown,
1417
waitHooks{"install", "leader-elected", "config-changed", "start"},
1419
addAction{"action-log", nil},
1420
waitActionResults{[]actionResult{{
1422
results: map[string]interface{}{},
1423
status: params.ActionCompleted,
1425
waitUnitAgent{status: status.StatusIdle},
1427
statusGetter: unitStatusGetter,
1428
status: status.StatusUnknown,
1431
"action-fail causes the action to fail with a message",
1433
customize: func(c *gc.C, ctx *context, path string) {
1434
ctx.writeAction(c, path, "action-log-fail")
1435
ctx.writeActionsYaml(c, path, "action-log-fail")
1439
ensureStateWorker{},
1440
createServiceAndUnit{},
1443
waitUnitAgent{status: status.StatusIdle},
1445
statusGetter: unitStatusGetter,
1446
status: status.StatusUnknown,
1448
waitHooks{"install", "leader-elected", "config-changed", "start"},
1450
addAction{"action-log-fail", nil},
1451
waitActionResults{[]actionResult{{
1452
name: "action-log-fail",
1453
results: map[string]interface{}{
1454
"foo": "still works",
1456
message: "I'm afraid I can't let you do that, Dave.",
1457
status: params.ActionFailed,
1459
waitUnitAgent{status: status.StatusIdle}, waitUnitAgent{
1460
statusGetter: unitStatusGetter,
1461
status: status.StatusUnknown,
1464
"action-fail with the wrong arguments fails but is not an error",
1466
customize: func(c *gc.C, ctx *context, path string) {
1467
ctx.writeAction(c, path, "action-log-fail-error")
1468
ctx.writeActionsYaml(c, path, "action-log-fail-error")
1472
ensureStateWorker{},
1473
createServiceAndUnit{},
1476
waitUnitAgent{status: status.StatusIdle},
1478
statusGetter: unitStatusGetter,
1479
status: status.StatusUnknown,
1481
waitHooks{"install", "leader-elected", "config-changed", "start"},
1483
addAction{"action-log-fail-error", nil},
1484
waitActionResults{[]actionResult{{
1485
name: "action-log-fail-error",
1486
results: map[string]interface{}{
1487
"foo": "still works",
1489
message: "A real message",
1490
status: params.ActionFailed,
1492
waitUnitAgent{status: status.StatusIdle},
1494
statusGetter: unitStatusGetter,
1495
status: status.StatusUnknown,
1498
"actions with correct params passed are not an error",
1500
customize: func(c *gc.C, ctx *context, path string) {
1501
ctx.writeAction(c, path, "snapshot")
1502
ctx.writeActionsYaml(c, path, "snapshot")
1506
ensureStateWorker{},
1507
createServiceAndUnit{},
1510
waitUnitAgent{status: status.StatusIdle},
1512
statusGetter: unitStatusGetter,
1513
status: status.StatusUnknown,
1515
waitHooks{"install", "leader-elected", "config-changed", "start"},
1519
params: map[string]interface{}{"outfile": "foo.bar"},
1521
waitActionResults{[]actionResult{{
1523
results: map[string]interface{}{
1524
"outfile": map[string]interface{}{
1525
"name": "snapshot-01.tar",
1526
"size": map[string]interface{}{
1527
"magnitude": "10.3",
1531
"completion": "yes",
1533
status: params.ActionCompleted,
1535
waitUnitAgent{status: status.StatusIdle},
1537
statusGetter: unitStatusGetter,
1538
status: status.StatusUnknown,
1541
"actions with incorrect params passed are not an error but fail",
1543
customize: func(c *gc.C, ctx *context, path string) {
1544
ctx.writeAction(c, path, "snapshot")
1545
ctx.writeActionsYaml(c, path, "snapshot")
1549
ensureStateWorker{},
1550
createServiceAndUnit{},
1553
waitUnitAgent{status: status.StatusIdle},
1555
statusGetter: unitStatusGetter,
1556
status: status.StatusUnknown,
1558
waitHooks{"install", "leader-elected", "config-changed", "start"},
1562
params: map[string]interface{}{"outfile": 2},
1564
waitActionResults{[]actionResult{{
1566
results: map[string]interface{}{},
1567
status: params.ActionFailed,
1568
message: `cannot run "snapshot" action: validation failed: (root).outfile : must be of type string, given 2`,
1570
waitUnitAgent{status: status.StatusIdle},
1572
statusGetter: unitStatusGetter,
1573
status: status.StatusUnknown,
1576
"actions not defined in actions.yaml fail without causing a uniter error",
1578
customize: func(c *gc.C, ctx *context, path string) {
1579
ctx.writeAction(c, path, "snapshot")
1583
ensureStateWorker{},
1584
createServiceAndUnit{},
1587
waitUnitAgent{status: status.StatusIdle},
1589
statusGetter: unitStatusGetter,
1590
status: status.StatusUnknown,
1592
waitHooks{"install", "leader-elected", "config-changed", "start"},
1594
addAction{"snapshot", map[string]interface{}{"outfile": "foo.bar"}},
1595
waitActionResults{[]actionResult{{
1597
results: map[string]interface{}{},
1598
status: params.ActionFailed,
1599
message: `cannot run "snapshot" action: not defined`,
1601
waitUnitAgent{status: status.StatusIdle},
1603
statusGetter: unitStatusGetter,
1604
status: status.StatusUnknown,
1607
"pending actions get consumed",
1609
customize: func(c *gc.C, ctx *context, path string) {
1610
ctx.writeAction(c, path, "action-log")
1611
ctx.writeActionsYaml(c, path, "action-log")
1615
ensureStateWorker{},
1616
createServiceAndUnit{},
1617
addAction{"action-log", nil},
1618
addAction{"action-log", nil},
1619
addAction{"action-log", nil},
1622
waitUnitAgent{status: status.StatusIdle},
1624
statusGetter: unitStatusGetter,
1625
status: status.StatusUnknown,
1627
waitHooks{"install", "leader-elected", "config-changed", "start"},
1629
waitActionResults{[]actionResult{{
1631
results: map[string]interface{}{},
1632
status: params.ActionCompleted,
1635
results: map[string]interface{}{},
1636
status: params.ActionCompleted,
1639
results: map[string]interface{}{},
1640
status: params.ActionCompleted,
1642
waitUnitAgent{status: status.StatusIdle},
1644
statusGetter: unitStatusGetter,
1645
status: status.StatusUnknown,
1648
"actions not implemented fail but are not errors",
1650
customize: func(c *gc.C, ctx *context, path string) {
1651
ctx.writeActionsYaml(c, path, "action-log")
1655
ensureStateWorker{},
1656
createServiceAndUnit{},
1659
waitUnitAgent{status: status.StatusIdle},
1661
statusGetter: unitStatusGetter,
1662
status: status.StatusUnknown,
1664
waitHooks{"install", "leader-elected", "config-changed", "start"},
1666
addAction{"action-log", nil},
1667
waitActionResults{[]actionResult{{
1669
results: map[string]interface{}{},
1670
status: params.ActionFailed,
1671
message: `action not implemented on unit "u/0"`,
1673
waitUnitAgent{status: status.StatusIdle},
1675
statusGetter: unitStatusGetter,
1676
status: status.StatusUnknown,
1679
"actions may run from ModeHookError, but do not clear the error",
1680
startupErrorWithCustomCharm{
1682
customize: func(c *gc.C, ctx *context, path string) {
1683
ctx.writeAction(c, path, "action-log")
1684
ctx.writeActionsYaml(c, path, "action-log")
1687
addAction{"action-log", nil},
1689
statusGetter: unitStatusGetter,
1690
status: status.StatusError,
1691
info: `hook failed: "start"`,
1692
data: map[string]interface{}{
1696
waitActionResults{[]actionResult{{
1698
results: map[string]interface{}{},
1699
status: params.ActionCompleted,
1702
statusGetter: unitStatusGetter,
1703
status: status.StatusError,
1704
info: `hook failed: "start"`,
1705
data: map[string]interface{}{"hook": "start"},
1708
resolveError{state.ResolvedNoHooks},
1709
waitUnitAgent{status: status.StatusIdle},
1711
statusGetter: unitStatusGetter,
1712
status: status.StatusMaintenance,
1713
info: "installing charm software",
1719
func (s *UniterSuite) TestUniterSubordinates(c *gc.C) {
1720
s.runUniterTests(c, []uniterTest{
1723
"unit becomes dying while subordinates exist",
1725
addSubordinateRelation{"juju-info"},
1726
waitSubordinateExists{"logging/0"},
1728
waitSubordinateDying{},
1729
waitHooks{"leader-settings-changed", "stop"},
1731
removeSubordinate{},
1734
"new subordinate becomes necessary while old one is dying",
1736
addSubordinateRelation{"juju-info"},
1737
waitSubordinateExists{"logging/0"},
1738
removeSubordinateRelation{"juju-info"},
1739
// The subordinate Uniter would usually set Dying in this situation.
1741
addSubordinateRelation{"logging-dir"},
1743
removeSubordinate{},
1744
waitSubordinateExists{"logging/1"},
1749
func (s *UniterSuite) TestSubordinateDying(c *gc.C) {
1750
// Create a test context for later use.
1754
path: filepath.Join(s.dataDir, "agents", "unit-u-0"),
1756
charms: make(map[string][]byte),
1757
updateStatusHookTicker: s.updateStatusHookTicker,
1758
charmDirGuard: &mockCharmDirGuard{},
1761
addControllerMachine(c, ctx.st)
1763
// Create the subordinate service.
1764
dir := testcharms.Repo.ClonedDir(c.MkDir(), "logging")
1765
curl, err := corecharm.ParseURL("cs:quantal/logging")
1766
c.Assert(err, jc.ErrorIsNil)
1767
curl = curl.WithRevision(dir.Revision())
1768
step(c, ctx, addCharm{dir, curl})
1769
ctx.svc = s.AddTestingService(c, "u", ctx.sch)
1771
// Create the principal service and add a relation.
1772
wps := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
1773
wpu, err := wps.AddUnit()
1774
c.Assert(err, jc.ErrorIsNil)
1775
eps, err := s.State.InferEndpoints("wordpress", "u")
1776
c.Assert(err, jc.ErrorIsNil)
1777
rel, err := s.State.AddRelation(eps...)
1778
c.Assert(err, jc.ErrorIsNil)
1779
assertAssignUnit(c, s.State, wpu)
1781
// Create the subordinate unit by entering scope as the principal.
1782
wpru, err := rel.Unit(wpu)
1783
c.Assert(err, jc.ErrorIsNil)
1784
err = wpru.EnterScope(nil)
1785
c.Assert(err, jc.ErrorIsNil)
1786
ctx.unit, err = s.State.Unit("u/0")
1787
c.Assert(err, jc.ErrorIsNil)
1790
// Run the actual test.
1791
ctx.run(c, []stepper{
1795
custom{func(c *gc.C, ctx *context) {
1796
c.Assert(rel.Destroy(), gc.IsNil)
1802
func (s *UniterSuite) TestRebootDisabledInActions(c *gc.C) {
1803
s.runUniterTests(c, []uniterTest{
1805
"test that juju-reboot disabled in actions",
1807
customize: func(c *gc.C, ctx *context, path string) {
1808
ctx.writeAction(c, path, "action-reboot")
1809
ctx.writeActionsYaml(c, path, "action-reboot")
1813
ensureStateWorker{},
1814
createServiceAndUnit{},
1815
addAction{"action-reboot", nil},
1819
status: status.StatusIdle,
1822
statusGetter: unitStatusGetter,
1823
status: status.StatusUnknown,
1825
waitActionResults{[]actionResult{{
1826
name: "action-reboot",
1827
results: map[string]interface{}{
1828
"reboot-delayed": "good",
1829
"reboot-now": "good",
1831
status: params.ActionCompleted,
1836
func (s *UniterSuite) TestRebootFinishesHook(c *gc.C) {
1837
s.runUniterTests(c, []uniterTest{
1839
"test that juju-reboot finishes hook, and reboots",
1841
customize: func(c *gc.C, ctx *context, path string) {
1842
hpath := filepath.Join(path, "hooks", "install")
1843
ctx.writeExplicitHook(c, hpath, rebootHook)
1847
ensureStateWorker{},
1848
createServiceAndUnit{},
1851
waitUniterDead{err: "machine needs to reboot"},
1852
waitHooks{"install"},
1855
status: status.StatusIdle,
1858
statusGetter: unitStatusGetter,
1859
status: status.StatusUnknown,
1861
waitHooks{"leader-elected", "config-changed", "start"},
1865
func (s *UniterSuite) TestRebootNowKillsHook(c *gc.C) {
1866
s.runUniterTests(c, []uniterTest{
1868
"test that juju-reboot --now kills hook and exits",
1870
customize: func(c *gc.C, ctx *context, path string) {
1871
hpath := filepath.Join(path, "hooks", "install")
1872
ctx.writeExplicitHook(c, hpath, rebootNowHook)
1876
ensureStateWorker{},
1877
createServiceAndUnit{},
1880
waitUniterDead{err: "machine needs to reboot"},
1881
waitHooks{"install"},
1884
status: status.StatusIdle,
1887
statusGetter: unitStatusGetter,
1888
status: status.StatusUnknown,
1890
waitHooks{"install", "leader-elected", "config-changed", "start"},
1894
func (s *UniterSuite) TestRebootDisabledOnHookError(c *gc.C) {
1895
s.runUniterTests(c, []uniterTest{
1897
"test juju-reboot will not happen if hook errors out",
1899
customize: func(c *gc.C, ctx *context, path string) {
1900
hpath := filepath.Join(path, "hooks", "install")
1901
ctx.writeExplicitHook(c, hpath, badRebootHook)
1905
ensureStateWorker{},
1906
createServiceAndUnit{},
1910
statusGetter: unitStatusGetter,
1911
status: status.StatusError,
1912
info: fmt.Sprintf(`hook failed: "install"`),
1918
func (s *UniterSuite) TestJujuRunExecutionSerialized(c *gc.C) {
1919
s.runUniterTests(c, []uniterTest{
1921
"hook failed status should stay around after juju run",
1922
createCharm{badHooks: []string{"config-changed"}},
1926
statusGetter: unitStatusGetter,
1927
status: status.StatusError,
1928
info: `hook failed: "config-changed"`,
1929
data: map[string]interface{}{
1930
"hook": "config-changed",
1933
runCommands{"exit 0"},
1935
statusGetter: unitStatusGetter,
1936
status: status.StatusError,
1937
info: `hook failed: "config-changed"`,
1938
data: map[string]interface{}{
1939
"hook": "config-changed",
1945
func (s *UniterSuite) TestRebootFromJujuRun(c *gc.C) {
1946
//TODO(bogdanteleaga): Fix this on windows
1947
if runtime.GOOS == "windows" {
1948
c.Skip("bug 1403084: currently does not work on windows")
1950
s.runUniterTests(c, []uniterTest{
1954
runCommands{"juju-reboot"},
1955
waitUniterDead{err: "machine needs to reboot"},
1957
waitHooks{"config-changed"},
1959
"test juju-reboot with bad hook",
1960
startupError{"install"},
1961
runCommands{"juju-reboot"},
1962
waitUniterDead{err: "machine needs to reboot"},
1966
"test juju-reboot --now",
1968
runCommands{"juju-reboot --now"},
1969
waitUniterDead{err: "machine needs to reboot"},
1971
waitHooks{"config-changed"},
1973
"test juju-reboot --now with bad hook",
1974
startupError{"install"},
1975
runCommands{"juju-reboot --now"},
1976
waitUniterDead{err: "machine needs to reboot"},
1983
func (s *UniterSuite) TestLeadership(c *gc.C) {
1984
s.runUniterTests(c, []uniterTest{
1986
"hook tools when leader",
1988
runCommands{"leader-set foo=bar baz=qux"},
1989
verifyLeaderSettings{"foo": "bar", "baz": "qux"},
1991
"hook tools when not leader",
1992
quickStart{minion: true},
1993
runCommands{leadershipScript},
1995
"leader-elected triggers when elected",
1996
quickStart{minion: true},
1998
waitHooks{"leader-elected"},
2000
"leader-settings-changed triggers when leader settings change",
2001
quickStart{minion: true},
2002
setLeaderSettings{"ping": "pong"},
2003
waitHooks{"leader-settings-changed"},
2005
"leader-settings-changed triggers when bounced",
2006
quickStart{minion: true},
2007
verifyRunning{minion: true},
2009
"leader-settings-changed triggers when deposed (while stopped)",
2013
verifyRunning{minion: true},
2018
func (s *UniterSuite) TestLeadershipUnexpectedDepose(c *gc.C) {
2019
s.runUniterTests(c, []uniterTest{
2021
// NOTE: this is a strange and ugly test, intended to detect what
2022
// *would* happen if the uniter suddenly failed to renew its lease;
2023
// it depends on an artificially shortened tracker refresh time to
2024
// run in a reasonable amount of time.
2025
"leader-settings-changed triggers when deposed (while running)",
2028
waitHooks{"leader-settings-changed"},
2033
func (s *UniterSuite) TestStorage(c *gc.C) {
2034
// appendStorageMetadata customises the wordpress charm's metadata,
2035
// adding a "wp-content" filesystem store. We do it here rather
2036
// than in the charm itself to avoid modifying all of the other
2038
appendStorageMetadata := func(c *gc.C, ctx *context, path string) {
2039
f, err := os.OpenFile(filepath.Join(path, "metadata.yaml"), os.O_RDWR|os.O_APPEND, 0644)
2040
c.Assert(err, jc.ErrorIsNil)
2043
c.Assert(err, jc.ErrorIsNil)
2045
_, err = io.WriteString(f, "storage:\n wp-content:\n type: filesystem\n")
2046
c.Assert(err, jc.ErrorIsNil)
2048
s.runUniterTests(c, []uniterTest{
2050
"test that storage-attached is called",
2051
createCharm{customize: appendStorageMetadata},
2053
ensureStateWorker{},
2054
createServiceAndUnit{},
2058
waitHooks{"wp-content-storage-attached"},
2059
waitHooks(startupHooks(false)),
2061
"test that storage-detaching is called before stop",
2062
createCharm{customize: appendStorageMetadata},
2064
ensureStateWorker{},
2065
createServiceAndUnit{},
2069
waitHooks{"wp-content-storage-attached"},
2070
waitHooks(startupHooks(false)),
2072
waitHooks{"leader-settings-changed"},
2073
// "stop" hook is not called until storage is detached
2074
waitHooks{"wp-content-storage-detaching", "stop"},
2075
verifyStorageDetached{},
2078
"test that storage-detaching is called only if previously attached",
2079
createCharm{customize: appendStorageMetadata},
2081
ensureStateWorker{},
2082
createServiceAndUnit{},
2083
// provision and destroy the storage before the uniter starts,
2084
// to ensure it never sees the storage as attached
2086
destroyStorageAttachment{},
2088
waitHooks(startupHooks(false)),
2090
// storage-detaching is not called because it was never attached
2091
waitHooks{"leader-settings-changed", "stop"},
2092
verifyStorageDetached{},
2095
"test that delay-provisioned storage does not block forever",
2096
createCharm{customize: appendStorageMetadata},
2098
ensureStateWorker{},
2099
createServiceAndUnit{},
2101
// no hooks should be run, as storage isn't provisioned
2104
waitHooks{"wp-content-storage-attached"},
2105
waitHooks(startupHooks(false)),
2107
"test that unprovisioned storage does not block unit termination",
2108
createCharm{customize: appendStorageMetadata},
2110
ensureStateWorker{},
2111
createServiceAndUnit{},
2114
// no hooks should be run, and unit agent should terminate
2118
// TODO(axw) test that storage-attached is run for new
2119
// storage attachments before upgrade-charm is run. This
2120
// requires additions to state to add storage when a charm
2125
type mockExecutor struct {
2129
func (m *mockExecutor) Run(op operation.Operation) error {
2130
// want to allow charm unpacking to occur
2131
if strings.HasPrefix(op.String(), "install") {
2132
return m.Executor.Run(op)
2134
// but hooks should error
2135
return errors.New("some error occurred")
2138
func (s *UniterSuite) TestOperationErrorReported(c *gc.C) {
2139
executorFunc := func(stateFilePath string, getInstallCharm func() (*corecharm.URL, error), acquireLock func() (mutex.Releaser, error)) (operation.Executor, error) {
2140
e, err := operation.NewExecutor(stateFilePath, getInstallCharm, acquireLock)
2141
c.Assert(err, jc.ErrorIsNil)
2142
return &mockExecutor{e}, nil
2144
s.runUniterTests(c, []uniterTest{
2146
"error running operations are reported",
2149
createUniter{executorFunc: executorFunc},
2151
status: status.StatusFailed,
2152
info: "resolver loop error",
2154
expectError{".*some error occurred.*"},