github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	corecharm "gopkg.in/juju/charm.v4"
    15  	"gopkg.in/juju/charm.v4/hooks"
    16  
    17  	"github.com/juju/juju/worker/uniter/hook"
    18  	"github.com/juju/juju/worker/uniter/operation"
    19  )
    20  
    21  type NewExecutorSuite struct {
    22  	testing.IsolationSuite
    23  	basePath string
    24  }
    25  
    26  var _ = gc.Suite(&NewExecutorSuite{})
    27  
    28  func failGetInstallCharm() (*corecharm.URL, error) {
    29  	return nil, errors.New("lol!")
    30  }
    31  
    32  func (s *NewExecutorSuite) SetUpTest(c *gc.C) {
    33  	s.IsolationSuite.SetUpTest(c)
    34  	s.basePath = c.MkDir()
    35  }
    36  
    37  func (s *NewExecutorSuite) path(path string) string {
    38  	return filepath.Join(s.basePath, path)
    39  }
    40  
    41  func (s *NewExecutorSuite) TestNewExecutorNoFileNoCharm(c *gc.C) {
    42  	executor, err := operation.NewExecutor(s.path("missing"), failGetInstallCharm)
    43  	c.Assert(executor, gc.IsNil)
    44  	c.Assert(err, gc.ErrorMatches, "lol!")
    45  }
    46  
    47  func (s *NewExecutorSuite) TestNewExecutorInvalidFile(c *gc.C) {
    48  	ft.File{"existing", "", 0666}.Create(c, s.basePath)
    49  	executor, err := operation.NewExecutor(s.path("existing"), failGetInstallCharm)
    50  	c.Assert(executor, gc.IsNil)
    51  	c.Assert(err, gc.ErrorMatches, `cannot read ".*": invalid operation state: .*`)
    52  }
    53  
    54  func (s *NewExecutorSuite) TestNewExecutorNoFile(c *gc.C) {
    55  	charmURL := corecharm.MustParseURL("cs:quantal/nyancat-323")
    56  	getInstallCharm := func() (*corecharm.URL, error) {
    57  		return charmURL, nil
    58  	}
    59  	executor, err := operation.NewExecutor(s.path("missing"), getInstallCharm)
    60  	c.Assert(err, jc.ErrorIsNil)
    61  	c.Assert(executor.State(), gc.DeepEquals, operation.State{
    62  		Kind:     operation.Install,
    63  		Step:     operation.Queued,
    64  		CharmURL: charmURL,
    65  	})
    66  	ft.Removed{"missing"}.Check(c, s.basePath)
    67  }
    68  
    69  func (s *NewExecutorSuite) TestNewExecutorValidFile(c *gc.C) {
    70  	// note: this content matches valid persistent state as of 1.21; we expect
    71  	// that "hook" will have to become "last-hook" to enable action execution
    72  	// during hook error states. If you do this, please leave at least one test
    73  	// with this form of the yaml in place.
    74  	ft.File{"existing", `
    75  started: true
    76  op: continue
    77  opstep: pending
    78  hook: {kind: config-changed}
    79  `[1:], 0666}.Create(c, s.basePath)
    80  	executor, err := operation.NewExecutor(s.path("existing"), failGetInstallCharm)
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	c.Assert(executor.State(), gc.DeepEquals, operation.State{
    83  		Kind:    operation.Continue,
    84  		Step:    operation.Pending,
    85  		Started: true,
    86  		Hook:    &hook.Info{Kind: hooks.ConfigChanged},
    87  	})
    88  }
    89  
    90  type ExecutorSuite struct {
    91  	testing.IsolationSuite
    92  }
    93  
    94  var _ = gc.Suite(&ExecutorSuite{})
    95  
    96  func assertWroteState(c *gc.C, path string, expect operation.State) {
    97  	actual, err := operation.NewStateFile(path).Read()
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	c.Assert(*actual, gc.DeepEquals, expect)
   100  }
   101  
   102  func newExecutor(c *gc.C, st *operation.State) (operation.Executor, string) {
   103  	path := filepath.Join(c.MkDir(), "state")
   104  	err := operation.NewStateFile(path).Write(st)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	executor, err := operation.NewExecutor(path, failGetInstallCharm)
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	return executor, path
   109  }
   110  
   111  func justInstalledState() operation.State {
   112  	return operation.State{
   113  		Kind: operation.Continue,
   114  		Step: operation.Pending,
   115  		Hook: &hook.Info{Kind: hooks.ConfigChanged},
   116  	}
   117  }
   118  
   119  func (s *ExecutorSuite) TestSucceedNoStateChanges(c *gc.C) {
   120  	initialState := justInstalledState()
   121  	executor, statePath := newExecutor(c, &initialState)
   122  	op := &mockOperation{
   123  		prepare: newStep(nil, nil),
   124  		execute: newStep(nil, nil),
   125  		commit:  newStep(nil, nil),
   126  	}
   127  
   128  	err := executor.Run(op)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  
   131  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   132  	c.Assert(op.execute.gotState, gc.DeepEquals, initialState)
   133  	c.Assert(op.commit.gotState, gc.DeepEquals, initialState)
   134  	assertWroteState(c, statePath, initialState)
   135  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   136  }
   137  
   138  func (s *ExecutorSuite) TestSucceedWithStateChanges(c *gc.C) {
   139  	initialState := justInstalledState()
   140  	executor, statePath := newExecutor(c, &initialState)
   141  	op := &mockOperation{
   142  		prepare: newStep(&operation.State{
   143  			Kind: operation.RunHook,
   144  			Step: operation.Pending,
   145  			Hook: &hook.Info{Kind: hooks.ConfigChanged},
   146  		}, nil),
   147  		execute: newStep(&operation.State{
   148  			Kind: operation.RunHook,
   149  			Step: operation.Done,
   150  			Hook: &hook.Info{Kind: hooks.ConfigChanged},
   151  		}, nil),
   152  		commit: newStep(&operation.State{
   153  			Kind: operation.RunHook,
   154  			Step: operation.Queued,
   155  			Hook: &hook.Info{Kind: hooks.Start},
   156  		}, nil),
   157  	}
   158  
   159  	err := executor.Run(op)
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   163  	c.Assert(op.execute.gotState, gc.DeepEquals, *op.prepare.newState)
   164  	c.Assert(op.commit.gotState, gc.DeepEquals, *op.execute.newState)
   165  	assertWroteState(c, statePath, *op.commit.newState)
   166  	c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState)
   167  }
   168  
   169  func (s *ExecutorSuite) TestErrSkipExecute(c *gc.C) {
   170  	initialState := justInstalledState()
   171  	executor, statePath := newExecutor(c, &initialState)
   172  	op := &mockOperation{
   173  		prepare: newStep(&operation.State{
   174  			Kind: operation.RunHook,
   175  			Step: operation.Pending,
   176  			Hook: &hook.Info{Kind: hooks.ConfigChanged},
   177  		}, operation.ErrSkipExecute),
   178  		commit: newStep(&operation.State{
   179  			Kind: operation.RunHook,
   180  			Step: operation.Queued,
   181  			Hook: &hook.Info{Kind: hooks.Start},
   182  		}, nil),
   183  	}
   184  
   185  	err := executor.Run(op)
   186  	c.Assert(err, jc.ErrorIsNil)
   187  
   188  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   189  	c.Assert(op.commit.gotState, gc.DeepEquals, *op.prepare.newState)
   190  	assertWroteState(c, statePath, *op.commit.newState)
   191  	c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState)
   192  }
   193  
   194  func (s *ExecutorSuite) TestValidateStateChange(c *gc.C) {
   195  	initialState := justInstalledState()
   196  	executor, statePath := newExecutor(c, &initialState)
   197  	op := &mockOperation{
   198  		prepare: newStep(&operation.State{
   199  			Kind: operation.RunHook,
   200  			Step: operation.Pending,
   201  		}, nil),
   202  	}
   203  
   204  	err := executor.Run(op)
   205  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": invalid operation state: missing hook info`)
   206  	c.Assert(errors.Cause(err), gc.ErrorMatches, "missing hook info")
   207  
   208  	assertWroteState(c, statePath, initialState)
   209  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   210  }
   211  
   212  func (s *ExecutorSuite) TestFailPrepareNoStateChange(c *gc.C) {
   213  	initialState := justInstalledState()
   214  	executor, statePath := newExecutor(c, &initialState)
   215  	op := &mockOperation{
   216  		prepare: newStep(nil, errors.New("pow")),
   217  	}
   218  
   219  	err := executor.Run(op)
   220  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": pow`)
   221  	c.Assert(errors.Cause(err), gc.ErrorMatches, "pow")
   222  
   223  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   224  	assertWroteState(c, statePath, initialState)
   225  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   226  }
   227  
   228  func (s *ExecutorSuite) TestFailPrepareWithStateChange(c *gc.C) {
   229  	initialState := justInstalledState()
   230  	executor, statePath := newExecutor(c, &initialState)
   231  	op := &mockOperation{
   232  		prepare: newStep(&operation.State{
   233  			Kind: operation.RunHook,
   234  			Step: operation.Pending,
   235  			Hook: &hook.Info{Kind: hooks.Start},
   236  		}, errors.New("blam")),
   237  	}
   238  
   239  	err := executor.Run(op)
   240  	c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": blam`)
   241  	c.Assert(errors.Cause(err), gc.ErrorMatches, "blam")
   242  
   243  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   244  	assertWroteState(c, statePath, *op.prepare.newState)
   245  	c.Assert(executor.State(), gc.DeepEquals, *op.prepare.newState)
   246  }
   247  
   248  func (s *ExecutorSuite) TestFailExecuteNoStateChange(c *gc.C) {
   249  	initialState := justInstalledState()
   250  	executor, statePath := newExecutor(c, &initialState)
   251  	op := &mockOperation{
   252  		prepare: newStep(nil, nil),
   253  		execute: newStep(nil, errors.New("splat")),
   254  	}
   255  
   256  	err := executor.Run(op)
   257  	c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": splat`)
   258  	c.Assert(errors.Cause(err), gc.ErrorMatches, "splat")
   259  
   260  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   261  	assertWroteState(c, statePath, initialState)
   262  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   263  }
   264  
   265  func (s *ExecutorSuite) TestFailExecuteWithStateChange(c *gc.C) {
   266  	initialState := justInstalledState()
   267  	executor, statePath := newExecutor(c, &initialState)
   268  	op := &mockOperation{
   269  		prepare: newStep(nil, nil),
   270  		execute: newStep(&operation.State{
   271  			Kind: operation.RunHook,
   272  			Step: operation.Pending,
   273  			Hook: &hook.Info{Kind: hooks.Start},
   274  		}, errors.New("kerblooie")),
   275  	}
   276  
   277  	err := executor.Run(op)
   278  	c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": kerblooie`)
   279  	c.Assert(errors.Cause(err), gc.ErrorMatches, "kerblooie")
   280  
   281  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   282  	assertWroteState(c, statePath, *op.execute.newState)
   283  	c.Assert(executor.State(), gc.DeepEquals, *op.execute.newState)
   284  }
   285  
   286  func (s *ExecutorSuite) TestFailCommitNoStateChange(c *gc.C) {
   287  	initialState := justInstalledState()
   288  	executor, statePath := newExecutor(c, &initialState)
   289  	op := &mockOperation{
   290  		prepare: newStep(nil, nil),
   291  		execute: newStep(nil, nil),
   292  		commit:  newStep(nil, errors.New("whack")),
   293  	}
   294  
   295  	err := executor.Run(op)
   296  	c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": whack`)
   297  	c.Assert(errors.Cause(err), gc.ErrorMatches, "whack")
   298  
   299  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   300  	assertWroteState(c, statePath, initialState)
   301  	c.Assert(executor.State(), gc.DeepEquals, initialState)
   302  }
   303  
   304  func (s *ExecutorSuite) TestFailCommitWithStateChange(c *gc.C) {
   305  	initialState := justInstalledState()
   306  	executor, statePath := newExecutor(c, &initialState)
   307  	op := &mockOperation{
   308  		prepare: newStep(nil, nil),
   309  		execute: newStep(nil, nil),
   310  		commit: newStep(&operation.State{
   311  			Kind: operation.RunHook,
   312  			Step: operation.Pending,
   313  			Hook: &hook.Info{Kind: hooks.Start},
   314  		}, errors.New("take that you bandit")),
   315  	}
   316  
   317  	err := executor.Run(op)
   318  	c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": take that you bandit`)
   319  	c.Assert(errors.Cause(err), gc.ErrorMatches, "take that you bandit")
   320  
   321  	c.Assert(op.prepare.gotState, gc.DeepEquals, initialState)
   322  	assertWroteState(c, statePath, *op.commit.newState)
   323  	c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState)
   324  }
   325  
   326  type mockStep struct {
   327  	gotState operation.State
   328  	newState *operation.State
   329  	err      error
   330  }
   331  
   332  func newStep(newState *operation.State, err error) *mockStep {
   333  	return &mockStep{newState: newState, err: err}
   334  }
   335  
   336  func (step *mockStep) run(state operation.State) (*operation.State, error) {
   337  	step.gotState = state
   338  	return step.newState, step.err
   339  }
   340  
   341  type mockOperation struct {
   342  	prepare *mockStep
   343  	execute *mockStep
   344  	commit  *mockStep
   345  }
   346  
   347  func (op *mockOperation) String() string {
   348  	return "mock operation"
   349  }
   350  
   351  func (op *mockOperation) Prepare(state operation.State) (*operation.State, error) {
   352  	return op.prepare.run(state)
   353  }
   354  
   355  func (op *mockOperation) Execute(state operation.State) (*operation.State, error) {
   356  	return op.execute.run(state)
   357  }
   358  
   359  func (op *mockOperation) Commit(state operation.State) (*operation.State, error) {
   360  	return op.commit.run(state)
   361  }