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