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