github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"path/filepath"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	ft "github.com/juju/testing/filetesting"
    13  	gc "gopkg.in/check.v1"
    14  	"gopkg.in/juju/charm.v6/hooks"
    15  
    16  	"github.com/juju/juju/worker/uniter/hook"
    17  	"github.com/juju/juju/worker/uniter/operation"
    18  )
    19  
    20  type NewExecutorSuite struct {
    21  	testing.IsolationSuite
    22  	basePath string
    23  }
    24  
    25  var _ = gc.Suite(&NewExecutorSuite{})
    26  
    27  func failAcquireLock(_ string) (func(), error) {
    28  	return nil, errors.New("wat")
    29  }
    30  
    31  func (s *NewExecutorSuite) SetUpTest(c *gc.C) {
    32  	s.IsolationSuite.SetUpTest(c)
    33  	s.basePath = c.MkDir()
    34  }
    35  
    36  func (s *NewExecutorSuite) path(path string) string {
    37  	return filepath.Join(s.basePath, path)
    38  }
    39  
    40  func (s *NewExecutorSuite) TestNewExecutorInvalidFile(c *gc.C) {
    41  	ft.File{"existing", "", 0666}.Create(c, s.basePath)
    42  	executor, err := operation.NewExecutor(s.path("existing"), operation.State{}, failAcquireLock)
    43  	c.Assert(executor, gc.IsNil)
    44  	c.Assert(err, gc.ErrorMatches, `cannot read ".*": invalid operation state: .*`)
    45  }
    46  
    47  func (s *NewExecutorSuite) TestNewExecutorNoFile(c *gc.C) {
    48  	initialState := operation.State{}
    49  	executor, err := operation.NewExecutor(s.path("missing"), initialState, failAcquireLock)
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	c.Assert(executor.State(), gc.DeepEquals, initialState)
    52  	ft.Removed{"missing"}.Check(c, s.basePath)
    53  }
    54  
    55  func (s *NewExecutorSuite) TestNewExecutorValidFile(c *gc.C) {
    56  	// note: this content matches valid persistent state as of 1.21; we expect
    57  	// that "hook" will have to become "last-hook" to enable action execution
    58  	// during hook error states. If you do this, please leave at least one test
    59  	// with this form of the yaml in place.
    60  	ft.File{"existing", `
    61  started: true
    62  op: continue
    63  opstep: pending
    64  `[1:], 0666}.Create(c, s.basePath)
    65  	executor, err := operation.NewExecutor(s.path("existing"), operation.State{}, failAcquireLock)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	c.Assert(executor.State(), gc.DeepEquals, operation.State{
    68  		Kind:    operation.Continue,
    69  		Step:    operation.Pending,
    70  		Started: true,
    71  	})
    72  }
    73  
    74  type ExecutorSuite struct {
    75  	testing.IsolationSuite
    76  }
    77  
    78  var _ = gc.Suite(&ExecutorSuite{})
    79  
    80  func assertWroteState(c *gc.C, path string, expect operation.State) {
    81  	actual, err := operation.NewStateFile(path).Read()
    82  	c.Assert(err, jc.ErrorIsNil)
    83  	c.Assert(*actual, gc.DeepEquals, expect)
    84  }
    85  
    86  func newExecutor(c *gc.C, st *operation.State) (operation.Executor, string) {
    87  	path := filepath.Join(c.MkDir(), "state")
    88  	err := operation.NewStateFile(path).Write(st)
    89  	c.Assert(err, jc.ErrorIsNil)
    90  	executor, err := operation.NewExecutor(path, operation.State{}, failAcquireLock)
    91  	c.Assert(err, jc.ErrorIsNil)
    92  	return executor, path
    93  }
    94  
    95  func justInstalledState() operation.State {
    96  	return operation.State{
    97  		Kind: operation.Continue,
    98  		Step: operation.Pending,
    99  	}
   100  }
   101  
   102  func (s *ExecutorSuite) TestSucceedNoStateChanges(c *gc.C) {
   103  	initialState := justInstalledState()
   104  	executor, statePath := newExecutor(c, &initialState)
   105  
   106  	op := &mockOperation{
   107  		prepare: newStep(nil, nil),
   108  		execute: newStep(nil, nil),
   109  		commit:  newStep(nil, nil),
   110  	}
   111  
   112  	err := executor.Run(op)
   113  	c.Assert(err, jc.ErrorIsNil)
   114  
   115  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   116  	c.Assert(op.execute.gotState, gc.DeepEquals, initialState)
   117  	c.Assert(op.commit.gotState, gc.DeepEquals, initialState)
   118  	assertWroteState(c, statePath, initialState)
   119  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   120  }
   121  
   122  func (s *ExecutorSuite) TestSucceedWithStateChanges(c *gc.C) {
   123  	initialState := justInstalledState()
   124  	executor, statePath := newExecutor(c, &initialState)
   125  	op := &mockOperation{
   126  		prepare: newStep(&operation.State{
   127  			Kind: operation.RunHook,
   128  			Step: operation.Pending,
   129  			Hook: &hook.Info{Kind: hooks.ConfigChanged},
   130  		}, nil),
   131  		execute: newStep(&operation.State{
   132  			Kind: operation.RunHook,
   133  			Step: operation.Done,
   134  			Hook: &hook.Info{Kind: hooks.ConfigChanged},
   135  		}, nil),
   136  		commit: newStep(&operation.State{
   137  			Kind: operation.RunHook,
   138  			Step: operation.Queued,
   139  			Hook: &hook.Info{Kind: hooks.Start},
   140  		}, nil),
   141  	}
   142  
   143  	err := executor.Run(op)
   144  	c.Assert(err, jc.ErrorIsNil)
   145  
   146  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   147  	c.Assert(op.execute.gotState, gc.DeepEquals, *op.prepare.newState)
   148  	c.Assert(op.commit.gotState, gc.DeepEquals, *op.execute.newState)
   149  	assertWroteState(c, statePath, *op.commit.newState)
   150  	c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState)
   151  }
   152  
   153  func (s *ExecutorSuite) TestErrSkipExecute(c *gc.C) {
   154  	initialState := justInstalledState()
   155  	executor, statePath := newExecutor(c, &initialState)
   156  	op := &mockOperation{
   157  		prepare: newStep(&operation.State{
   158  			Kind: operation.RunHook,
   159  			Step: operation.Pending,
   160  			Hook: &hook.Info{Kind: hooks.ConfigChanged},
   161  		}, operation.ErrSkipExecute),
   162  		commit: newStep(&operation.State{
   163  			Kind: operation.RunHook,
   164  			Step: operation.Queued,
   165  			Hook: &hook.Info{Kind: hooks.Start},
   166  		}, nil),
   167  	}
   168  
   169  	err := executor.Run(op)
   170  	c.Assert(err, jc.ErrorIsNil)
   171  
   172  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   173  	c.Assert(op.commit.gotState, gc.DeepEquals, *op.prepare.newState)
   174  	assertWroteState(c, statePath, *op.commit.newState)
   175  	c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState)
   176  }
   177  
   178  func (s *ExecutorSuite) TestValidateStateChange(c *gc.C) {
   179  	initialState := justInstalledState()
   180  	executor, statePath := newExecutor(c, &initialState)
   181  	op := &mockOperation{
   182  		prepare: newStep(&operation.State{
   183  			Kind: operation.RunHook,
   184  			Step: operation.Pending,
   185  		}, nil),
   186  	}
   187  
   188  	err := executor.Run(op)
   189  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": invalid operation state: missing hook info with Kind RunHook`)
   190  	c.Assert(errors.Cause(err), gc.ErrorMatches, "missing hook info with Kind RunHook")
   191  
   192  	assertWroteState(c, statePath, initialState)
   193  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   194  }
   195  
   196  func (s *ExecutorSuite) TestFailPrepareNoStateChange(c *gc.C) {
   197  	initialState := justInstalledState()
   198  	executor, statePath := newExecutor(c, &initialState)
   199  	op := &mockOperation{
   200  		prepare: newStep(nil, errors.New("pow")),
   201  	}
   202  
   203  	err := executor.Run(op)
   204  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": pow`)
   205  	c.Assert(errors.Cause(err), gc.ErrorMatches, "pow")
   206  
   207  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   208  	assertWroteState(c, statePath, initialState)
   209  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   210  }
   211  
   212  func (s *ExecutorSuite) TestFailPrepareWithStateChange(c *gc.C) {
   213  	initialState := justInstalledState()
   214  	executor, statePath := newExecutor(c, &initialState)
   215  	op := &mockOperation{
   216  		prepare: newStep(&operation.State{
   217  			Kind: operation.RunHook,
   218  			Step: operation.Pending,
   219  			Hook: &hook.Info{Kind: hooks.Start},
   220  		}, errors.New("blam")),
   221  	}
   222  
   223  	err := executor.Run(op)
   224  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": blam`)
   225  	c.Assert(errors.Cause(err), gc.ErrorMatches, "blam")
   226  
   227  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   228  	assertWroteState(c, statePath, *op.prepare.newState)
   229  	c.Assert(executor.State(), gc.DeepEquals, *op.prepare.newState)
   230  }
   231  
   232  func (s *ExecutorSuite) TestFailExecuteNoStateChange(c *gc.C) {
   233  	initialState := justInstalledState()
   234  	executor, statePath := newExecutor(c, &initialState)
   235  	op := &mockOperation{
   236  		prepare: newStep(nil, nil),
   237  		execute: newStep(nil, errors.New("splat")),
   238  	}
   239  
   240  	err := executor.Run(op)
   241  	c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": splat`)
   242  	c.Assert(errors.Cause(err), gc.ErrorMatches, "splat")
   243  
   244  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   245  	assertWroteState(c, statePath, initialState)
   246  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   247  }
   248  
   249  func (s *ExecutorSuite) TestFailExecuteWithStateChange(c *gc.C) {
   250  	initialState := justInstalledState()
   251  	executor, statePath := newExecutor(c, &initialState)
   252  	op := &mockOperation{
   253  		prepare: newStep(nil, nil),
   254  		execute: newStep(&operation.State{
   255  			Kind: operation.RunHook,
   256  			Step: operation.Pending,
   257  			Hook: &hook.Info{Kind: hooks.Start},
   258  		}, errors.New("kerblooie")),
   259  	}
   260  
   261  	err := executor.Run(op)
   262  	c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": kerblooie`)
   263  	c.Assert(errors.Cause(err), gc.ErrorMatches, "kerblooie")
   264  
   265  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   266  	assertWroteState(c, statePath, *op.execute.newState)
   267  	c.Assert(executor.State(), gc.DeepEquals, *op.execute.newState)
   268  }
   269  
   270  func (s *ExecutorSuite) TestFailCommitNoStateChange(c *gc.C) {
   271  	initialState := justInstalledState()
   272  	executor, statePath := newExecutor(c, &initialState)
   273  	op := &mockOperation{
   274  		prepare: newStep(nil, nil),
   275  		execute: newStep(nil, nil),
   276  		commit:  newStep(nil, errors.New("whack")),
   277  	}
   278  
   279  	err := executor.Run(op)
   280  	c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": whack`)
   281  	c.Assert(errors.Cause(err), gc.ErrorMatches, "whack")
   282  
   283  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   284  	assertWroteState(c, statePath, initialState)
   285  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   286  }
   287  
   288  func (s *ExecutorSuite) TestFailCommitWithStateChange(c *gc.C) {
   289  	initialState := justInstalledState()
   290  	executor, statePath := newExecutor(c, &initialState)
   291  	op := &mockOperation{
   292  		prepare: newStep(nil, nil),
   293  		execute: newStep(nil, nil),
   294  		commit: newStep(&operation.State{
   295  			Kind: operation.RunHook,
   296  			Step: operation.Pending,
   297  			Hook: &hook.Info{Kind: hooks.Start},
   298  		}, errors.New("take that you bandit")),
   299  	}
   300  
   301  	err := executor.Run(op)
   302  	c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": take that you bandit`)
   303  	c.Assert(errors.Cause(err), gc.ErrorMatches, "take that you bandit")
   304  
   305  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   306  	assertWroteState(c, statePath, *op.commit.newState)
   307  	c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState)
   308  }
   309  
   310  func (s *ExecutorSuite) initLockTest(c *gc.C, lockFunc func(string) (func(), error)) operation.Executor {
   311  	initialState := justInstalledState()
   312  	statePath := filepath.Join(c.MkDir(), "state")
   313  	err := operation.NewStateFile(statePath).Write(&initialState)
   314  	c.Assert(err, jc.ErrorIsNil)
   315  	executor, err := operation.NewExecutor(statePath, operation.State{}, lockFunc)
   316  	c.Assert(err, jc.ErrorIsNil)
   317  
   318  	return executor
   319  }
   320  
   321  func (s *ExecutorSuite) TestLockSucceedsStepsCalled(c *gc.C) {
   322  	op := &mockOperation{
   323  		needsLock: true,
   324  		prepare:   newStep(nil, nil),
   325  		execute:   newStep(nil, nil),
   326  		commit:    newStep(nil, nil),
   327  	}
   328  
   329  	mockLock := &mockLockFunc{op: op}
   330  	lockFunc := mockLock.newSucceedingLock()
   331  	executor := s.initLockTest(c, lockFunc)
   332  
   333  	err := executor.Run(op)
   334  	c.Assert(err, jc.ErrorIsNil)
   335  
   336  	c.Assert(mockLock.calledLock, jc.IsTrue)
   337  	c.Assert(mockLock.calledUnlock, jc.IsTrue)
   338  	c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue)
   339  
   340  	expectedStepsOnUnlock := []bool{true, true, true}
   341  	c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock)
   342  }
   343  
   344  func (s *ExecutorSuite) TestLockFailsOpsStepsNotCalled(c *gc.C) {
   345  	op := &mockOperation{
   346  		needsLock: true,
   347  		prepare:   newStep(nil, nil),
   348  		execute:   newStep(nil, nil),
   349  		commit:    newStep(nil, nil),
   350  	}
   351  
   352  	mockLock := &mockLockFunc{op: op}
   353  	lockFunc := mockLock.newFailingLock()
   354  	executor := s.initLockTest(c, lockFunc)
   355  
   356  	err := executor.Run(op)
   357  	c.Assert(err, gc.ErrorMatches, "could not acquire lock: wat")
   358  
   359  	c.Assert(mockLock.calledLock, jc.IsFalse)
   360  	c.Assert(mockLock.calledUnlock, jc.IsFalse)
   361  	c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue)
   362  
   363  	c.Assert(op.prepare.called, jc.IsFalse)
   364  	c.Assert(op.execute.called, jc.IsFalse)
   365  	c.Assert(op.commit.called, jc.IsFalse)
   366  }
   367  
   368  func (s *ExecutorSuite) testLockUnlocksOnError(c *gc.C, op *mockOperation) (error, *mockLockFunc) {
   369  	mockLock := &mockLockFunc{op: op}
   370  	lockFunc := mockLock.newSucceedingLock()
   371  	executor := s.initLockTest(c, lockFunc)
   372  
   373  	err := executor.Run(op)
   374  
   375  	c.Assert(mockLock.calledLock, jc.IsTrue)
   376  	c.Assert(mockLock.calledUnlock, jc.IsTrue)
   377  	c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue)
   378  
   379  	return err, mockLock
   380  }
   381  
   382  func (s *ExecutorSuite) TestLockUnlocksOnError_Prepare(c *gc.C) {
   383  	op := &mockOperation{
   384  		needsLock: true,
   385  		prepare:   newStep(nil, errors.New("kerblooie")),
   386  		execute:   newStep(nil, nil),
   387  		commit:    newStep(nil, nil),
   388  	}
   389  
   390  	err, mockLock := s.testLockUnlocksOnError(c, op)
   391  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": kerblooie`)
   392  	c.Assert(errors.Cause(err), gc.ErrorMatches, "kerblooie")
   393  
   394  	expectedStepsOnUnlock := []bool{true, false, false}
   395  	c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock)
   396  }
   397  
   398  func (s *ExecutorSuite) TestLockUnlocksOnError_Execute(c *gc.C) {
   399  	op := &mockOperation{
   400  		needsLock: true,
   401  		prepare:   newStep(nil, nil),
   402  		execute:   newStep(nil, errors.New("you asked for it")),
   403  		commit:    newStep(nil, nil),
   404  	}
   405  
   406  	err, mockLock := s.testLockUnlocksOnError(c, op)
   407  	c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": you asked for it`)
   408  	c.Assert(errors.Cause(err), gc.ErrorMatches, "you asked for it")
   409  
   410  	expectedStepsOnUnlock := []bool{true, true, false}
   411  	c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock)
   412  }
   413  
   414  func (s *ExecutorSuite) TestLockUnlocksOnError_Commit(c *gc.C) {
   415  	op := &mockOperation{
   416  		needsLock: true,
   417  		prepare:   newStep(nil, nil),
   418  		execute:   newStep(nil, nil),
   419  		commit:    newStep(nil, errors.New("well, shit")),
   420  	}
   421  
   422  	err, mockLock := s.testLockUnlocksOnError(c, op)
   423  	c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": well, shit`)
   424  	c.Assert(errors.Cause(err), gc.ErrorMatches, "well, shit")
   425  
   426  	expectedStepsOnUnlock := []bool{true, true, true}
   427  	c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock)
   428  }
   429  
   430  type mockLockFunc struct {
   431  	noStepsCalledOnLock bool
   432  	stepsCalledOnUnlock []bool
   433  	calledLock          bool
   434  	calledUnlock        bool
   435  	op                  *mockOperation
   436  }
   437  
   438  func (mock *mockLockFunc) newFailingLock() func(string) (func(), error) {
   439  	return func(string) (func(), error) {
   440  		mock.noStepsCalledOnLock = mock.op.prepare.called == false &&
   441  			mock.op.commit.called == false
   442  		return nil, errors.New("wat")
   443  	}
   444  }
   445  
   446  func (mock *mockLockFunc) newSucceedingLock() func(string) (func(), error) {
   447  	return func(string) (func(), error) {
   448  		mock.calledLock = true
   449  		// Ensure that when we lock no operation has been called
   450  		mock.noStepsCalledOnLock = mock.op.prepare.called == false &&
   451  			mock.op.commit.called == false
   452  		return func() {
   453  			// Record steps called when unlocking
   454  			mock.stepsCalledOnUnlock = []bool{mock.op.prepare.called,
   455  				mock.op.execute.called,
   456  				mock.op.commit.called}
   457  			mock.calledUnlock = true
   458  		}, nil
   459  	}
   460  }
   461  
   462  type mockStep struct {
   463  	gotState operation.State
   464  	newState *operation.State
   465  	err      error
   466  	called   bool
   467  }
   468  
   469  func newStep(newState *operation.State, err error) *mockStep {
   470  	return &mockStep{newState: newState, err: err}
   471  }
   472  
   473  func (step *mockStep) run(state operation.State) (*operation.State, error) {
   474  	step.called = true
   475  	step.gotState = state
   476  	return step.newState, step.err
   477  }
   478  
   479  type mockOperation struct {
   480  	needsLock bool
   481  	prepare   *mockStep
   482  	execute   *mockStep
   483  	commit    *mockStep
   484  }
   485  
   486  func (op *mockOperation) String() string {
   487  	return "mock operation"
   488  }
   489  
   490  func (op *mockOperation) NeedsGlobalMachineLock() bool {
   491  	return op.needsLock
   492  }
   493  
   494  func (op *mockOperation) Prepare(state operation.State) (*operation.State, error) {
   495  	return op.prepare.run(state)
   496  }
   497  
   498  func (op *mockOperation) Execute(state operation.State) (*operation.State, error) {
   499  	return op.execute.run(state)
   500  }
   501  
   502  func (op *mockOperation) Commit(state operation.State) (*operation.State, error) {
   503  	return op.commit.run(state)
   504  }