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: ¶ms.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: ¶ms.Action{Name: "backup", Parallel: ¶llel}, 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 }