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