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  }