github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/operation/runhook_test.go (about)

     1  // Copyright 2014-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package operation_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/charm.v5/hooks"
    14  
    15  	"github.com/juju/juju/worker/uniter/hook"
    16  	"github.com/juju/juju/worker/uniter/operation"
    17  	"github.com/juju/juju/worker/uniter/runner"
    18  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    19  )
    20  
    21  type RunHookSuite struct {
    22  	testing.IsolationSuite
    23  }
    24  
    25  var _ = gc.Suite(&RunHookSuite{})
    26  
    27  type newHook func(operation.Factory, hook.Info) (operation.Operation, error)
    28  
    29  func (s *RunHookSuite) testClearResolvedFlagError(c *gc.C, newHook newHook) {
    30  	callbacks := &PrepareHookCallbacks{
    31  		MockClearResolvedFlag: &MockNoArgs{err: errors.New("biff")},
    32  	}
    33  	factory := operation.NewFactory(operation.FactoryParams{
    34  		Callbacks: callbacks,
    35  	})
    36  	op, err := newHook(factory, hook.Info{Kind: hooks.ConfigChanged})
    37  	c.Assert(err, jc.ErrorIsNil)
    38  
    39  	newState, err := op.Prepare(operation.State{})
    40  	c.Check(newState, gc.IsNil)
    41  	c.Check(callbacks.MockClearResolvedFlag.called, jc.IsTrue)
    42  	c.Check(err, gc.ErrorMatches, "biff")
    43  }
    44  
    45  func (s *RunHookSuite) TestClearResolvedFlagError_Retry(c *gc.C) {
    46  	s.testClearResolvedFlagError(c, (operation.Factory).NewRetryHook)
    47  }
    48  
    49  func (s *RunHookSuite) TestClearResolvedFlagError_Skip(c *gc.C) {
    50  	s.testClearResolvedFlagError(c, (operation.Factory).NewSkipHook)
    51  }
    52  
    53  func (s *RunHookSuite) testPrepareHookError(
    54  	c *gc.C, newHook newHook, expectClearResolvedFlag, expectSkip bool,
    55  ) {
    56  	callbacks := &PrepareHookCallbacks{
    57  		MockPrepareHook:       &MockPrepareHook{err: errors.New("pow")},
    58  		MockClearResolvedFlag: &MockNoArgs{},
    59  	}
    60  	factory := operation.NewFactory(operation.FactoryParams{
    61  		Callbacks: callbacks,
    62  	})
    63  	op, err := newHook(factory, hook.Info{Kind: hooks.ConfigChanged})
    64  	c.Assert(err, jc.ErrorIsNil)
    65  
    66  	newState, err := op.Prepare(operation.State{})
    67  	c.Check(newState, gc.IsNil)
    68  	c.Check(callbacks.MockClearResolvedFlag.called, gc.Equals, expectClearResolvedFlag)
    69  	if expectSkip {
    70  		c.Check(err, gc.Equals, operation.ErrSkipExecute)
    71  		c.Check(callbacks.MockPrepareHook.gotHook, gc.IsNil)
    72  		return
    73  	}
    74  	c.Check(err, gc.ErrorMatches, "pow")
    75  	c.Check(callbacks.MockPrepareHook.gotHook, gc.DeepEquals, &hook.Info{
    76  		Kind: hooks.ConfigChanged,
    77  	})
    78  }
    79  
    80  func (s *RunHookSuite) TestPrepareHookCtxCalled(c *gc.C) {
    81  	ctx := &MockContext{}
    82  	callbacks := &PrepareHookCallbacks{
    83  		MockPrepareHook:       &MockPrepareHook{},
    84  		MockClearResolvedFlag: &MockNoArgs{},
    85  	}
    86  	runnerFactory := &MockRunnerFactory{
    87  		MockNewHookRunner: &MockNewHookRunner{
    88  			runner: &MockRunner{
    89  				context: ctx,
    90  			},
    91  		},
    92  	}
    93  	factory := operation.NewFactory(operation.FactoryParams{
    94  		RunnerFactory: runnerFactory,
    95  		Callbacks:     callbacks,
    96  	})
    97  
    98  	op, err := factory.NewRunHook(hook.Info{Kind: hooks.ConfigChanged})
    99  	c.Assert(err, jc.ErrorIsNil)
   100  
   101  	newState, err := op.Prepare(operation.State{})
   102  	c.Check(newState, gc.NotNil)
   103  	c.Assert(err, jc.ErrorIsNil)
   104  
   105  	ctx.CheckCall(c, 0, "Prepare")
   106  }
   107  
   108  func (s *RunHookSuite) TestPrepareHookCtxError(c *gc.C) {
   109  	ctx := &MockContext{}
   110  	ctx.SetErrors(errors.New("ctx prepare error"))
   111  	callbacks := &PrepareHookCallbacks{
   112  		MockPrepareHook:       &MockPrepareHook{},
   113  		MockClearResolvedFlag: &MockNoArgs{},
   114  	}
   115  	runnerFactory := &MockRunnerFactory{
   116  		MockNewHookRunner: &MockNewHookRunner{
   117  			runner: &MockRunner{
   118  				context: ctx,
   119  			},
   120  		},
   121  	}
   122  	factory := operation.NewFactory(operation.FactoryParams{
   123  		RunnerFactory: runnerFactory,
   124  		Callbacks:     callbacks,
   125  	})
   126  
   127  	op, err := factory.NewRunHook(hook.Info{Kind: hooks.ConfigChanged})
   128  	c.Assert(err, jc.ErrorIsNil)
   129  
   130  	newState, err := op.Prepare(operation.State{})
   131  	c.Check(newState, gc.IsNil)
   132  	c.Assert(err, gc.ErrorMatches, `ctx prepare error`)
   133  
   134  	ctx.CheckCall(c, 0, "Prepare")
   135  }
   136  
   137  func (s *RunHookSuite) TestPrepareHookError_Run(c *gc.C) {
   138  	s.testPrepareHookError(c, (operation.Factory).NewRunHook, false, false)
   139  }
   140  
   141  func (s *RunHookSuite) TestPrepareHookError_Retry(c *gc.C) {
   142  	s.testPrepareHookError(c, (operation.Factory).NewRetryHook, true, false)
   143  }
   144  
   145  func (s *RunHookSuite) TestPrepareHookError_Skip(c *gc.C) {
   146  	s.testPrepareHookError(c, (operation.Factory).NewSkipHook, true, true)
   147  }
   148  
   149  func (s *RunHookSuite) testPrepareRunnerError(c *gc.C, newHook newHook) {
   150  	callbacks := NewPrepareHookCallbacks()
   151  	runnerFactory := &MockRunnerFactory{
   152  		MockNewHookRunner: &MockNewHookRunner{err: errors.New("splat")},
   153  	}
   154  	factory := operation.NewFactory(operation.FactoryParams{
   155  		RunnerFactory: runnerFactory,
   156  		Callbacks:     callbacks,
   157  	})
   158  	op, err := newHook(factory, hook.Info{Kind: hooks.ConfigChanged})
   159  	c.Assert(err, jc.ErrorIsNil)
   160  
   161  	newState, err := op.Prepare(operation.State{})
   162  	c.Check(newState, gc.IsNil)
   163  	c.Check(err, gc.ErrorMatches, "splat")
   164  	c.Check(runnerFactory.MockNewHookRunner.gotHook, gc.DeepEquals, &hook.Info{
   165  		Kind: hooks.ConfigChanged,
   166  	})
   167  }
   168  
   169  func (s *RunHookSuite) TestPrepareRunnerError_Run(c *gc.C) {
   170  	s.testPrepareRunnerError(c, (operation.Factory).NewRunHook)
   171  }
   172  
   173  func (s *RunHookSuite) TestPrepareRunnerError_Retry(c *gc.C) {
   174  	s.testPrepareRunnerError(c, (operation.Factory).NewRetryHook)
   175  }
   176  
   177  func (s *RunHookSuite) testPrepareSuccess(
   178  	c *gc.C, newHook newHook, before, after operation.State,
   179  ) {
   180  	runnerFactory := NewRunHookRunnerFactory(errors.New("should not call"))
   181  	callbacks := NewPrepareHookCallbacks()
   182  	factory := operation.NewFactory(operation.FactoryParams{
   183  		RunnerFactory: runnerFactory,
   184  		Callbacks:     callbacks,
   185  	})
   186  	op, err := newHook(factory, hook.Info{Kind: hooks.ConfigChanged})
   187  	c.Assert(err, jc.ErrorIsNil)
   188  
   189  	newState, err := op.Prepare(before)
   190  	c.Check(err, jc.ErrorIsNil)
   191  	c.Check(newState, gc.DeepEquals, &after)
   192  }
   193  
   194  func (s *RunHookSuite) TestPrepareSuccess_BlankSlate(c *gc.C) {
   195  	for i, newHook := range []newHook{
   196  		(operation.Factory).NewRunHook,
   197  		(operation.Factory).NewRetryHook,
   198  	} {
   199  		c.Logf("variant %d", i)
   200  		s.testPrepareSuccess(c,
   201  			newHook,
   202  			operation.State{},
   203  			operation.State{
   204  				Kind: operation.RunHook,
   205  				Step: operation.Pending,
   206  				Hook: &hook.Info{Kind: hooks.ConfigChanged},
   207  			},
   208  		)
   209  	}
   210  }
   211  
   212  func (s *RunHookSuite) TestPrepareSuccess_Preserve(c *gc.C) {
   213  	for i, newHook := range []newHook{
   214  		(operation.Factory).NewRunHook,
   215  		(operation.Factory).NewRetryHook,
   216  	} {
   217  		c.Logf("variant %d", i)
   218  		s.testPrepareSuccess(c,
   219  			newHook,
   220  			overwriteState,
   221  			operation.State{
   222  				Started:            true,
   223  				CollectMetricsTime: 1234567,
   224  				UpdateStatusTime:   1234567,
   225  				Kind:               operation.RunHook,
   226  				Step:               operation.Pending,
   227  				Hook:               &hook.Info{Kind: hooks.ConfigChanged},
   228  			},
   229  		)
   230  	}
   231  }
   232  
   233  func (s *RunHookSuite) getExecuteRunnerTest(c *gc.C, newHook newHook, kind hooks.Kind, runErr error) (operation.Operation, *ExecuteHookCallbacks, *MockRunnerFactory) {
   234  	runnerFactory := NewRunHookRunnerFactory(runErr)
   235  	callbacks := &ExecuteHookCallbacks{
   236  		PrepareHookCallbacks:    NewPrepareHookCallbacks(),
   237  		MockNotifyHookCompleted: &MockNotify{},
   238  		MockNotifyHookFailed:    &MockNotify{},
   239  	}
   240  	factory := operation.NewFactory(operation.FactoryParams{
   241  		RunnerFactory: runnerFactory,
   242  		Callbacks:     callbacks,
   243  	})
   244  	op, err := newHook(factory, hook.Info{Kind: kind})
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	return op, callbacks, runnerFactory
   247  }
   248  
   249  func (s *RunHookSuite) testExecuteMissingHookError(c *gc.C, newHook newHook) {
   250  	runErr := runner.NewMissingHookError("blah-blah")
   251  	for _, kind := range hooks.UnitHooks() {
   252  		c.Logf("hook %v", kind)
   253  		op, callbacks, runnerFactory := s.getExecuteRunnerTest(c, newHook, kind, runErr)
   254  		_, err := op.Prepare(operation.State{})
   255  		c.Assert(err, jc.ErrorIsNil)
   256  
   257  		newState, err := op.Execute(operation.State{})
   258  		c.Assert(err, jc.ErrorIsNil)
   259  		c.Assert(newState, gc.DeepEquals, &operation.State{
   260  			Kind: operation.RunHook,
   261  			Step: operation.Done,
   262  			Hook: &hook.Info{Kind: kind},
   263  		})
   264  		c.Assert(*runnerFactory.MockNewHookRunner.runner.MockRunHook.gotName, gc.Equals, "some-hook-name")
   265  		c.Assert(callbacks.MockNotifyHookCompleted.gotName, gc.IsNil)
   266  		c.Assert(callbacks.MockNotifyHookFailed.gotName, gc.IsNil)
   267  
   268  		status, err := runnerFactory.MockNewHookRunner.runner.Context().UnitStatus()
   269  		c.Assert(err, jc.ErrorIsNil)
   270  		testAfterHookStatus(c, kind, status, false)
   271  	}
   272  }
   273  
   274  func (s *RunHookSuite) TestExecuteMissingHookError_Run(c *gc.C) {
   275  	s.testExecuteMissingHookError(c, (operation.Factory).NewRunHook)
   276  }
   277  
   278  func (s *RunHookSuite) TestExecuteMissingHookError_Retry(c *gc.C) {
   279  	s.testExecuteMissingHookError(c, (operation.Factory).NewRetryHook)
   280  }
   281  
   282  func (s *RunHookSuite) testExecuteRequeueRebootError(c *gc.C, newHook newHook) {
   283  	runErr := runner.ErrRequeueAndReboot
   284  	op, callbacks, runnerFactory := s.getExecuteRunnerTest(c, newHook, hooks.ConfigChanged, runErr)
   285  	_, err := op.Prepare(operation.State{})
   286  	c.Assert(err, jc.ErrorIsNil)
   287  
   288  	newState, err := op.Execute(operation.State{})
   289  	c.Assert(err, gc.Equals, operation.ErrNeedsReboot)
   290  	c.Assert(newState, gc.DeepEquals, &operation.State{
   291  		Kind: operation.RunHook,
   292  		Step: operation.Queued,
   293  		Hook: &hook.Info{Kind: hooks.ConfigChanged},
   294  	})
   295  	c.Assert(*runnerFactory.MockNewHookRunner.runner.MockRunHook.gotName, gc.Equals, "some-hook-name")
   296  	c.Assert(*callbacks.MockNotifyHookCompleted.gotName, gc.Equals, "some-hook-name")
   297  	c.Assert(*callbacks.MockNotifyHookCompleted.gotContext, gc.Equals, runnerFactory.MockNewHookRunner.runner.context)
   298  	c.Assert(callbacks.MockNotifyHookFailed.gotName, gc.IsNil)
   299  }
   300  
   301  func (s *RunHookSuite) TestExecuteRequeueRebootError_Run(c *gc.C) {
   302  	s.testExecuteRequeueRebootError(c, (operation.Factory).NewRunHook)
   303  }
   304  
   305  func (s *RunHookSuite) TestExecuteRequeueRebootError_Retry(c *gc.C) {
   306  	s.testExecuteRequeueRebootError(c, (operation.Factory).NewRetryHook)
   307  }
   308  
   309  func (s *RunHookSuite) testExecuteRebootError(c *gc.C, newHook newHook) {
   310  	runErr := runner.ErrReboot
   311  	op, callbacks, runnerFactory := s.getExecuteRunnerTest(c, newHook, hooks.ConfigChanged, runErr)
   312  	_, err := op.Prepare(operation.State{})
   313  	c.Assert(err, jc.ErrorIsNil)
   314  
   315  	newState, err := op.Execute(operation.State{})
   316  	c.Assert(err, gc.Equals, operation.ErrNeedsReboot)
   317  	c.Assert(newState, gc.DeepEquals, &operation.State{
   318  		Kind: operation.RunHook,
   319  		Step: operation.Done,
   320  		Hook: &hook.Info{Kind: hooks.ConfigChanged},
   321  	})
   322  	c.Assert(*runnerFactory.MockNewHookRunner.runner.MockRunHook.gotName, gc.Equals, "some-hook-name")
   323  	c.Assert(*callbacks.MockNotifyHookCompleted.gotName, gc.Equals, "some-hook-name")
   324  	c.Assert(*callbacks.MockNotifyHookCompleted.gotContext, gc.Equals, runnerFactory.MockNewHookRunner.runner.context)
   325  	c.Assert(callbacks.MockNotifyHookFailed.gotName, gc.IsNil)
   326  }
   327  
   328  func (s *RunHookSuite) TestExecuteRebootError_Run(c *gc.C) {
   329  	s.testExecuteRebootError(c, (operation.Factory).NewRunHook)
   330  }
   331  
   332  func (s *RunHookSuite) TestExecuteRebootError_Retry(c *gc.C) {
   333  	s.testExecuteRebootError(c, (operation.Factory).NewRetryHook)
   334  }
   335  
   336  func (s *RunHookSuite) testExecuteOtherError(c *gc.C, newHook newHook) {
   337  	runErr := errors.New("graaargh")
   338  	op, callbacks, runnerFactory := s.getExecuteRunnerTest(c, newHook, hooks.ConfigChanged, runErr)
   339  	_, err := op.Prepare(operation.State{})
   340  	c.Assert(err, jc.ErrorIsNil)
   341  
   342  	newState, err := op.Execute(operation.State{})
   343  	c.Assert(err, gc.Equals, operation.ErrHookFailed)
   344  	c.Assert(newState, gc.IsNil)
   345  	c.Assert(*runnerFactory.MockNewHookRunner.runner.MockRunHook.gotName, gc.Equals, "some-hook-name")
   346  	c.Assert(*callbacks.MockNotifyHookFailed.gotName, gc.Equals, "some-hook-name")
   347  	c.Assert(*callbacks.MockNotifyHookFailed.gotContext, gc.Equals, runnerFactory.MockNewHookRunner.runner.context)
   348  	c.Assert(callbacks.MockNotifyHookCompleted.gotName, gc.IsNil)
   349  }
   350  
   351  func (s *RunHookSuite) TestExecuteOtherError_Run(c *gc.C) {
   352  	s.testExecuteOtherError(c, (operation.Factory).NewRunHook)
   353  }
   354  
   355  func (s *RunHookSuite) TestExecuteOtherError_Retry(c *gc.C) {
   356  	s.testExecuteOtherError(c, (operation.Factory).NewRetryHook)
   357  }
   358  
   359  func (s *RunHookSuite) testExecuteSuccess(
   360  	c *gc.C, newHook newHook, before, after operation.State, setStatusCalled bool,
   361  ) {
   362  	op, callbacks, f := s.getExecuteRunnerTest(c, newHook, hooks.ConfigChanged, nil)
   363  	f.MockNewHookRunner.runner.MockRunHook.setStatusCalled = setStatusCalled
   364  	midState, err := op.Prepare(before)
   365  	c.Assert(err, jc.ErrorIsNil)
   366  	c.Assert(midState, gc.NotNil)
   367  
   368  	newState, err := op.Execute(*midState)
   369  	c.Assert(err, jc.ErrorIsNil)
   370  	c.Assert(newState, gc.DeepEquals, &after)
   371  	c.Check(callbacks.executingMessage, gc.Equals, "running some-hook-name hook")
   372  }
   373  
   374  func (s *RunHookSuite) TestExecuteSuccess_BlankSlate(c *gc.C) {
   375  	for i, newHook := range []newHook{
   376  		(operation.Factory).NewRunHook,
   377  		(operation.Factory).NewRetryHook,
   378  	} {
   379  		c.Logf("variant %d", i)
   380  		s.testExecuteSuccess(c,
   381  			newHook,
   382  			operation.State{},
   383  			operation.State{
   384  				Kind:      operation.RunHook,
   385  				Step:      operation.Done,
   386  				Hook:      &hook.Info{Kind: hooks.ConfigChanged},
   387  				StatusSet: true,
   388  			},
   389  			true,
   390  		)
   391  	}
   392  }
   393  
   394  func (s *RunHookSuite) TestExecuteSuccess_Preserve(c *gc.C) {
   395  	for i, newHook := range []newHook{
   396  		(operation.Factory).NewRunHook,
   397  		(operation.Factory).NewRetryHook,
   398  	} {
   399  		c.Logf("variant %d", i)
   400  		s.testExecuteSuccess(c,
   401  			newHook,
   402  			overwriteState,
   403  			operation.State{
   404  				Started:            true,
   405  				CollectMetricsTime: 1234567,
   406  				UpdateStatusTime:   1234567,
   407  				Kind:               operation.RunHook,
   408  				Step:               operation.Done,
   409  				Hook:               &hook.Info{Kind: hooks.ConfigChanged},
   410  				StatusSet:          true,
   411  			},
   412  			true,
   413  		)
   414  	}
   415  }
   416  
   417  func (s *RunHookSuite) testExecuteThenCharmStatus(
   418  	c *gc.C, newHook newHook, before, after operation.State, kind hooks.Kind, setStatusCalled bool,
   419  ) {
   420  	op, _, f := s.getExecuteRunnerTest(c, newHook, kind, nil)
   421  	f.MockNewHookRunner.runner.MockRunHook.setStatusCalled = setStatusCalled
   422  	midState, err := op.Prepare(before)
   423  	c.Assert(err, jc.ErrorIsNil)
   424  	c.Assert(midState, gc.NotNil)
   425  
   426  	status, err := f.MockNewHookRunner.runner.Context().UnitStatus()
   427  	c.Assert(err, jc.ErrorIsNil)
   428  
   429  	newState, err := op.Execute(*midState)
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	c.Assert(newState, gc.DeepEquals, &after)
   432  
   433  	status, err = f.MockNewHookRunner.runner.Context().UnitStatus()
   434  	c.Assert(err, jc.ErrorIsNil)
   435  	testAfterHookStatus(c, kind, status, after.StatusSet)
   436  }
   437  
   438  func testBeforeHookStatus(c *gc.C, kind hooks.Kind, status *jujuc.StatusInfo) {
   439  	switch kind {
   440  	case hooks.Install:
   441  		c.Assert(status.Status, gc.Equals, "maintenance")
   442  		c.Assert(status.Info, gc.Equals, "installing charm software")
   443  	case hooks.Stop:
   444  		c.Assert(string(status.Status), gc.Equals, "maintenance")
   445  		c.Assert(status.Info, gc.Equals, "cleaning up prior to charm deletion")
   446  	default:
   447  		c.Assert(string(status.Status), gc.Equals, "")
   448  	}
   449  }
   450  
   451  func testAfterHookStatus(c *gc.C, kind hooks.Kind, status *jujuc.StatusInfo, statusSetCalled bool) {
   452  	switch kind {
   453  	case hooks.Install:
   454  		c.Assert(status.Status, gc.Equals, "maintenance")
   455  		c.Assert(status.Info, gc.Equals, "installing charm software")
   456  	case hooks.Start:
   457  		if statusSetCalled {
   458  			c.Assert(string(status.Status), gc.Equals, "")
   459  		} else {
   460  			c.Assert(status.Status, gc.Equals, "unknown")
   461  		}
   462  	case hooks.Stop:
   463  		c.Assert(status.Status, gc.Equals, "terminated")
   464  	default:
   465  		c.Assert(string(status.Status), gc.Equals, "")
   466  	}
   467  }
   468  
   469  func (s *RunHookSuite) testBeforeHookExecute(c *gc.C, newHook newHook, kind hooks.Kind) {
   470  	// To check what happens in the beforeHook() call, we run a hook with an error
   471  	// so that it does not complete successfully and thus afterHook() does not run,
   472  	// overwriting the values.
   473  	runErr := errors.New("graaargh")
   474  	op, _, runnerFactory := s.getExecuteRunnerTest(c, newHook, kind, runErr)
   475  	_, err := op.Prepare(operation.State{})
   476  	c.Assert(err, jc.ErrorIsNil)
   477  
   478  	newState, err := op.Execute(operation.State{})
   479  	c.Assert(err, gc.Equals, operation.ErrHookFailed)
   480  	c.Assert(newState, gc.IsNil)
   481  
   482  	status, err := runnerFactory.MockNewHookRunner.runner.Context().UnitStatus()
   483  	c.Assert(err, jc.ErrorIsNil)
   484  	testBeforeHookStatus(c, kind, status)
   485  }
   486  
   487  func (s *RunHookSuite) TestBeforeHookStatus(c *gc.C) {
   488  	for _, kind := range hooks.UnitHooks() {
   489  		c.Logf("hook %v", kind)
   490  		for i, newHook := range []newHook{
   491  			(operation.Factory).NewRunHook,
   492  			(operation.Factory).NewRetryHook,
   493  		} {
   494  			c.Logf("variant %d", i)
   495  			s.testBeforeHookExecute(c, newHook, kind)
   496  		}
   497  	}
   498  }
   499  
   500  func (s *RunHookSuite) testExecuteHookWithSetStatus(c *gc.C, kind hooks.Kind, setStatusCalled bool) {
   501  	for i, newHook := range []newHook{
   502  		(operation.Factory).NewRunHook,
   503  		(operation.Factory).NewRetryHook,
   504  	} {
   505  		c.Logf("variant %d", i)
   506  		s.testExecuteThenCharmStatus(c,
   507  			newHook,
   508  			overwriteState,
   509  			operation.State{
   510  				Started:            true,
   511  				CollectMetricsTime: 1234567,
   512  				UpdateStatusTime:   1234567,
   513  				Kind:               operation.RunHook,
   514  				Step:               operation.Done,
   515  				Hook:               &hook.Info{Kind: kind},
   516  				StatusSet:          setStatusCalled,
   517  			},
   518  			kind,
   519  			setStatusCalled,
   520  		)
   521  	}
   522  }
   523  
   524  func (s *RunHookSuite) TestExecuteHookWithSetStatus(c *gc.C) {
   525  	for _, kind := range hooks.UnitHooks() {
   526  		c.Logf("hook %v", kind)
   527  		s.testExecuteHookWithSetStatus(c, kind, true)
   528  		s.testExecuteHookWithSetStatus(c, kind, false)
   529  	}
   530  }
   531  
   532  func (s *RunHookSuite) testCommitError(c *gc.C, newHook newHook) {
   533  	callbacks := &CommitHookCallbacks{
   534  		MockCommitHook: &MockCommitHook{nil, errors.New("pow")},
   535  	}
   536  	factory := operation.NewFactory(operation.FactoryParams{
   537  		Callbacks: callbacks,
   538  	})
   539  	op, err := newHook(factory, hook.Info{Kind: hooks.ConfigChanged})
   540  	c.Assert(err, jc.ErrorIsNil)
   541  
   542  	newState, err := op.Commit(operation.State{})
   543  	c.Assert(newState, gc.IsNil)
   544  	c.Assert(err, gc.ErrorMatches, "pow")
   545  }
   546  
   547  func (s *RunHookSuite) TestCommitError_Run(c *gc.C) {
   548  	s.testCommitError(c, (operation.Factory).NewRunHook)
   549  }
   550  
   551  func (s *RunHookSuite) TestCommitError_Retry(c *gc.C) {
   552  	s.testCommitError(c, (operation.Factory).NewRetryHook)
   553  }
   554  
   555  func (s *RunHookSuite) TestCommitError_Skip(c *gc.C) {
   556  	s.testCommitError(c, (operation.Factory).NewSkipHook)
   557  }
   558  
   559  func (s *RunHookSuite) testCommitSuccess(c *gc.C, newHook newHook, hookInfo hook.Info, before, after operation.State) {
   560  	callbacks := &CommitHookCallbacks{
   561  		MockCommitHook: &MockCommitHook{},
   562  	}
   563  	factory := operation.NewFactory(operation.FactoryParams{
   564  		Callbacks: callbacks,
   565  	})
   566  	op, err := newHook(factory, hookInfo)
   567  	c.Assert(err, jc.ErrorIsNil)
   568  
   569  	newState, err := op.Commit(before)
   570  	c.Assert(err, jc.ErrorIsNil)
   571  	c.Assert(newState, gc.DeepEquals, &after)
   572  }
   573  
   574  func (s *RunHookSuite) TestCommitSuccess_ConfigChanged_QueueStartHook(c *gc.C) {
   575  	for i, newHook := range []newHook{
   576  		(operation.Factory).NewRunHook,
   577  		(operation.Factory).NewRetryHook,
   578  		(operation.Factory).NewSkipHook,
   579  	} {
   580  		c.Logf("variant %d", i)
   581  		s.testCommitSuccess(c,
   582  			newHook,
   583  			hook.Info{Kind: hooks.ConfigChanged},
   584  			operation.State{},
   585  			operation.State{
   586  				Kind: operation.RunHook,
   587  				Step: operation.Queued,
   588  				Hook: &hook.Info{Kind: hooks.Start},
   589  			},
   590  		)
   591  	}
   592  }
   593  
   594  func (s *RunHookSuite) TestCommitSuccess_ConfigChanged_Preserve(c *gc.C) {
   595  	for i, newHook := range []newHook{
   596  		(operation.Factory).NewRunHook,
   597  		(operation.Factory).NewRetryHook,
   598  		(operation.Factory).NewSkipHook,
   599  	} {
   600  		c.Logf("variant %d", i)
   601  		s.testCommitSuccess(c,
   602  			newHook,
   603  			hook.Info{Kind: hooks.ConfigChanged},
   604  			overwriteState,
   605  			operation.State{
   606  				Started:            true,
   607  				CollectMetricsTime: 1234567,
   608  				UpdateStatusTime:   1234567,
   609  				Kind:               operation.Continue,
   610  				Step:               operation.Pending,
   611  			},
   612  		)
   613  	}
   614  }
   615  
   616  func (s *RunHookSuite) TestCommitSuccess_Start_SetStarted(c *gc.C) {
   617  	for i, newHook := range []newHook{
   618  		(operation.Factory).NewRunHook,
   619  		(operation.Factory).NewRetryHook,
   620  		(operation.Factory).NewSkipHook,
   621  	} {
   622  		c.Logf("variant %d", i)
   623  		s.testCommitSuccess(c,
   624  			newHook,
   625  			hook.Info{Kind: hooks.Start},
   626  			operation.State{},
   627  			operation.State{
   628  				Started: true,
   629  				Kind:    operation.Continue,
   630  				Step:    operation.Pending,
   631  			},
   632  		)
   633  	}
   634  }
   635  
   636  func (s *RunHookSuite) TestCommitSuccess_Start_Preserve(c *gc.C) {
   637  	for i, newHook := range []newHook{
   638  		(operation.Factory).NewRunHook,
   639  		(operation.Factory).NewRetryHook,
   640  		(operation.Factory).NewSkipHook,
   641  	} {
   642  		c.Logf("variant %d", i)
   643  		s.testCommitSuccess(c,
   644  			newHook,
   645  			hook.Info{Kind: hooks.Start},
   646  			overwriteState,
   647  			operation.State{
   648  				Started:            true,
   649  				CollectMetricsTime: 1234567,
   650  				UpdateStatusTime:   1234567,
   651  				Kind:               operation.Continue,
   652  				Step:               operation.Pending,
   653  			},
   654  		)
   655  	}
   656  }
   657  
   658  func (s *RunHookSuite) testQueueHook_BlankSlate(c *gc.C, cause hooks.Kind) {
   659  	for i, newHook := range []newHook{
   660  		(operation.Factory).NewRunHook,
   661  		(operation.Factory).NewRetryHook,
   662  		(operation.Factory).NewSkipHook,
   663  	} {
   664  		c.Logf("variant %d", i)
   665  		var hi *hook.Info
   666  		switch cause {
   667  		case hooks.UpgradeCharm:
   668  			hi = &hook.Info{Kind: hooks.ConfigChanged}
   669  		default:
   670  			hi = nil
   671  		}
   672  		s.testCommitSuccess(c,
   673  			newHook,
   674  			hook.Info{Kind: cause},
   675  			operation.State{},
   676  			operation.State{
   677  				Kind:    operation.RunHook,
   678  				Step:    operation.Queued,
   679  				Stopped: cause == hooks.Stop,
   680  				Hook:    hi,
   681  			},
   682  		)
   683  	}
   684  }
   685  
   686  func (s *RunHookSuite) testQueueHook_Preserve(c *gc.C, cause hooks.Kind) {
   687  	for i, newHook := range []newHook{
   688  		(operation.Factory).NewRunHook,
   689  		(operation.Factory).NewRetryHook,
   690  		(operation.Factory).NewSkipHook,
   691  	} {
   692  		c.Logf("variant %d", i)
   693  		var hi *hook.Info
   694  		switch cause {
   695  		case hooks.UpgradeCharm:
   696  			hi = &hook.Info{Kind: hooks.ConfigChanged}
   697  		default:
   698  			hi = nil
   699  		}
   700  		s.testCommitSuccess(c,
   701  			newHook,
   702  			hook.Info{Kind: cause},
   703  			overwriteState,
   704  			operation.State{
   705  				Kind:               operation.RunHook,
   706  				Step:               operation.Queued,
   707  				Started:            true,
   708  				Stopped:            cause == hooks.Stop,
   709  				Hook:               hi,
   710  				CollectMetricsTime: 1234567,
   711  				UpdateStatusTime:   1234567,
   712  			},
   713  		)
   714  	}
   715  }
   716  
   717  func (s *RunHookSuite) TestQueueHook_UpgradeCharm_BlankSlate(c *gc.C) {
   718  	s.testQueueHook_BlankSlate(c, hooks.UpgradeCharm)
   719  }
   720  
   721  func (s *RunHookSuite) TestQueueHook_UpgradeCharm_Preserve(c *gc.C) {
   722  	s.testQueueHook_Preserve(c, hooks.UpgradeCharm)
   723  }
   724  
   725  func (s *RunHookSuite) testQueueNothing_BlankSlate(c *gc.C, hookInfo hook.Info) {
   726  	for i, newHook := range []newHook{
   727  		(operation.Factory).NewRunHook,
   728  		(operation.Factory).NewRetryHook,
   729  		(operation.Factory).NewSkipHook,
   730  	} {
   731  		c.Logf("variant %d", i)
   732  		s.testCommitSuccess(c,
   733  			newHook,
   734  			hookInfo,
   735  			operation.State{},
   736  			operation.State{
   737  				Kind:    operation.Continue,
   738  				Step:    operation.Pending,
   739  				Stopped: hookInfo.Kind == hooks.Stop,
   740  			},
   741  		)
   742  	}
   743  }
   744  
   745  func (s *RunHookSuite) testQueueNothing_Preserve(c *gc.C, hookInfo hook.Info) {
   746  	for i, newHook := range []newHook{
   747  		(operation.Factory).NewRunHook,
   748  		(operation.Factory).NewRetryHook,
   749  		(operation.Factory).NewSkipHook,
   750  	} {
   751  		c.Logf("variant %d", i)
   752  		s.testCommitSuccess(c,
   753  			newHook,
   754  			hookInfo,
   755  			overwriteState,
   756  			operation.State{
   757  				Kind:               operation.Continue,
   758  				Step:               operation.Pending,
   759  				Started:            true,
   760  				Stopped:            hookInfo.Kind == hooks.Stop,
   761  				CollectMetricsTime: 1234567,
   762  				UpdateStatusTime:   1234567,
   763  			},
   764  		)
   765  	}
   766  }
   767  
   768  func (s *RunHookSuite) TestQueueNothing_Install_BlankSlate(c *gc.C) {
   769  	s.testQueueNothing_BlankSlate(c, hook.Info{
   770  		Kind: hooks.Install,
   771  	})
   772  }
   773  
   774  func (s *RunHookSuite) TestQueueNothing_Install_Preserve(c *gc.C) {
   775  	s.testQueueNothing_Preserve(c, hook.Info{
   776  		Kind: hooks.Install,
   777  	})
   778  }
   779  
   780  func (s *RunHookSuite) TestQueueNothing_Stop_BlankSlate(c *gc.C) {
   781  	s.testQueueNothing_BlankSlate(c, hook.Info{
   782  		Kind: hooks.Stop,
   783  	})
   784  }
   785  
   786  func (s *RunHookSuite) TestQueueNothing_Stop_Preserve(c *gc.C) {
   787  	s.testQueueNothing_Preserve(c, hook.Info{
   788  		Kind: hooks.Stop,
   789  	})
   790  }
   791  
   792  func (s *RunHookSuite) TestQueueNothing_RelationJoined_BlankSlate(c *gc.C) {
   793  	s.testQueueNothing_BlankSlate(c, hook.Info{
   794  		Kind:       hooks.RelationJoined,
   795  		RemoteUnit: "u/0",
   796  	})
   797  }
   798  
   799  func (s *RunHookSuite) TestQueueNothing_RelationJoined_Preserve(c *gc.C) {
   800  	s.testQueueNothing_Preserve(c, hook.Info{
   801  		Kind:       hooks.RelationJoined,
   802  		RemoteUnit: "u/0",
   803  	})
   804  }
   805  
   806  func (s *RunHookSuite) TestQueueNothing_RelationChanged_BlankSlate(c *gc.C) {
   807  	s.testQueueNothing_BlankSlate(c, hook.Info{
   808  		Kind:       hooks.RelationChanged,
   809  		RemoteUnit: "u/0",
   810  	})
   811  }
   812  
   813  func (s *RunHookSuite) TestQueueNothing_RelationChanged_Preserve(c *gc.C) {
   814  	s.testQueueNothing_Preserve(c, hook.Info{
   815  		Kind:       hooks.RelationChanged,
   816  		RemoteUnit: "u/0",
   817  	})
   818  }
   819  
   820  func (s *RunHookSuite) TestQueueNothing_RelationDeparted_BlankSlate(c *gc.C) {
   821  	s.testQueueNothing_BlankSlate(c, hook.Info{
   822  		Kind:       hooks.RelationDeparted,
   823  		RemoteUnit: "u/0",
   824  	})
   825  }
   826  
   827  func (s *RunHookSuite) TestQueueNothing_RelationDeparted_Preserve(c *gc.C) {
   828  	s.testQueueNothing_Preserve(c, hook.Info{
   829  		Kind:       hooks.RelationDeparted,
   830  		RemoteUnit: "u/0",
   831  	})
   832  }
   833  
   834  func (s *RunHookSuite) TestQueueNothing_RelationBroken_BlankSlate(c *gc.C) {
   835  	s.testQueueNothing_BlankSlate(c, hook.Info{
   836  		Kind: hooks.RelationBroken,
   837  	})
   838  }
   839  
   840  func (s *RunHookSuite) TestQueueNothing_RelationBroken_Preserve(c *gc.C) {
   841  	s.testQueueNothing_Preserve(c, hook.Info{
   842  		Kind: hooks.RelationBroken,
   843  	})
   844  }
   845  
   846  func (s *RunHookSuite) testCommitSuccess_UpdateStatusTime(c *gc.C, newHook newHook) {
   847  	callbacks := &CommitHookCallbacks{
   848  		MockCommitHook: &MockCommitHook{},
   849  	}
   850  	factory := operation.NewFactory(operation.FactoryParams{
   851  		Callbacks: callbacks,
   852  	})
   853  	op, err := newHook(factory, hook.Info{Kind: hooks.UpdateStatus})
   854  	c.Assert(err, jc.ErrorIsNil)
   855  
   856  	nowBefore := time.Now().Unix()
   857  	newState, err := op.Commit(overwriteState)
   858  	c.Assert(err, jc.ErrorIsNil)
   859  
   860  	nowAfter := time.Now().Unix()
   861  	nowWritten := newState.UpdateStatusTime
   862  	c.Logf("%d <= %d <= %d", nowBefore, nowWritten, nowAfter)
   863  	c.Check(nowBefore <= nowWritten, jc.IsTrue)
   864  	c.Check(nowWritten <= nowAfter, jc.IsTrue)
   865  
   866  	// Check the other fields match.
   867  	newState.UpdateStatusTime = 0
   868  	c.Check(newState, gc.DeepEquals, &operation.State{
   869  		Started:            true,
   870  		Kind:               operation.Continue,
   871  		Step:               operation.Pending,
   872  		CollectMetricsTime: 1234567,
   873  	})
   874  }
   875  
   876  func (s *RunHookSuite) TestCommitSuccess_UpdateStatusTime_Run(c *gc.C) {
   877  	s.testCommitSuccess_UpdateStatusTime(c, (operation.Factory).NewRunHook)
   878  }
   879  
   880  func (s *RunHookSuite) TestCommitSuccess_UpdateStatusTime_Retry(c *gc.C) {
   881  	s.testCommitSuccess_UpdateStatusTime(c, (operation.Factory).NewRetryHook)
   882  }
   883  
   884  func (s *RunHookSuite) TestCommitSuccess_UpdateStatusTime_Skip(c *gc.C) {
   885  	s.testCommitSuccess_UpdateStatusTime(c, (operation.Factory).NewSkipHook)
   886  }
   887  
   888  func (s *RunHookSuite) testCommitSuccess_CollectMetricsTime(c *gc.C, newHook newHook) {
   889  	callbacks := &CommitHookCallbacks{
   890  		MockCommitHook: &MockCommitHook{},
   891  	}
   892  	factory := operation.NewFactory(operation.FactoryParams{
   893  		Callbacks: callbacks,
   894  	})
   895  	op, err := newHook(factory, hook.Info{Kind: hooks.CollectMetrics})
   896  	c.Assert(err, jc.ErrorIsNil)
   897  
   898  	nowBefore := time.Now().Unix()
   899  	newState, err := op.Commit(overwriteState)
   900  	c.Assert(err, jc.ErrorIsNil)
   901  
   902  	nowAfter := time.Now().Unix()
   903  	nowWritten := newState.CollectMetricsTime
   904  	c.Logf("%d <= %d <= %d", nowBefore, nowWritten, nowAfter)
   905  	c.Check(nowBefore <= nowWritten, jc.IsTrue)
   906  	c.Check(nowWritten <= nowAfter, jc.IsTrue)
   907  
   908  	// Check the other fields match.
   909  	newState.CollectMetricsTime = 0
   910  	c.Check(newState, gc.DeepEquals, &operation.State{
   911  		Started:          true,
   912  		Kind:             operation.Continue,
   913  		Step:             operation.Pending,
   914  		UpdateStatusTime: 1234567,
   915  	})
   916  }
   917  
   918  func (s *RunHookSuite) TestCommitSuccess_CollectMetricsTime_Run(c *gc.C) {
   919  	s.testCommitSuccess_CollectMetricsTime(c, (operation.Factory).NewRunHook)
   920  }
   921  
   922  func (s *RunHookSuite) TestCommitSuccess_CollectMetricsTime_Retry(c *gc.C) {
   923  	s.testCommitSuccess_CollectMetricsTime(c, (operation.Factory).NewRetryHook)
   924  }
   925  
   926  func (s *RunHookSuite) TestCommitSuccess_CollectMetricsTime_Skip(c *gc.C) {
   927  	s.testCommitSuccess_CollectMetricsTime(c, (operation.Factory).NewSkipHook)
   928  }
   929  
   930  func (s *RunHookSuite) testNeedsGlobalMachineLock(c *gc.C, newHook newHook, expected bool) {
   931  	factory := operation.NewFactory(operation.FactoryParams{})
   932  	op, err := newHook(factory, hook.Info{Kind: hooks.ConfigChanged})
   933  	c.Assert(err, jc.ErrorIsNil)
   934  	c.Assert(op.NeedsGlobalMachineLock(), gc.Equals, expected)
   935  }
   936  
   937  func (s *RunHookSuite) TestNeedsGlobalMachineLock_Run(c *gc.C) {
   938  	s.testNeedsGlobalMachineLock(c, (operation.Factory).NewRunHook, true)
   939  }
   940  
   941  func (s *RunHookSuite) TestNeedsGlobalMachineLock_Retry(c *gc.C) {
   942  	s.testNeedsGlobalMachineLock(c, (operation.Factory).NewRetryHook, true)
   943  }
   944  
   945  func (s *RunHookSuite) TestNeedsGlobalMachineLock_Skip(c *gc.C) {
   946  	s.testNeedsGlobalMachineLock(c, (operation.Factory).NewSkipHook, false)
   947  }