github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/uniter/operation/runaction_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/names/v5"
    13  	"github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/api/agent/uniter"
    18  	basetesting "github.com/juju/juju/api/base/testing"
    19  	"github.com/juju/juju/rpc/params"
    20  	"github.com/juju/juju/worker/common/charmrunner"
    21  	"github.com/juju/juju/worker/uniter/hook"
    22  	"github.com/juju/juju/worker/uniter/operation"
    23  	"github.com/juju/juju/worker/uniter/remotestate"
    24  	"github.com/juju/juju/worker/uniter/runner"
    25  	"github.com/juju/juju/worker/uniter/runner/context"
    26  )
    27  
    28  type RunActionSuite struct {
    29  	testing.IsolationSuite
    30  }
    31  
    32  var _ = gc.Suite(&RunActionSuite{})
    33  
    34  func newOpFactory(runnerFactory runner.Factory, callbacks operation.Callbacks) operation.Factory {
    35  	actionResult := params.ActionResult{
    36  		Action: &params.Action{Name: "backup"},
    37  	}
    38  	return newOpFactoryForAction(runnerFactory, callbacks, actionResult)
    39  }
    40  
    41  func newOpFactoryForAction(runnerFactory runner.Factory, callbacks operation.Callbacks, action params.ActionResult) operation.Factory {
    42  	apiCaller := basetesting.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
    43  		*(result.(*params.ActionResults)) = params.ActionResults{
    44  			Results: []params.ActionResult{action},
    45  		}
    46  		return nil
    47  	})
    48  	st := uniter.NewState(apiCaller, names.NewUnitTag("mysql/0"))
    49  	return operation.NewFactory(operation.FactoryParams{
    50  		State:         st,
    51  		RunnerFactory: runnerFactory,
    52  		Callbacks:     callbacks,
    53  		Logger:        loggo.GetLogger("test"),
    54  	})
    55  }
    56  
    57  func (s *RunActionSuite) TestPrepareErrorBadActionAndFailSucceeds(c *gc.C) {
    58  	errBadAction := charmrunner.NewBadActionError("some-action-id", "splat")
    59  	runnerFactory := &MockRunnerFactory{
    60  		MockNewActionRunner: &MockNewActionRunner{err: errBadAction},
    61  	}
    62  	callbacks := &RunActionCallbacks{
    63  		MockFailAction: &MockFailAction{err: errors.New("squelch")},
    64  	}
    65  	factory := newOpFactory(runnerFactory, callbacks)
    66  	op, err := factory.NewAction(someActionId)
    67  	c.Assert(err, jc.ErrorIsNil)
    68  
    69  	newState, err := op.Prepare(operation.State{})
    70  	c.Assert(newState, gc.IsNil)
    71  	c.Assert(err, gc.ErrorMatches, "squelch")
    72  	c.Assert(*runnerFactory.MockNewActionRunner.gotActionId, gc.Equals, someActionId)
    73  	c.Assert(runnerFactory.MockNewActionRunner.gotCancel, gc.NotNil)
    74  	c.Assert(*callbacks.MockFailAction.gotActionId, gc.Equals, someActionId)
    75  	c.Assert(*callbacks.MockFailAction.gotMessage, gc.Equals, errBadAction.Error())
    76  }
    77  
    78  func (s *RunActionSuite) TestPrepareErrorBadActionAndFailErrors(c *gc.C) {
    79  	errBadAction := charmrunner.NewBadActionError("some-action-id", "foof")
    80  	runnerFactory := &MockRunnerFactory{
    81  		MockNewActionRunner: &MockNewActionRunner{err: errBadAction},
    82  	}
    83  	callbacks := &RunActionCallbacks{
    84  		MockFailAction: &MockFailAction{},
    85  	}
    86  	factory := newOpFactory(runnerFactory, callbacks)
    87  	op, err := factory.NewAction(someActionId)
    88  	c.Assert(err, jc.ErrorIsNil)
    89  
    90  	newState, err := op.Prepare(operation.State{})
    91  	c.Assert(newState, gc.IsNil)
    92  	c.Assert(err, gc.Equals, operation.ErrSkipExecute)
    93  	c.Assert(*runnerFactory.MockNewActionRunner.gotActionId, gc.Equals, someActionId)
    94  	c.Assert(runnerFactory.MockNewActionRunner.gotCancel, gc.NotNil)
    95  	c.Assert(*callbacks.MockFailAction.gotActionId, gc.Equals, someActionId)
    96  	c.Assert(*callbacks.MockFailAction.gotMessage, gc.Equals, errBadAction.Error())
    97  }
    98  
    99  func (s *RunActionSuite) TestPrepareErrorActionNotAvailable(c *gc.C) {
   100  	runnerFactory := &MockRunnerFactory{
   101  		MockNewActionRunner: &MockNewActionRunner{err: charmrunner.ErrActionNotAvailable},
   102  	}
   103  	factory := newOpFactory(runnerFactory, nil)
   104  	op, err := factory.NewAction(someActionId)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  
   107  	newState, err := op.Prepare(operation.State{})
   108  	c.Assert(newState, gc.IsNil)
   109  	c.Assert(err, gc.Equals, operation.ErrSkipExecute)
   110  	c.Assert(*runnerFactory.MockNewActionRunner.gotActionId, gc.Equals, someActionId)
   111  	c.Assert(runnerFactory.MockNewActionRunner.gotCancel, gc.NotNil)
   112  }
   113  
   114  func (s *RunActionSuite) TestPrepareErrorOther(c *gc.C) {
   115  	runnerFactory := &MockRunnerFactory{
   116  		MockNewActionRunner: &MockNewActionRunner{err: errors.New("foop")},
   117  	}
   118  	factory := newOpFactory(runnerFactory, nil)
   119  	op, err := factory.NewAction(someActionId)
   120  	c.Assert(err, jc.ErrorIsNil)
   121  
   122  	newState, err := op.Prepare(operation.State{})
   123  	c.Assert(newState, gc.IsNil)
   124  	c.Assert(err, gc.ErrorMatches, `cannot create runner for action ".*": foop`)
   125  	c.Assert(*runnerFactory.MockNewActionRunner.gotActionId, gc.Equals, someActionId)
   126  	c.Assert(runnerFactory.MockNewActionRunner.gotCancel, gc.NotNil)
   127  }
   128  
   129  func (s *RunActionSuite) TestPrepareCtxCalled(c *gc.C) {
   130  	ctx := &MockContext{actionData: &context.ActionData{Name: "some-action-name"}}
   131  	runnerFactory := &MockRunnerFactory{
   132  		MockNewActionRunner: &MockNewActionRunner{
   133  			runner: &MockRunner{
   134  				context: ctx,
   135  			},
   136  		},
   137  	}
   138  	factory := newOpFactory(runnerFactory, nil)
   139  	op, err := factory.NewAction(someActionId)
   140  	c.Assert(err, jc.ErrorIsNil)
   141  
   142  	newState, err := op.Prepare(operation.State{})
   143  	c.Assert(err, jc.ErrorIsNil)
   144  	c.Assert(newState, gc.NotNil)
   145  	ctx.CheckCall(c, 0, "Prepare")
   146  }
   147  
   148  func (s *RunActionSuite) TestPrepareCtxError(c *gc.C) {
   149  	ctx := &MockContext{actionData: &context.ActionData{Name: "some-action-name"}}
   150  	ctx.SetErrors(errors.New("ctx prepare error"))
   151  	runnerFactory := &MockRunnerFactory{
   152  		MockNewActionRunner: &MockNewActionRunner{
   153  			runner: &MockRunner{
   154  				context: ctx,
   155  			},
   156  		},
   157  	}
   158  	factory := newOpFactory(runnerFactory, nil)
   159  	op, err := factory.NewAction(someActionId)
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	newState, err := op.Prepare(operation.State{})
   163  	c.Assert(err, gc.ErrorMatches, `ctx prepare error`)
   164  	c.Assert(newState, gc.IsNil)
   165  	ctx.CheckCall(c, 0, "Prepare")
   166  }
   167  
   168  func (s *RunActionSuite) TestPrepareSuccessCleanState(c *gc.C) {
   169  	runnerFactory := NewRunActionRunnerFactory(errors.New("should not call"))
   170  	factory := newOpFactory(runnerFactory, nil)
   171  	op, err := factory.NewAction(someActionId)
   172  	c.Assert(err, jc.ErrorIsNil)
   173  
   174  	newState, err := op.Prepare(operation.State{})
   175  	c.Assert(err, jc.ErrorIsNil)
   176  	c.Assert(newState, jc.DeepEquals, &operation.State{
   177  		Kind:     operation.RunAction,
   178  		Step:     operation.Pending,
   179  		ActionId: &someActionId,
   180  	})
   181  	c.Assert(*runnerFactory.MockNewActionRunner.gotActionId, gc.Equals, someActionId)
   182  	c.Assert(runnerFactory.MockNewActionRunner.gotCancel, gc.NotNil)
   183  }
   184  
   185  func (s *RunActionSuite) TestPrepareSuccessDirtyState(c *gc.C) {
   186  	runnerFactory := NewRunActionRunnerFactory(errors.New("should not call"))
   187  	factory := newOpFactory(runnerFactory, nil)
   188  	op, err := factory.NewAction(someActionId)
   189  	c.Assert(err, jc.ErrorIsNil)
   190  
   191  	newState, err := op.Prepare(overwriteState)
   192  	c.Assert(err, jc.ErrorIsNil)
   193  	c.Assert(newState, jc.DeepEquals, &operation.State{
   194  		Kind:     operation.RunAction,
   195  		Step:     operation.Pending,
   196  		ActionId: &someActionId,
   197  		Started:  true,
   198  		Hook:     &hook.Info{Kind: hooks.Install},
   199  	})
   200  	c.Assert(*runnerFactory.MockNewActionRunner.gotActionId, gc.Equals, someActionId)
   201  	c.Assert(runnerFactory.MockNewActionRunner.gotCancel, gc.NotNil)
   202  }
   203  
   204  func (s *RunActionSuite) TestExecuteSuccess(c *gc.C) {
   205  	var stateChangeTests = []struct {
   206  		description string
   207  		before      operation.State
   208  		after       operation.State
   209  	}{{
   210  		description: "empty state",
   211  		after: operation.State{
   212  			Kind:     operation.RunAction,
   213  			Step:     operation.Done,
   214  			ActionId: &someActionId,
   215  		},
   216  	}, {
   217  		description: "preserves appropriate fields",
   218  		before:      overwriteState,
   219  		after: operation.State{
   220  			Kind:     operation.RunAction,
   221  			Step:     operation.Done,
   222  			ActionId: &someActionId,
   223  			Hook:     &hook.Info{Kind: hooks.Install},
   224  			Started:  true,
   225  		},
   226  	}}
   227  
   228  	for i, test := range stateChangeTests {
   229  		c.Logf("test %d: %s", i, test.description)
   230  		runnerFactory := NewRunActionRunnerFactory(nil)
   231  		callbacks := &RunActionCallbacks{}
   232  		factory := newOpFactory(runnerFactory, callbacks)
   233  		op, err := factory.NewAction(someActionId)
   234  		c.Assert(err, jc.ErrorIsNil)
   235  		midState, err := op.Prepare(test.before)
   236  		c.Assert(midState, gc.NotNil)
   237  		c.Assert(err, jc.ErrorIsNil)
   238  
   239  		newState, err := op.Execute(*midState)
   240  		c.Assert(err, jc.ErrorIsNil)
   241  		c.Assert(newState, jc.DeepEquals, &test.after)
   242  		c.Assert(callbacks.executingMessage, gc.Equals, "running action some-action-name")
   243  		c.Assert(*runnerFactory.MockNewActionRunner.runner.MockRunAction.gotName, gc.Equals, "some-action-name")
   244  		c.Assert(runnerFactory.MockNewActionRunner.gotCancel, gc.NotNil)
   245  	}
   246  }
   247  
   248  func (s *RunActionSuite) TestExecuteCancel(c *gc.C) {
   249  	actionChan := make(chan error)
   250  	defer close(actionChan)
   251  	runnerFactory := NewRunActionWaitRunnerFactory(actionChan)
   252  	callbacks := &RunActionCallbacks{
   253  		actionStatus: "running",
   254  	}
   255  	factory := newOpFactory(runnerFactory, callbacks)
   256  	op, err := factory.NewAction(someActionId)
   257  	c.Assert(err, jc.ErrorIsNil)
   258  	midState, err := op.Prepare(operation.State{})
   259  	c.Assert(midState, gc.NotNil)
   260  	c.Assert(err, jc.ErrorIsNil)
   261  
   262  	abortedErr := errors.Errorf("aborted")
   263  	wait := make(chan struct{})
   264  	go func() {
   265  		newState, err := op.Execute(*midState)
   266  		c.Assert(errors.Cause(err), gc.Equals, abortedErr)
   267  		c.Assert(newState, gc.IsNil)
   268  		c.Assert(runnerFactory.MockNewActionWaitRunner.runner.actionName, gc.Equals, "some-action-name")
   269  		c.Assert(runnerFactory.MockNewActionWaitRunner.runner.actionChan, gc.Equals, (<-chan error)(actionChan))
   270  		c.Assert(runnerFactory.MockNewActionWaitRunner.gotCancel, gc.NotNil)
   271  		close(wait)
   272  	}()
   273  
   274  	op.RemoteStateChanged(remotestate.Snapshot{
   275  		ActionChanged: map[string]int{
   276  			someActionId: 1,
   277  		},
   278  	})
   279  
   280  	callbacks.setActionStatus("aborting", nil)
   281  
   282  	op.RemoteStateChanged(remotestate.Snapshot{
   283  		ActionChanged: map[string]int{
   284  			someActionId: 2,
   285  		},
   286  	})
   287  
   288  	select {
   289  	case <-runnerFactory.gotCancel:
   290  	case <-time.After(testing.ShortWait):
   291  		c.Fatalf("waiting for cancel")
   292  	}
   293  
   294  	select {
   295  	case actionChan <- abortedErr:
   296  	case <-time.After(testing.ShortWait):
   297  		c.Fatalf("waiting for send")
   298  	}
   299  
   300  	select {
   301  	case <-wait:
   302  	case <-time.After(testing.ShortWait):
   303  		c.Fatalf("waiting for finish")
   304  	}
   305  }
   306  
   307  func (s *RunActionSuite) TestCommit(c *gc.C) {
   308  	var stateChangeTests = []struct {
   309  		description string
   310  		before      operation.State
   311  		after       operation.State
   312  	}{{
   313  		description: "empty state",
   314  		after: operation.State{
   315  			Kind: operation.Continue,
   316  			Step: operation.Pending,
   317  		},
   318  	}, {
   319  		description: "preserves only appropriate fields, no hook",
   320  		before: operation.State{
   321  			Kind:     operation.Continue,
   322  			Step:     operation.Pending,
   323  			Started:  true,
   324  			CharmURL: "ch:quantal/wordpress-2",
   325  			ActionId: &randomActionId,
   326  		},
   327  		after: operation.State{
   328  			Kind:    operation.Continue,
   329  			Step:    operation.Pending,
   330  			Started: true,
   331  		},
   332  	}, {
   333  		description: "preserves only appropriate fields, with hook",
   334  		before: operation.State{
   335  			Kind:     operation.Continue,
   336  			Step:     operation.Pending,
   337  			Started:  true,
   338  			CharmURL: "ch:quantal/wordpress-2",
   339  			ActionId: &randomActionId,
   340  			Hook:     &hook.Info{Kind: hooks.Install},
   341  		},
   342  		after: operation.State{
   343  			Kind:    operation.RunHook,
   344  			Step:    operation.Pending,
   345  			Hook:    &hook.Info{Kind: hooks.Install},
   346  			Started: true,
   347  		},
   348  	}}
   349  
   350  	for i, test := range stateChangeTests {
   351  		c.Logf("test %d: %s", i, test.description)
   352  		factory := newOpFactory(nil, nil)
   353  		op, err := factory.NewAction(someActionId)
   354  		c.Assert(err, jc.ErrorIsNil)
   355  
   356  		newState, err := op.Commit(test.before)
   357  		c.Assert(err, jc.ErrorIsNil)
   358  		c.Assert(newState, jc.DeepEquals, &test.after)
   359  	}
   360  }
   361  
   362  func (s *RunActionSuite) TestNeedsGlobalMachineLock(c *gc.C) {
   363  	factory := newOpFactory(nil, nil)
   364  	op, err := factory.NewAction(someActionId)
   365  	c.Assert(err, jc.ErrorIsNil)
   366  	c.Assert(op.NeedsGlobalMachineLock(), jc.IsTrue)
   367  }
   368  
   369  func (s *RunActionSuite) TestDoesNotNeedGlobalMachineLock(c *gc.C) {
   370  	parallel := true
   371  	actionResult := params.ActionResult{
   372  		Action: &params.Action{Name: "backup", Parallel: &parallel},
   373  	}
   374  	factory := newOpFactoryForAction(nil, nil, actionResult)
   375  	op, err := factory.NewAction(someActionId)
   376  	c.Assert(err, jc.ErrorIsNil)
   377  	c.Assert(op.NeedsGlobalMachineLock(), jc.IsFalse)
   378  }