github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/operation/executor_test.go (about)

     1  // Copyright 2014 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/charm/v12/hooks"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"go.uber.org/mock/gomock"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/yaml.v2"
    17  
    18  	"github.com/juju/juju/rpc/params"
    19  	"github.com/juju/juju/worker/uniter/hook"
    20  	"github.com/juju/juju/worker/uniter/operation"
    21  	"github.com/juju/juju/worker/uniter/operation/mocks"
    22  	"github.com/juju/juju/worker/uniter/remotestate"
    23  )
    24  
    25  type NewExecutorSuite struct {
    26  	testing.IsolationSuite
    27  
    28  	mockStateRW *mocks.MockUnitStateReadWriter
    29  }
    30  
    31  var _ = gc.Suite(&NewExecutorSuite{})
    32  
    33  func failAcquireLock(_, _ string) (func(), error) {
    34  	return nil, errors.New("wat")
    35  }
    36  
    37  func (s *NewExecutorSuite) SetUpTest(c *gc.C) {
    38  	s.IsolationSuite.SetUpTest(c)
    39  }
    40  
    41  func (s *NewExecutorSuite) setupMocks(c *gc.C) *gomock.Controller {
    42  	ctlr := gomock.NewController(c)
    43  	s.mockStateRW = mocks.NewMockUnitStateReadWriter(ctlr)
    44  	return ctlr
    45  }
    46  
    47  func (s *NewExecutorSuite) expectState(c *gc.C, st operation.State) {
    48  	data, err := yaml.Marshal(st)
    49  	c.Assert(err, jc.ErrorIsNil)
    50  	stStr := string(data)
    51  
    52  	mExp := s.mockStateRW.EXPECT()
    53  	mExp.State().Return(params.UnitStateResult{UniterState: stStr}, nil)
    54  }
    55  
    56  func (s *NewExecutorSuite) expectStateNil() {
    57  	mExp := s.mockStateRW.EXPECT()
    58  	mExp.State().Return(params.UnitStateResult{}, nil)
    59  }
    60  
    61  func (s *NewExecutorSuite) TestNewExecutorInvalidStateRead(c *gc.C) {
    62  	defer s.setupMocks(c).Finish()
    63  	initialState := operation.State{Step: operation.Queued}
    64  	s.expectState(c, initialState)
    65  	cfg := operation.ExecutorConfig{
    66  		StateReadWriter: s.mockStateRW,
    67  		InitialState:    initialState,
    68  		AcquireLock:     failAcquireLock,
    69  		Logger:          loggo.GetLogger("test"),
    70  	}
    71  	executor, err := operation.NewExecutor("test", cfg)
    72  	c.Assert(executor, gc.IsNil)
    73  	c.Assert(err, gc.ErrorMatches, `validation of uniter state: invalid operation state: .*`)
    74  }
    75  
    76  func (s *NewExecutorSuite) TestNewExecutorNoInitialState(c *gc.C) {
    77  	defer s.setupMocks(c).Finish()
    78  	s.expectStateNil()
    79  	initialState := operation.State{Step: operation.Queued}
    80  	cfg := operation.ExecutorConfig{
    81  		StateReadWriter: s.mockStateRW,
    82  		InitialState:    initialState,
    83  		AcquireLock:     failAcquireLock,
    84  		Logger:          loggo.GetLogger("test"),
    85  	}
    86  	executor, err := operation.NewExecutor("test", cfg)
    87  	c.Assert(err, jc.ErrorIsNil)
    88  	c.Assert(executor.State(), gc.DeepEquals, initialState)
    89  }
    90  
    91  func (s *NewExecutorSuite) TestNewExecutorValidFile(c *gc.C) {
    92  	// note: this content matches valid persistent state as of 1.21; we expect
    93  	// that "hook" will have to become "last-hook" to enable action execution
    94  	// during hook error states. If you do this, please leave at least one test
    95  	// with this form of the yaml in place.
    96  	defer s.setupMocks(c).Finish()
    97  	s.mockStateRW.EXPECT().State().Return(params.UnitStateResult{UniterState: "started: true\nop: continue\nopstep: pending\n"}, nil)
    98  	cfg := operation.ExecutorConfig{
    99  		StateReadWriter: s.mockStateRW,
   100  		InitialState:    operation.State{Step: operation.Queued},
   101  		AcquireLock:     failAcquireLock,
   102  		Logger:          loggo.GetLogger("test"),
   103  	}
   104  	executor, err := operation.NewExecutor("test", cfg)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	c.Assert(executor.State(), gc.DeepEquals, operation.State{
   107  		Kind:    operation.Continue,
   108  		Step:    operation.Pending,
   109  		Started: true,
   110  	})
   111  }
   112  
   113  type ExecutorSuite struct {
   114  	testing.IsolationSuite
   115  	mockStateRW *mocks.MockUnitStateReadWriter
   116  }
   117  
   118  var _ = gc.Suite(&ExecutorSuite{})
   119  
   120  func (s *ExecutorSuite) setupMocks(c *gc.C) *gomock.Controller {
   121  	ctlr := gomock.NewController(c)
   122  	s.mockStateRW = mocks.NewMockUnitStateReadWriter(ctlr)
   123  	return ctlr
   124  }
   125  
   126  func (s *ExecutorSuite) expectSetState(c *gc.C, st operation.State) {
   127  	data, err := yaml.Marshal(st)
   128  	c.Assert(err, jc.ErrorIsNil)
   129  	strUniterState := string(data)
   130  
   131  	mExp := s.mockStateRW.EXPECT()
   132  	mExp.SetState(unitStateMatcher{c: c, expected: strUniterState}).Return(nil)
   133  }
   134  
   135  func (s *ExecutorSuite) expectState(c *gc.C, st operation.State) {
   136  	data, err := yaml.Marshal(st)
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	strState := string(data)
   139  
   140  	mExp := s.mockStateRW.EXPECT()
   141  	mExp.State().Return(params.UnitStateResult{UniterState: strState}, nil)
   142  }
   143  
   144  func (s *ExecutorSuite) expectConfigChangedPendingOp(c *gc.C) operation.State {
   145  	op := operation.State{
   146  		Kind: operation.RunHook,
   147  		Step: operation.Pending,
   148  		Hook: &hook.Info{Kind: hooks.ConfigChanged},
   149  	}
   150  	s.expectSetState(c, op)
   151  	return op
   152  }
   153  
   154  func (s *ExecutorSuite) expectConfigChangedDoneOp(c *gc.C) operation.State {
   155  	op := operation.State{
   156  		Kind: operation.RunHook,
   157  		Step: operation.Done,
   158  		Hook: &hook.Info{Kind: hooks.ConfigChanged},
   159  	}
   160  	s.expectSetState(c, op)
   161  	return op
   162  }
   163  
   164  func (s *ExecutorSuite) expectStartQueuedOp(c *gc.C) operation.State {
   165  	op := operation.State{
   166  		Kind: operation.RunHook,
   167  		Step: operation.Queued,
   168  		Hook: &hook.Info{Kind: hooks.Start},
   169  	}
   170  	s.expectSetState(c, op)
   171  	return op
   172  }
   173  
   174  func (s *ExecutorSuite) expectStartPendingOp(c *gc.C) operation.State {
   175  	op := operation.State{
   176  		Kind: operation.RunHook,
   177  		Step: operation.Pending,
   178  		Hook: &hook.Info{Kind: hooks.Start},
   179  	}
   180  	s.expectSetState(c, op)
   181  	return op
   182  }
   183  
   184  func (s *ExecutorSuite) newExecutor(c *gc.C, st *operation.State) operation.Executor {
   185  	// ensure s.setupMocks called first.
   186  	c.Assert(s.mockStateRW, gc.NotNil)
   187  
   188  	s.expectState(c, *st)
   189  	cfg := operation.ExecutorConfig{
   190  		StateReadWriter: s.mockStateRW,
   191  		InitialState:    operation.State{Step: operation.Queued},
   192  		AcquireLock:     failAcquireLock,
   193  		Logger:          loggo.GetLogger("test"),
   194  	}
   195  	executor, err := operation.NewExecutor("test", cfg)
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	return executor
   198  }
   199  
   200  func justInstalledState() operation.State {
   201  	return operation.State{
   202  		Kind: operation.Continue,
   203  		Step: operation.Pending,
   204  	}
   205  }
   206  
   207  func (s *ExecutorSuite) TestSucceedNoStateChanges(c *gc.C) {
   208  	defer s.setupMocks(c).Finish()
   209  	initialState := justInstalledState()
   210  	executor := s.newExecutor(c, &initialState)
   211  
   212  	prepare := newStep(nil, nil)
   213  	execute := newStep(nil, nil)
   214  	commit := newStep(nil, nil)
   215  	op := &mockOperation{
   216  		prepare: prepare,
   217  		execute: execute,
   218  		commit:  commit,
   219  	}
   220  
   221  	err := executor.Run(op, nil)
   222  	c.Assert(err, jc.ErrorIsNil)
   223  
   224  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   225  	c.Assert(execute.gotState, gc.DeepEquals, initialState)
   226  	c.Assert(commit.gotState, gc.DeepEquals, initialState)
   227  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   228  }
   229  
   230  func (s *ExecutorSuite) TestSucceedWithStateChanges(c *gc.C) {
   231  	defer s.setupMocks(c).Finish()
   232  
   233  	prepareOp := s.expectConfigChangedPendingOp(c)
   234  	executeOp := s.expectConfigChangedDoneOp(c)
   235  	commitOp := s.expectStartQueuedOp(c)
   236  
   237  	initialState := justInstalledState()
   238  	executor := s.newExecutor(c, &initialState)
   239  
   240  	prepare := newStep(&prepareOp, nil)
   241  	execute := newStep(&executeOp, nil)
   242  	commit := newStep(&commitOp, nil)
   243  	op := &mockOperation{
   244  		prepare: prepare,
   245  		execute: execute,
   246  		commit:  commit,
   247  	}
   248  
   249  	err := executor.Run(op, nil)
   250  	c.Assert(err, jc.ErrorIsNil)
   251  
   252  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   253  	c.Assert(execute.gotState, gc.DeepEquals, *prepare.newState)
   254  	c.Assert(commit.gotState, gc.DeepEquals, *execute.newState)
   255  	c.Assert(executor.State(), gc.DeepEquals, *commit.newState)
   256  }
   257  
   258  func (s *ExecutorSuite) TestSucceedWithRemoteStateChanges(c *gc.C) {
   259  	defer s.setupMocks(c).Finish()
   260  
   261  	initialState := justInstalledState()
   262  	executor := s.newExecutor(c, &initialState)
   263  
   264  	remoteStateUpdated := make(chan struct{}, 1)
   265  	prepare := newStep(nil, nil)
   266  	execute := mockStepFunc(func(state operation.State) (*operation.State, error) {
   267  		select {
   268  		case <-remoteStateUpdated:
   269  			return nil, nil
   270  		case <-time.After(testing.ShortWait):
   271  			c.Fatal("remote state wasn't updated")
   272  			return nil, nil
   273  		}
   274  	})
   275  	commit := newStep(nil, nil)
   276  	op := &mockOperation{
   277  		prepare: prepare,
   278  		execute: execute,
   279  		commit:  commit,
   280  		remoteStateFunc: func(snapshot remotestate.Snapshot) {
   281  			c.Assert(snapshot, gc.DeepEquals, remotestate.Snapshot{
   282  				ConfigHash: "test",
   283  			})
   284  			remoteStateUpdated <- struct{}{}
   285  		},
   286  	}
   287  
   288  	rs := make(chan remotestate.Snapshot, 1)
   289  	rs <- remotestate.Snapshot{
   290  		ConfigHash: "test",
   291  	}
   292  	err := executor.Run(op, rs)
   293  	c.Assert(err, jc.ErrorIsNil)
   294  }
   295  
   296  func (s *ExecutorSuite) TestErrSkipExecute(c *gc.C) {
   297  	defer s.setupMocks(c).Finish()
   298  
   299  	prepareOp := s.expectConfigChangedPendingOp(c)
   300  	commitOp := s.expectStartQueuedOp(c)
   301  
   302  	initialState := justInstalledState()
   303  	executor := s.newExecutor(c, &initialState)
   304  
   305  	prepare := newStep(&prepareOp, operation.ErrSkipExecute)
   306  	commit := newStep(&commitOp, nil)
   307  	op := &mockOperation{
   308  		prepare: prepare,
   309  		commit:  commit,
   310  	}
   311  
   312  	err := executor.Run(op, nil)
   313  	c.Assert(err, jc.ErrorIsNil)
   314  
   315  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   316  	c.Assert(commit.gotState, gc.DeepEquals, *prepare.newState)
   317  	c.Assert(executor.State(), gc.DeepEquals, *commit.newState)
   318  }
   319  
   320  func (s *ExecutorSuite) TestValidateStateChange(c *gc.C) {
   321  	defer s.setupMocks(c).Finish()
   322  
   323  	initialState := justInstalledState()
   324  	executor := s.newExecutor(c, &initialState)
   325  
   326  	prepare := newStep(&operation.State{
   327  		Kind: operation.RunHook,
   328  		Step: operation.Pending,
   329  	}, nil)
   330  	op := &mockOperation{
   331  		prepare: prepare,
   332  	}
   333  
   334  	err := executor.Run(op, nil)
   335  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation" for test: invalid operation state: missing hook info with Kind RunHook`)
   336  	c.Assert(errors.Cause(err), gc.ErrorMatches, "missing hook info with Kind RunHook")
   337  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   338  }
   339  
   340  func (s *ExecutorSuite) TestFailPrepareNoStateChange(c *gc.C) {
   341  	defer s.setupMocks(c).Finish()
   342  
   343  	initialState := justInstalledState()
   344  	executor := s.newExecutor(c, &initialState)
   345  
   346  	prepare := newStep(nil, errors.New("pow"))
   347  	op := &mockOperation{
   348  		prepare: prepare,
   349  	}
   350  
   351  	err := executor.Run(op, nil)
   352  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation" for test: pow`)
   353  	c.Assert(errors.Cause(err), gc.ErrorMatches, "pow")
   354  
   355  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   356  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   357  }
   358  
   359  func (s *ExecutorSuite) TestFailPrepareWithStateChange(c *gc.C) {
   360  	defer s.setupMocks(c).Finish()
   361  	prepareOp := s.expectStartPendingOp(c)
   362  
   363  	initialState := justInstalledState()
   364  	executor := s.newExecutor(c, &initialState)
   365  
   366  	prepare := newStep(&prepareOp, errors.New("blam"))
   367  	op := &mockOperation{
   368  		prepare: prepare,
   369  	}
   370  
   371  	err := executor.Run(op, nil)
   372  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation" for test: blam`)
   373  	c.Assert(errors.Cause(err), gc.ErrorMatches, "blam")
   374  
   375  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   376  	c.Assert(executor.State(), gc.DeepEquals, *prepare.newState)
   377  }
   378  
   379  func (s *ExecutorSuite) TestFailExecuteNoStateChange(c *gc.C) {
   380  	defer s.setupMocks(c).Finish()
   381  
   382  	initialState := justInstalledState()
   383  	executor := s.newExecutor(c, &initialState)
   384  
   385  	prepare := newStep(nil, nil)
   386  	execute := newStep(nil, errors.New("splat"))
   387  	op := &mockOperation{
   388  		prepare: prepare,
   389  		execute: execute,
   390  	}
   391  
   392  	err := executor.Run(op, nil)
   393  	c.Assert(err, gc.ErrorMatches, `executing operation "mock operation" for test: splat`)
   394  	c.Assert(errors.Cause(err), gc.ErrorMatches, "splat")
   395  
   396  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   397  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   398  }
   399  
   400  func (s *ExecutorSuite) TestFailExecuteWithStateChange(c *gc.C) {
   401  	defer s.setupMocks(c).Finish()
   402  	executeOp := s.expectStartPendingOp(c)
   403  
   404  	initialState := justInstalledState()
   405  	executor := s.newExecutor(c, &initialState)
   406  
   407  	prepare := newStep(nil, nil)
   408  	execute := newStep(&executeOp, errors.New("kerblooie"))
   409  	op := &mockOperation{
   410  		prepare: prepare,
   411  		execute: execute,
   412  	}
   413  
   414  	err := executor.Run(op, nil)
   415  	c.Assert(err, gc.ErrorMatches, `executing operation "mock operation" for test: kerblooie`)
   416  	c.Assert(errors.Cause(err), gc.ErrorMatches, "kerblooie")
   417  
   418  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   419  	c.Assert(executor.State(), gc.DeepEquals, *execute.newState)
   420  }
   421  
   422  func (s *ExecutorSuite) TestFailCommitNoStateChange(c *gc.C) {
   423  	defer s.setupMocks(c).Finish()
   424  
   425  	initialState := justInstalledState()
   426  	executor := s.newExecutor(c, &initialState)
   427  
   428  	prepare := newStep(nil, nil)
   429  	execute := newStep(nil, nil)
   430  	commit := newStep(nil, errors.New("whack"))
   431  	op := &mockOperation{
   432  		prepare: prepare,
   433  		execute: execute,
   434  		commit:  commit,
   435  	}
   436  
   437  	err := executor.Run(op, nil)
   438  	c.Assert(err, gc.ErrorMatches, `committing operation "mock operation" for test: whack`)
   439  	c.Assert(errors.Cause(err), gc.ErrorMatches, "whack")
   440  
   441  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   442  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   443  }
   444  
   445  func (s *ExecutorSuite) TestFailCommitWithStateChange(c *gc.C) {
   446  	defer s.setupMocks(c).Finish()
   447  
   448  	initialState := justInstalledState()
   449  	commitOp := s.expectStartPendingOp(c)
   450  
   451  	executor := s.newExecutor(c, &initialState)
   452  	prepare := newStep(nil, nil)
   453  	execute := newStep(nil, nil)
   454  	commit := newStep(&commitOp, errors.New("take that you bandit"))
   455  	op := &mockOperation{
   456  		prepare: prepare,
   457  		execute: execute,
   458  		commit:  commit,
   459  	}
   460  
   461  	err := executor.Run(op, nil)
   462  	c.Assert(err, gc.ErrorMatches, `committing operation "mock operation" for test: take that you bandit`)
   463  	c.Assert(errors.Cause(err), gc.ErrorMatches, "take that you bandit")
   464  
   465  	c.Assert(prepare.gotState, gc.DeepEquals, initialState)
   466  	c.Assert(executor.State(), gc.DeepEquals, *commit.newState)
   467  }
   468  
   469  func (s *ExecutorSuite) initLockTest(c *gc.C, lockFunc func(string, string) (func(), error)) operation.Executor {
   470  	initialState := justInstalledState()
   471  	err := operation.NewStateOps(s.mockStateRW).Write(&initialState)
   472  	c.Assert(err, jc.ErrorIsNil)
   473  	cfg := operation.ExecutorConfig{
   474  		StateReadWriter: s.mockStateRW,
   475  		InitialState:    operation.State{Step: operation.Queued},
   476  		AcquireLock:     lockFunc,
   477  		Logger:          loggo.GetLogger("test"),
   478  	}
   479  	executor, err := operation.NewExecutor("test", cfg)
   480  	c.Assert(err, jc.ErrorIsNil)
   481  
   482  	return executor
   483  }
   484  
   485  func (s *ExecutorSuite) TestLockSucceedsStepsCalled(c *gc.C) {
   486  	op := &mockOperation{
   487  		needsLock: true,
   488  		prepare:   newStep(nil, nil),
   489  		execute:   newStep(nil, nil),
   490  		commit:    newStep(nil, nil),
   491  	}
   492  
   493  	mockLock := &mockLockFunc{op: op}
   494  	lockFunc := mockLock.newSucceedingLock()
   495  	executor := s.initLockTest(c, lockFunc)
   496  
   497  	err := executor.Run(op, nil)
   498  	c.Assert(err, jc.ErrorIsNil)
   499  
   500  	c.Assert(mockLock.calledLock, jc.IsTrue)
   501  	c.Assert(mockLock.calledUnlock, jc.IsTrue)
   502  	c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue)
   503  
   504  	expectedStepsOnUnlock := []bool{true, true, true}
   505  	c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock)
   506  }
   507  
   508  func (s *ExecutorSuite) TestLockFailsOpsStepsNotCalled(c *gc.C) {
   509  	prepare := newStep(nil, nil)
   510  	execute := newStep(nil, nil)
   511  	commit := newStep(nil, nil)
   512  	op := &mockOperation{
   513  		needsLock: true,
   514  		prepare:   prepare,
   515  		execute:   execute,
   516  		commit:    commit,
   517  	}
   518  
   519  	mockLock := &mockLockFunc{op: op}
   520  	lockFunc := mockLock.newFailingLock()
   521  	executor := s.initLockTest(c, lockFunc)
   522  
   523  	err := executor.Run(op, nil)
   524  	c.Assert(err, gc.ErrorMatches, "could not acquire lock: wat")
   525  
   526  	c.Assert(mockLock.calledLock, jc.IsFalse)
   527  	c.Assert(mockLock.calledUnlock, jc.IsFalse)
   528  	c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue)
   529  
   530  	c.Assert(prepare.called, jc.IsFalse)
   531  	c.Assert(execute.called, jc.IsFalse)
   532  	c.Assert(commit.called, jc.IsFalse)
   533  }
   534  
   535  func (s *ExecutorSuite) testLockUnlocksOnError(c *gc.C, op *mockOperation) (error, *mockLockFunc) {
   536  	mockLock := &mockLockFunc{op: op}
   537  	lockFunc := mockLock.newSucceedingLock()
   538  	executor := s.initLockTest(c, lockFunc)
   539  
   540  	err := executor.Run(op, nil)
   541  
   542  	c.Assert(mockLock.calledLock, jc.IsTrue)
   543  	c.Assert(mockLock.calledUnlock, jc.IsTrue)
   544  	c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue)
   545  
   546  	return err, mockLock
   547  }
   548  
   549  func (s *ExecutorSuite) TestLockUnlocksOnError_Prepare(c *gc.C) {
   550  	op := &mockOperation{
   551  		needsLock: true,
   552  		prepare:   newStep(nil, errors.New("kerblooie")),
   553  		execute:   newStep(nil, nil),
   554  		commit:    newStep(nil, nil),
   555  	}
   556  
   557  	err, mockLock := s.testLockUnlocksOnError(c, op)
   558  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": kerblooie`)
   559  	c.Assert(errors.Cause(err), gc.ErrorMatches, "kerblooie")
   560  
   561  	expectedStepsOnUnlock := []bool{true, false, false}
   562  	c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock)
   563  }
   564  
   565  func (s *ExecutorSuite) TestLockUnlocksOnError_Execute(c *gc.C) {
   566  	op := &mockOperation{
   567  		needsLock: true,
   568  		prepare:   newStep(nil, nil),
   569  		execute:   newStep(nil, errors.New("you asked for it")),
   570  		commit:    newStep(nil, nil),
   571  	}
   572  
   573  	err, mockLock := s.testLockUnlocksOnError(c, op)
   574  	c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": you asked for it`)
   575  	c.Assert(errors.Cause(err), gc.ErrorMatches, "you asked for it")
   576  
   577  	expectedStepsOnUnlock := []bool{true, true, false}
   578  	c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock)
   579  }
   580  
   581  func (s *ExecutorSuite) TestLockUnlocksOnError_Commit(c *gc.C) {
   582  	op := &mockOperation{
   583  		needsLock: true,
   584  		prepare:   newStep(nil, nil),
   585  		execute:   newStep(nil, nil),
   586  		commit:    newStep(nil, errors.New("well, shit")),
   587  	}
   588  
   589  	err, mockLock := s.testLockUnlocksOnError(c, op)
   590  	c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": well, shit`)
   591  	c.Assert(errors.Cause(err), gc.ErrorMatches, "well, shit")
   592  
   593  	expectedStepsOnUnlock := []bool{true, true, true}
   594  	c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock)
   595  }
   596  
   597  type mockLockFunc struct {
   598  	noStepsCalledOnLock bool
   599  	stepsCalledOnUnlock []bool
   600  	calledLock          bool
   601  	calledUnlock        bool
   602  	op                  *mockOperation
   603  }
   604  
   605  func (mock *mockLockFunc) newFailingLock() func(string, string) (func(), error) {
   606  	return func(string, string) (func(), error) {
   607  		mock.noStepsCalledOnLock = mock.op.prepare.(*mockStep).called == false &&
   608  			mock.op.commit.(*mockStep).called == false
   609  		return nil, errors.New("wat")
   610  	}
   611  }
   612  
   613  func (mock *mockLockFunc) newSucceedingLock() func(string, string) (func(), error) {
   614  	return func(string, string) (func(), error) {
   615  		mock.calledLock = true
   616  		// Ensure that when we lock no operation has been called
   617  		mock.noStepsCalledOnLock = mock.op.prepare.(*mockStep).called == false &&
   618  			mock.op.commit.(*mockStep).called == false
   619  		return func() {
   620  			// Record steps called when unlocking
   621  			mock.stepsCalledOnUnlock = []bool{mock.op.prepare.(*mockStep).called,
   622  				mock.op.execute.(*mockStep).called,
   623  				mock.op.commit.(*mockStep).called}
   624  			mock.calledUnlock = true
   625  		}, nil
   626  	}
   627  }
   628  
   629  type mockStepInterface interface {
   630  	Run(state operation.State) (*operation.State, error)
   631  }
   632  
   633  type mockStepFunc func(state operation.State) (*operation.State, error)
   634  
   635  func (m mockStepFunc) Run(state operation.State) (*operation.State, error) {
   636  	return m(state)
   637  }
   638  
   639  type mockStep struct {
   640  	gotState operation.State
   641  	newState *operation.State
   642  	err      error
   643  	called   bool
   644  }
   645  
   646  func newStep(newState *operation.State, err error) *mockStep {
   647  	return &mockStep{newState: newState, err: err}
   648  }
   649  
   650  func (step *mockStep) Run(state operation.State) (*operation.State, error) {
   651  	step.called = true
   652  	step.gotState = state
   653  	return step.newState, step.err
   654  }
   655  
   656  type mockOperation struct {
   657  	needsLock       bool
   658  	prepare         mockStepInterface
   659  	execute         mockStepInterface
   660  	commit          mockStepInterface
   661  	remoteStateFunc func(snapshot remotestate.Snapshot)
   662  }
   663  
   664  func (op *mockOperation) String() string {
   665  	return "mock operation"
   666  }
   667  
   668  func (op *mockOperation) NeedsGlobalMachineLock() bool {
   669  	return op.needsLock
   670  }
   671  
   672  func (m *mockOperation) ExecutionGroup() string {
   673  	return ""
   674  }
   675  
   676  func (op *mockOperation) Prepare(state operation.State) (*operation.State, error) {
   677  	return op.prepare.Run(state)
   678  }
   679  
   680  func (op *mockOperation) Execute(state operation.State) (*operation.State, error) {
   681  	return op.execute.Run(state)
   682  }
   683  
   684  func (op *mockOperation) Commit(state operation.State) (*operation.State, error) {
   685  	return op.commit.Run(state)
   686  }
   687  
   688  func (op *mockOperation) RemoteStateChanged(snapshot remotestate.Snapshot) {
   689  	if op.remoteStateFunc != nil {
   690  		op.remoteStateFunc(snapshot)
   691  	}
   692  }