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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package actions_test
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"github.com/juju/testing"
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  
    13  	"github.com/juju/juju/worker/common/charmrunner"
    14  	"github.com/juju/juju/worker/uniter/actions"
    15  	"github.com/juju/juju/worker/uniter/hook"
    16  	"github.com/juju/juju/worker/uniter/operation"
    17  	"github.com/juju/juju/worker/uniter/remotestate"
    18  	"github.com/juju/juju/worker/uniter/resolver"
    19  )
    20  
    21  type actionsSuite struct {
    22  	testing.IsolationSuite
    23  }
    24  
    25  var _ = gc.Suite(&actionsSuite{})
    26  
    27  func (s *actionsSuite) newResolver() resolver.Resolver {
    28  	return actions.NewResolver(loggo.GetLogger("test"))
    29  }
    30  
    31  func (s *actionsSuite) TestNoActions(c *gc.C) {
    32  	actionResolver := s.newResolver()
    33  	localState := resolver.LocalState{}
    34  	remoteState := remotestate.Snapshot{}
    35  	_, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
    36  	c.Assert(err, gc.DeepEquals, resolver.ErrNoOperation)
    37  }
    38  
    39  func (s *actionsSuite) TestActionStateKindContinue(c *gc.C) {
    40  	actionResolver := s.newResolver()
    41  	localState := resolver.LocalState{
    42  		State: operation.State{
    43  			Kind: operation.Continue,
    44  		},
    45  	}
    46  	remoteState := remotestate.Snapshot{
    47  		ActionsPending: []string{"actionA", "actionB"},
    48  	}
    49  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	c.Assert(op, jc.DeepEquals, mockOp("actionA"))
    52  }
    53  
    54  func (s *actionsSuite) TestActionRunHook(c *gc.C) {
    55  	actionResolver := s.newResolver()
    56  	localState := resolver.LocalState{
    57  		State: operation.State{
    58  			Kind: operation.RunHook,
    59  			Step: operation.Pending,
    60  		},
    61  	}
    62  	remoteState := remotestate.Snapshot{
    63  		ActionsPending: []string{"actionA", "actionB"},
    64  	}
    65  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	c.Assert(op, jc.DeepEquals, mockOp("actionA"))
    68  }
    69  
    70  func (s *actionsSuite) TestNextAction(c *gc.C) {
    71  	actionResolver := s.newResolver()
    72  	localState := resolver.LocalState{
    73  		State: operation.State{
    74  			Kind: operation.Continue,
    75  		},
    76  		CompletedActions: map[string]struct{}{"actionA": {}},
    77  	}
    78  	remoteState := remotestate.Snapshot{
    79  		ActionsPending: []string{"actionA", "actionB"},
    80  	}
    81  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
    82  	c.Assert(err, jc.ErrorIsNil)
    83  	c.Assert(op, jc.DeepEquals, mockOp("actionB"))
    84  }
    85  
    86  func (s *actionsSuite) TestNextActionBlocked(c *gc.C) {
    87  	actionResolver := s.newResolver()
    88  	localState := resolver.LocalState{
    89  		State: operation.State{
    90  			Kind: operation.Continue,
    91  		},
    92  		CompletedActions: map[string]struct{}{"actionA": {}},
    93  	}
    94  	remoteState := remotestate.Snapshot{
    95  		ActionsPending: []string{"actionA", "actionB"},
    96  		ActionsBlocked: true,
    97  	}
    98  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
    99  	c.Assert(err, gc.DeepEquals, resolver.ErrNoOperation)
   100  	c.Assert(op, gc.IsNil)
   101  }
   102  
   103  func (s *actionsSuite) TestNextActionNotAvailable(c *gc.C) {
   104  	actionResolver := s.newResolver()
   105  	localState := resolver.LocalState{
   106  		State: operation.State{
   107  			Kind: operation.Continue,
   108  		},
   109  		CompletedActions: map[string]struct{}{"actionA": {}},
   110  	}
   111  	remoteState := remotestate.Snapshot{
   112  		ActionsPending: []string{"actionA", "actionB"},
   113  	}
   114  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{err: charmrunner.ErrActionNotAvailable})
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	c.Assert(op, jc.DeepEquals, mockFailAction("actionB"))
   117  }
   118  
   119  func (s *actionsSuite) TestNextActionBlockedRemoteInit(c *gc.C) {
   120  	actionResolver := s.newResolver()
   121  	localState := resolver.LocalState{
   122  		State: operation.State{
   123  			Kind: operation.Continue,
   124  		},
   125  		CompletedActions:    map[string]struct{}{"actionA": {}},
   126  		OutdatedRemoteCharm: true,
   127  	}
   128  	remoteState := remotestate.Snapshot{
   129  		ActionsPending: []string{"actionA", "actionB"},
   130  		ActionsBlocked: false,
   131  	}
   132  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
   133  	c.Assert(err, gc.DeepEquals, resolver.ErrNoOperation)
   134  	c.Assert(op, gc.IsNil)
   135  }
   136  
   137  func (s *actionsSuite) TestNextActionBlockedRemoteInitInProgress(c *gc.C) {
   138  	actionResolver := s.newResolver()
   139  	actionId := "actionB"
   140  	localState := resolver.LocalState{
   141  		State: operation.State{
   142  			Kind:     operation.RunAction,
   143  			ActionId: &actionId,
   144  		},
   145  		CompletedActions:    map[string]struct{}{"actionA": {}},
   146  		OutdatedRemoteCharm: true,
   147  	}
   148  	remoteState := remotestate.Snapshot{
   149  		ActionsPending: []string{"actionA", "actionB"},
   150  		ActionsBlocked: false,
   151  	}
   152  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
   153  	c.Assert(err, jc.ErrorIsNil)
   154  	c.Assert(op, gc.DeepEquals, mockFailAction("actionB"))
   155  }
   156  
   157  func (s *actionsSuite) TestNextActionBlockedRemoteInitSkipHook(c *gc.C) {
   158  	actionResolver := s.newResolver()
   159  	actionId := "actionBad"
   160  	localState := resolver.LocalState{
   161  		State: operation.State{
   162  			Kind:     operation.RunAction,
   163  			ActionId: &actionId,
   164  			Hook:     &hook.Info{Kind: "test"},
   165  		},
   166  		CompletedActions:    map[string]struct{}{"actionA": {}},
   167  		OutdatedRemoteCharm: false,
   168  	}
   169  	remoteState := remotestate.Snapshot{
   170  		ActionsPending: []string{"actionA", "actionB"},
   171  		ActionsBlocked: true,
   172  	}
   173  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	c.Assert(op, gc.DeepEquals, mockSkipHook(*localState.Hook))
   176  }
   177  
   178  func (s *actionsSuite) TestActionStateKindRunAction(c *gc.C) {
   179  	actionResolver := s.newResolver()
   180  	actionA := "actionA"
   181  
   182  	localState := resolver.LocalState{
   183  		State: operation.State{
   184  			Kind:     operation.RunAction,
   185  			ActionId: &actionA,
   186  		},
   187  		CompletedActions: map[string]struct{}{},
   188  	}
   189  	remoteState := remotestate.Snapshot{
   190  		ActionsPending: []string{},
   191  	}
   192  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
   193  	c.Assert(err, jc.ErrorIsNil)
   194  	c.Assert(op, jc.DeepEquals, mockOp(actionA))
   195  }
   196  
   197  func (s *actionsSuite) TestActionStateKindRunActionSkipHook(c *gc.C) {
   198  	actionResolver := s.newResolver()
   199  	actionA := "actionA"
   200  
   201  	localState := resolver.LocalState{
   202  		State: operation.State{
   203  			Kind:     operation.RunAction,
   204  			ActionId: &actionA,
   205  			Hook:     &hook.Info{Kind: "test"},
   206  		},
   207  		CompletedActions: map[string]struct{}{},
   208  	}
   209  	remoteState := remotestate.Snapshot{
   210  		ActionsPending: []string{},
   211  	}
   212  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	c.Assert(op, jc.DeepEquals, mockSkipHook(*localState.Hook))
   215  }
   216  
   217  func (s *actionsSuite) TestActionStateKindRunActionPendingRemote(c *gc.C) {
   218  	actionResolver := s.newResolver()
   219  	actionA := "actionA"
   220  
   221  	localState := resolver.LocalState{
   222  		State: operation.State{
   223  			Kind:     operation.RunAction,
   224  			ActionId: &actionA,
   225  		},
   226  		CompletedActions: map[string]struct{}{},
   227  	}
   228  	remoteState := remotestate.Snapshot{
   229  		ActionsPending: []string{actionA, "actionB"},
   230  	}
   231  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
   232  	c.Assert(err, jc.ErrorIsNil)
   233  	c.Assert(op, jc.DeepEquals, mockFailAction(actionA))
   234  }
   235  
   236  func (s *actionsSuite) TestPendingActionNotAvailable(c *gc.C) {
   237  	actionResolver := s.newResolver()
   238  	actionA := "666"
   239  
   240  	localState := resolver.LocalState{
   241  		State: operation.State{
   242  			Kind:     operation.RunAction,
   243  			Step:     operation.Pending,
   244  			ActionId: &actionA,
   245  		},
   246  		CompletedActions: map[string]struct{}{},
   247  	}
   248  	remoteState := remotestate.Snapshot{
   249  		ActionsPending: []string{"666"},
   250  	}
   251  	op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{})
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	c.Assert(op, jc.DeepEquals, mockFailAction(actionA))
   254  }
   255  
   256  type mockOperations struct {
   257  	operation.Factory
   258  	err error
   259  }
   260  
   261  func (m *mockOperations) NewAction(id string) (operation.Operation, error) {
   262  	if m.err != nil {
   263  		return nil, errors.Annotate(m.err, "action error")
   264  	}
   265  	if id == "666" {
   266  		return nil, charmrunner.ErrActionNotAvailable
   267  	}
   268  	return mockOp(id), nil
   269  }
   270  
   271  func (m *mockOperations) NewFailAction(id string) (operation.Operation, error) {
   272  	return mockFailAction(id), nil
   273  }
   274  
   275  func (m *mockOperations) NewSkipHook(hookInfo hook.Info) (operation.Operation, error) {
   276  	return mockSkipHook(hookInfo), nil
   277  }
   278  
   279  func mockOp(name string) operation.Operation {
   280  	return &mockOperation{name: name}
   281  }
   282  
   283  func mockFailAction(name string) operation.Operation {
   284  	return &mockFailOp{name: name}
   285  }
   286  
   287  func mockSkipHook(hookInfo hook.Info) operation.Operation {
   288  	return &mockSkipHookOp{hookInfo: hookInfo}
   289  }
   290  
   291  type mockOperation struct {
   292  	operation.Operation
   293  	name string
   294  }
   295  
   296  func (op *mockOperation) String() string {
   297  	return op.name
   298  }
   299  
   300  type mockFailOp struct {
   301  	operation.Operation
   302  	name string
   303  }
   304  
   305  func (op *mockFailOp) String() string {
   306  	return op.name
   307  }
   308  
   309  type mockSkipHookOp struct {
   310  	operation.Operation
   311  	hookInfo hook.Info
   312  }
   313  
   314  func (op *mockSkipHookOp) String() string {
   315  	return string(op.hookInfo.Kind)
   316  }