github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/runner/runner_test.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package runner_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "os" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/juju/charm/v12/hooks" 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 envtesting "github.com/juju/testing" 18 jc "github.com/juju/testing/checkers" 19 "github.com/juju/utils/v3/exec" 20 gc "gopkg.in/check.v1" 21 22 "github.com/juju/juju/core/model" 23 "github.com/juju/juju/worker/common/charmrunner" 24 "github.com/juju/juju/worker/uniter/hook" 25 "github.com/juju/juju/worker/uniter/runner" 26 "github.com/juju/juju/worker/uniter/runner/context" 27 runnertesting "github.com/juju/juju/worker/uniter/runner/testing" 28 ) 29 30 type RunCommandSuite struct { 31 ContextSuite 32 } 33 34 var _ = gc.Suite(&RunCommandSuite{}) 35 36 func (s *RunCommandSuite) TestRunCommandsEnvStdOutAndErrAndRC(c *gc.C) { 37 ctx, err := s.contextFactory.HookContext(hook.Info{Kind: hooks.ConfigChanged}) 38 c.Assert(err, jc.ErrorIsNil) 39 paths := runnertesting.NewRealPaths(c) 40 r := runner.NewRunner(ctx, paths, nil) 41 42 // Ensure the current process env is passed through to the command. 43 s.PatchEnvironment("KUBERNETES_PORT", "443") 44 45 commands := ` 46 echo $JUJU_CHARM_DIR 47 echo $FOO 48 echo this is standard err >&2 49 exit 42 50 ` 51 result, err := r.RunCommands(commands, runner.Operator) 52 c.Assert(err, jc.ErrorIsNil) 53 54 c.Assert(result.Code, gc.Equals, 42) 55 c.Assert(strings.ReplaceAll(string(result.Stdout), "\n", ""), gc.Equals, paths.GetCharmDir()) 56 c.Assert(strings.TrimRight(string(result.Stderr), "\n"), gc.Equals, "this is standard err") 57 c.Assert(ctx.GetProcess(), gc.NotNil) 58 } 59 60 type RunHookSuite struct { 61 ContextSuite 62 } 63 64 var _ = gc.Suite(&RunHookSuite{}) 65 66 // LineBufferSize matches the constant used when creating 67 // the bufio line reader. 68 const lineBufferSize = 4096 69 70 var runHookTests = []struct { 71 summary string 72 relid int 73 spec hookSpec 74 err string 75 hookType runner.HookHandlerType 76 }{ 77 { 78 summary: "missing hook is not an error", 79 relid: -1, 80 hookType: runner.InvalidHookHandler, 81 }, { 82 summary: "report error indicated by hook's exit status", 83 relid: -1, 84 spec: hookSpec{ 85 perm: 0700, 86 code: 99, 87 }, 88 err: "exit status 99", 89 hookType: runner.ExplicitHookHandler, 90 }, { 91 summary: "report error with invalid script", 92 relid: -1, 93 spec: hookSpec{ 94 perm: 0700, 95 code: 2, 96 missingShebang: true, 97 }, 98 err: "fork/exec.*: exec format error", 99 hookType: runner.ExplicitHookHandler, 100 }, { 101 summary: "report error with missing charm", 102 relid: -1, 103 spec: hookSpec{ 104 charmMissing: true, 105 perm: 0700, 106 }, 107 err: "charm missing from disk", 108 hookType: runner.InvalidHookHandler, 109 }, { 110 summary: "output logging", 111 relid: -1, 112 spec: hookSpec{ 113 perm: 0700, 114 stdout: "stdout", 115 stderr: "stderr", 116 }, 117 hookType: runner.ExplicitHookHandler, 118 }, { 119 summary: "output logging with background process", 120 relid: -1, 121 spec: hookSpec{ 122 perm: 0700, 123 stdout: "stdout", 124 background: "not printed", 125 }, 126 hookType: runner.ExplicitHookHandler, 127 }, { 128 summary: "long line split", 129 relid: -1, 130 spec: hookSpec{ 131 perm: 0700, 132 stdout: strings.Repeat("a", lineBufferSize+10), 133 }, 134 hookType: runner.ExplicitHookHandler, 135 }, 136 } 137 138 type RestrictedWriter struct { 139 Module string // what Module should be included in the log buffer 140 Buffer bytes.Buffer 141 } 142 143 func (r *RestrictedWriter) Write(entry loggo.Entry) { 144 if strings.HasPrefix(entry.Module, r.Module) { 145 fmt.Fprintf(&r.Buffer, "%s %s %s\n", entry.Level.String(), entry.Module, entry.Message) 146 } 147 } 148 149 func (s *RunHookSuite) TestRunHook(c *gc.C) { 150 writer := &RestrictedWriter{Module: "unit.u/0.something-happened"} 151 c.Assert(loggo.RegisterWriter("test", writer), jc.ErrorIsNil) 152 for i, t := range runHookTests { 153 writer.Buffer.Reset() 154 c.Logf("\ntest %d of %d: %s; perm %v", i, len(runHookTests)+1, t.summary, t.spec.perm) 155 ctx, err := s.contextFactory.HookContext(hook.Info{Kind: hooks.ConfigChanged}) 156 c.Assert(err, jc.ErrorIsNil) 157 158 paths := runnertesting.NewRealPaths(c) 159 rnr := runner.NewRunner(ctx, paths, nil) 160 var hookExists bool 161 if t.spec.perm != 0 { 162 spec := t.spec 163 spec.dir = "hooks" 164 spec.name = hookName 165 c.Logf("makeCharm %#v", spec) 166 makeCharm(c, spec, paths.GetCharmDir()) 167 hookExists = true 168 } else if !t.spec.charmMissing { 169 makeCharmMetadata(c, paths.GetCharmDir()) 170 } 171 t0 := time.Now() 172 hookType, err := rnr.RunHook("something-happened") 173 if t.err == "" && hookExists { 174 c.Assert(err, jc.ErrorIsNil) 175 } else if !hookExists { 176 c.Assert(charmrunner.IsMissingHookError(err), jc.IsTrue) 177 } else { 178 c.Assert(err, gc.ErrorMatches, t.err) 179 } 180 if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second { 181 c.Errorf("background process holding up hook execution") 182 } 183 c.Assert(hookType, gc.Equals, t.hookType) 184 if t.spec.stdout != "" { 185 if len(t.spec.stdout) < lineBufferSize { 186 c.Check(writer.Buffer.String(), jc.Contains, 187 fmt.Sprintf("DEBUG unit.u/0.something-happened %s\n", t.spec.stdout)) 188 } else { 189 // Lines longer than lineBufferSize get split into multiple log messages 190 c.Check(writer.Buffer.String(), jc.Contains, 191 fmt.Sprintf("DEBUG unit.u/0.something-happened %s\n", t.spec.stdout[:lineBufferSize])) 192 c.Check(writer.Buffer.String(), jc.Contains, 193 fmt.Sprintf("DEBUG unit.u/0.something-happened %s\n", t.spec.stdout[lineBufferSize:])) 194 } 195 } 196 if t.spec.stderr != "" { 197 c.Check(writer.Buffer.String(), jc.Contains, 198 fmt.Sprintf("WARNING unit.u/0.something-happened %s\n", t.spec.stderr)) 199 } 200 } 201 } 202 203 func (s *RunHookSuite) TestRunHookDispatchingHookHandler(c *gc.C) { 204 ctx, err := s.contextFactory.HookContext(hook.Info{Kind: hooks.ConfigChanged}) 205 c.Assert(err, jc.ErrorIsNil) 206 207 paths := runnertesting.NewRealPaths(c) 208 rnr := runner.NewRunner(ctx, paths, nil) 209 spec := hookSpec{ 210 name: "dispatch", 211 perm: 0700, 212 } 213 c.Logf("makeCharm %#v", spec) 214 makeCharm(c, spec, paths.GetCharmDir()) 215 216 hookType, err := rnr.RunHook("something-happened") 217 c.Assert(err, jc.ErrorIsNil) 218 c.Assert(hookType, gc.Equals, runner.DispatchingHookHandler) 219 } 220 221 type MockContext struct { 222 context.Context 223 actionData *context.ActionData 224 actionDataErr error 225 actionParams map[string]interface{} 226 actionParamsErr error 227 actionResults map[string]interface{} 228 expectPid int 229 flushBadge string 230 flushFailure error 231 flushResult error 232 modelType model.ModelType 233 } 234 235 func (ctx *MockContext) GetLogger(module string) loggo.Logger { 236 return loggo.GetLogger(module) 237 } 238 239 func (ctx *MockContext) UnitName() string { 240 return "some-unit/999" 241 } 242 243 func (ctx *MockContext) HookVars( 244 paths context.Paths, 245 _ bool, 246 envVars context.Environmenter, 247 ) ([]string, error) { 248 path := envVars.Getenv("PATH") 249 newPath := fmt.Sprintf("PATH=pathypathpath;%s", path) 250 return []string{"VAR=value", newPath}, nil 251 } 252 253 func (ctx *MockContext) ActionData() (*context.ActionData, error) { 254 if ctx.actionData == nil { 255 return nil, errors.New("blam") 256 } 257 return ctx.actionData, ctx.actionDataErr 258 } 259 260 func (ctx *MockContext) SetProcess(process context.HookProcess) { 261 ctx.expectPid = process.Pid() 262 } 263 264 func (ctx *MockContext) Prepare() error { 265 return nil 266 } 267 268 func (ctx *MockContext) Flush(badge string, failure error) error { 269 ctx.flushBadge = badge 270 ctx.flushFailure = failure 271 return ctx.flushResult 272 } 273 274 func (ctx *MockContext) ActionParams() (map[string]interface{}, error) { 275 return ctx.actionParams, ctx.actionParamsErr 276 } 277 278 func (ctx *MockContext) UpdateActionResults(keys []string, value interface{}) error { 279 for _, key := range keys { 280 ctx.actionResults[key] = value 281 } 282 return nil 283 } 284 285 func (ctx *MockContext) ModelType() model.ModelType { 286 if ctx.modelType == "" { 287 return model.IAAS 288 } 289 return ctx.modelType 290 } 291 292 type RunMockContextSuite struct { 293 envtesting.IsolationSuite 294 paths runnertesting.RealPaths 295 } 296 297 var _ = gc.Suite(&RunMockContextSuite{}) 298 299 func (s *RunMockContextSuite) SetUpTest(c *gc.C) { 300 s.IsolationSuite.SetUpTest(c) 301 s.paths = runnertesting.NewRealPaths(c) 302 } 303 304 func (s *RunMockContextSuite) assertRecordedPid(c *gc.C, expectPid int) { 305 path := filepath.Join(s.paths.GetCharmDir(), "pid") 306 content, err := os.ReadFile(path) 307 c.Assert(err, jc.ErrorIsNil) 308 expectContent := fmt.Sprintf("%d", expectPid) 309 c.Assert(strings.TrimRight(string(content), "\r\n"), gc.Equals, expectContent) 310 } 311 312 func (s *RunMockContextSuite) TestRunHookFlushSuccess(c *gc.C) { 313 expectErr := errors.New("pew pew pew") 314 ctx := &MockContext{ 315 flushResult: expectErr, 316 } 317 makeCharm(c, hookSpec{ 318 dir: "hooks", 319 name: hookName, 320 perm: 0700, 321 }, s.paths.GetCharmDir()) 322 _, actualErr := runner.NewRunner(ctx, s.paths, nil).RunHook("something-happened") 323 c.Assert(actualErr, gc.Equals, expectErr) 324 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 325 c.Assert(ctx.flushFailure, gc.IsNil) 326 s.assertRecordedPid(c, ctx.expectPid) 327 } 328 329 func (s *RunMockContextSuite) TestRunHookFlushFailure(c *gc.C) { 330 expectErr := errors.New("pew pew pew") 331 ctx := &MockContext{ 332 flushResult: expectErr, 333 } 334 makeCharm(c, hookSpec{ 335 dir: "hooks", 336 name: hookName, 337 perm: 0700, 338 code: 123, 339 }, s.paths.GetCharmDir()) 340 _, actualErr := runner.NewRunner(ctx, s.paths, nil).RunHook("something-happened") 341 c.Assert(actualErr, gc.Equals, expectErr) 342 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 343 c.Assert(ctx.flushFailure, gc.ErrorMatches, "exit status 123") 344 s.assertRecordedPid(c, ctx.expectPid) 345 } 346 347 func (s *RunHookSuite) TestRunActionDispatchingHookHandler(c *gc.C) { 348 ctx := &MockContext{ 349 actionData: &context.ActionData{}, 350 actionResults: map[string]interface{}{}, 351 } 352 353 paths := runnertesting.NewRealPaths(c) 354 rnr := runner.NewRunner(ctx, paths, nil) 355 spec := hookSpec{ 356 name: "dispatch", 357 perm: 0700, 358 } 359 c.Logf("makeCharm %#v", spec) 360 makeCharm(c, spec, paths.GetCharmDir()) 361 362 hookType, err := rnr.RunAction("something-happened") 363 c.Assert(err, jc.ErrorIsNil) 364 c.Assert(hookType, gc.Equals, runner.DispatchingHookHandler) 365 } 366 367 func (s *RunMockContextSuite) TestRunActionFlushSuccess(c *gc.C) { 368 expectErr := errors.New("pew pew pew") 369 ctx := &MockContext{ 370 flushResult: expectErr, 371 actionData: &context.ActionData{}, 372 actionResults: map[string]interface{}{}, 373 } 374 makeCharm(c, hookSpec{ 375 dir: "actions", 376 name: hookName, 377 perm: 0700, 378 stdout: "hello", 379 stderr: "world", 380 }, s.paths.GetCharmDir()) 381 hookType, actualErr := runner.NewRunner(ctx, s.paths, nil).RunAction("something-happened") 382 c.Assert(actualErr, gc.Equals, expectErr) 383 c.Assert(hookType, gc.Equals, runner.ExplicitHookHandler) 384 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 385 c.Assert(ctx.flushFailure, gc.IsNil) 386 s.assertRecordedPid(c, ctx.expectPid) 387 c.Assert(ctx.actionResults, jc.DeepEquals, map[string]interface{}{ 388 "return-code": 0, "stderr": "world\n", "stdout": "hello\n", 389 }) 390 } 391 392 func (s *RunMockContextSuite) TestRunActionFlushCharmActionsCAASSuccess(c *gc.C) { 393 expectErr := errors.New("pew pew pew") 394 ctx := &MockContext{ 395 flushResult: expectErr, 396 actionData: &context.ActionData{}, 397 actionResults: map[string]interface{}{}, 398 modelType: model.CAAS, 399 } 400 makeCharm(c, hookSpec{ 401 dir: "actions", 402 name: hookName, 403 perm: 0700, 404 }, s.paths.GetCharmDir()) 405 406 execCount := 0 407 execFunc := func(params runner.ExecParams) (*exec.ExecResponse, error) { 408 execCount++ 409 switch execCount { 410 case 1: 411 return &exec.ExecResponse{}, nil 412 case 2: 413 return &exec.ExecResponse{ 414 Stdout: bytes.NewBufferString("hello").Bytes(), 415 Stderr: bytes.NewBufferString("world").Bytes(), 416 }, nil 417 } 418 c.Fatal("invalid count") 419 return nil, nil 420 } 421 _, actualErr := runner.NewRunner(ctx, s.paths, execFunc).RunAction("something-happened") 422 c.Assert(execCount, gc.Equals, 2) 423 c.Assert(actualErr, gc.Equals, expectErr) 424 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 425 c.Assert(ctx.flushFailure, gc.IsNil) 426 c.Assert(ctx.actionResults, jc.DeepEquals, map[string]interface{}{ 427 "return-code": 0, "stderr": "world", "stdout": "hello", 428 }) 429 } 430 431 func (s *RunMockContextSuite) TestRunActionFlushCharmActionsCAASFailed(c *gc.C) { 432 ctx := &MockContext{ 433 flushResult: errors.New("pew pew pew"), 434 actionData: &context.ActionData{}, 435 modelType: model.CAAS, 436 } 437 makeCharm(c, hookSpec{ 438 dir: "actions", 439 name: hookName, 440 perm: 0700, 441 }, s.paths.GetCharmDir()) 442 execCount := 0 443 execFunc := func(params runner.ExecParams) (*exec.ExecResponse, error) { 444 execCount++ 445 switch execCount { 446 case 1: 447 return &exec.ExecResponse{}, nil 448 case 2: 449 return nil, errors.Errorf("failed exec") 450 } 451 c.Fatal("invalid count") 452 return nil, nil 453 } 454 _, actualErr := runner.NewRunner(ctx, s.paths, execFunc).RunAction("something-happened") 455 c.Assert(execCount, gc.Equals, 2) 456 c.Assert(actualErr, gc.Equals, ctx.flushResult) 457 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 458 c.Assert(ctx.flushFailure, gc.ErrorMatches, "failed exec") 459 } 460 461 func (s *RunMockContextSuite) TestRunActionFlushFailure(c *gc.C) { 462 expectErr := errors.New("pew pew pew") 463 ctx := &MockContext{ 464 flushResult: expectErr, 465 actionData: &context.ActionData{}, 466 actionResults: map[string]interface{}{}, 467 } 468 makeCharm(c, hookSpec{ 469 dir: "actions", 470 name: hookName, 471 perm: 0700, 472 code: 123, 473 }, s.paths.GetCharmDir()) 474 _, actualErr := runner.NewRunner(ctx, s.paths, nil).RunAction("something-happened") 475 c.Assert(actualErr, gc.Equals, expectErr) 476 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 477 c.Assert(ctx.flushFailure, gc.ErrorMatches, "exit status 123") 478 s.assertRecordedPid(c, ctx.expectPid) 479 } 480 481 func (s *RunMockContextSuite) TestRunActionDataFailure(c *gc.C) { 482 expectErr := errors.New("stork") 483 ctx := &MockContext{ 484 actionData: &context.ActionData{}, 485 actionDataErr: expectErr, 486 } 487 _, actualErr := runner.NewRunner(ctx, s.paths, nil).RunAction("juju-exec") 488 c.Assert(errors.Cause(actualErr), gc.Equals, expectErr) 489 } 490 491 func (s *RunMockContextSuite) TestRunActionSuccessful(c *gc.C) { 492 params := map[string]interface{}{ 493 "command": "echo 1", 494 "timeout": 0, 495 } 496 ctx := &MockContext{ 497 actionData: &context.ActionData{ 498 Params: params, 499 }, 500 actionParams: params, 501 actionResults: map[string]interface{}{}, 502 } 503 _, err := runner.NewRunner(ctx, s.paths, nil).RunAction("juju-exec") 504 c.Assert(err, jc.ErrorIsNil) 505 c.Assert(ctx.flushBadge, gc.Equals, "juju-exec") 506 c.Assert(ctx.flushFailure, gc.IsNil) 507 c.Assert(ctx.actionResults["return-code"], gc.Equals, 0) 508 c.Assert(strings.TrimRight(ctx.actionResults["stdout"].(string), "\r\n"), gc.Equals, "1") 509 c.Assert(ctx.actionResults["stderr"], gc.Equals, nil) 510 } 511 512 func (s *RunMockContextSuite) TestRunActionError(c *gc.C) { 513 params := map[string]interface{}{ 514 "command": "echo 1\nexit 3", 515 "timeout": 0, 516 } 517 ctx := &MockContext{ 518 actionData: &context.ActionData{ 519 Params: params, 520 }, 521 actionParams: params, 522 actionResults: map[string]interface{}{}, 523 } 524 _, err := runner.NewRunner(ctx, s.paths, nil).RunAction("juju-exec") 525 c.Assert(err, jc.ErrorIsNil) 526 c.Assert(ctx.flushBadge, gc.Equals, "juju-exec") 527 c.Assert(ctx.flushFailure, gc.IsNil) 528 c.Assert(ctx.actionResults["return-code"], gc.Equals, 3) 529 c.Assert(strings.TrimRight(ctx.actionResults["stdout"].(string), "\r\n"), gc.Equals, "1") 530 c.Assert(ctx.actionResults["stderr"], gc.Equals, nil) 531 } 532 533 func (s *RunMockContextSuite) TestRunActionCancelled(c *gc.C) { 534 timeout := 1 * time.Nanosecond 535 params := map[string]interface{}{ 536 "command": "sleep 10", 537 "timeout": float64(timeout.Nanoseconds()), 538 } 539 ctx := &MockContext{ 540 actionData: &context.ActionData{ 541 Params: params, 542 }, 543 actionParams: params, 544 actionResults: map[string]interface{}{}, 545 } 546 _, err := runner.NewRunner(ctx, s.paths, nil).RunAction("juju-exec") 547 c.Assert(err, jc.ErrorIsNil) 548 c.Assert(ctx.flushBadge, gc.Equals, "juju-exec") 549 c.Assert(ctx.flushFailure, gc.Equals, exec.ErrCancelled) 550 c.Assert(ctx.actionResults["return-code"], gc.Equals, 0) 551 c.Assert(ctx.actionResults["stdout"], gc.Equals, nil) 552 c.Assert(ctx.actionResults["stderr"], gc.Equals, nil) 553 } 554 555 func (s *RunMockContextSuite) TestRunCommandsFlushSuccess(c *gc.C) { 556 expectErr := errors.New("pew pew pew") 557 ctx := &MockContext{ 558 flushResult: expectErr, 559 } 560 _, actualErr := runner.NewRunner(ctx, s.paths, nil).RunCommands(echoPidScript, runner.Operator) 561 c.Assert(actualErr, gc.Equals, expectErr) 562 c.Assert(ctx.flushBadge, gc.Equals, "run commands") 563 c.Assert(ctx.flushFailure, gc.IsNil) 564 s.assertRecordedPid(c, ctx.expectPid) 565 } 566 567 func (s *RunMockContextSuite) TestRunCommandsFlushFailure(c *gc.C) { 568 expectErr := errors.New("pew pew pew") 569 ctx := &MockContext{ 570 flushResult: expectErr, 571 } 572 _, actualErr := runner.NewRunner(ctx, s.paths, nil).RunCommands(echoPidScript+"; exit 123", runner.Operator) 573 c.Assert(actualErr, gc.Equals, expectErr) 574 c.Assert(ctx.flushBadge, gc.Equals, "run commands") 575 c.Assert(ctx.flushFailure, gc.IsNil) // exit code in _ result, as tested elsewhere 576 s.assertRecordedPid(c, ctx.expectPid) 577 } 578 579 func (s *RunMockContextSuite) TestRunCommandsFlushSuccessWorkloadNoExec(c *gc.C) { 580 expectErr := errors.New("pew pew pew") 581 ctx := &MockContext{ 582 flushResult: expectErr, 583 modelType: model.CAAS, 584 } 585 _, actualErr := runner.NewRunner(ctx, s.paths, nil).RunCommands(echoPidScript, runner.Workload) 586 c.Assert(actualErr, gc.Equals, expectErr) 587 c.Assert(ctx.flushBadge, gc.Equals, "run commands") 588 c.Assert(ctx.flushFailure, gc.IsNil) 589 s.assertRecordedPid(c, ctx.expectPid) 590 } 591 592 func (s *RunMockContextSuite) TestRunCommandsFlushFailureWorkloadNoExec(c *gc.C) { 593 expectErr := errors.New("pew pew pew") 594 ctx := &MockContext{ 595 flushResult: expectErr, 596 modelType: model.CAAS, 597 } 598 _, actualErr := runner.NewRunner(ctx, s.paths, nil).RunCommands(echoPidScript+"; exit 123", runner.Workload) 599 c.Assert(actualErr, gc.Equals, expectErr) 600 c.Assert(ctx.flushBadge, gc.Equals, "run commands") 601 c.Assert(ctx.flushFailure, gc.IsNil) // exit code in _ result, as tested elsewhere 602 s.assertRecordedPid(c, ctx.expectPid) 603 } 604 605 func (s *RunMockContextSuite) TestRunCommandsFlushSuccessWorkload(c *gc.C) { 606 ctx := &MockContext{ 607 modelType: model.CAAS, 608 } 609 execCount := 0 610 execFunc := func(params runner.ExecParams) (*exec.ExecResponse, error) { 611 execCount++ 612 switch execCount { 613 case 1: 614 return &exec.ExecResponse{}, nil 615 case 2: 616 return &exec.ExecResponse{}, nil 617 } 618 c.Fatal("invalid count") 619 return nil, nil 620 } 621 _, actualErr := runner.NewRunner(ctx, s.paths, execFunc).RunCommands(echoPidScript, runner.Workload) 622 c.Assert(execCount, gc.Equals, 2) 623 c.Assert(actualErr, jc.ErrorIsNil) 624 c.Assert(ctx.flushBadge, gc.Equals, "run commands") 625 c.Assert(ctx.flushFailure, jc.ErrorIsNil) 626 } 627 628 func (s *RunMockContextSuite) TestRunCommandsFlushFailedWorkload(c *gc.C) { 629 expectErr := errors.New("pew pew pew") 630 ctx := &MockContext{ 631 flushResult: expectErr, 632 modelType: model.CAAS, 633 } 634 execCount := 0 635 execFunc := func(params runner.ExecParams) (*exec.ExecResponse, error) { 636 execCount++ 637 switch execCount { 638 case 1: 639 return &exec.ExecResponse{}, nil 640 case 2: 641 return nil, errors.Errorf("failed exec") 642 } 643 c.Fatal("invalid count") 644 return nil, nil 645 } 646 _, actualErr := runner.NewRunner(ctx, s.paths, execFunc).RunCommands(echoPidScript, runner.Workload) 647 c.Assert(execCount, gc.Equals, 2) 648 c.Assert(actualErr, gc.Equals, expectErr) 649 c.Assert(ctx.flushBadge, gc.Equals, "run commands") 650 c.Assert(ctx.flushFailure, gc.ErrorMatches, "failed exec") 651 } 652 653 func (s *RunMockContextSuite) TestRunActionCAASSuccess(c *gc.C) { 654 params := map[string]interface{}{ 655 "command": "echo 1", 656 "timeout": 0, 657 "workload-context": true, 658 } 659 ctx := &MockContext{ 660 modelType: model.CAAS, 661 actionData: &context.ActionData{ 662 Params: params, 663 }, 664 actionParams: params, 665 actionResults: map[string]interface{}{}, 666 } 667 execCount := 0 668 execFunc := func(params runner.ExecParams) (*exec.ExecResponse, error) { 669 execCount++ 670 switch execCount { 671 case 1: 672 return &exec.ExecResponse{}, nil 673 case 2: 674 return &exec.ExecResponse{ 675 Stdout: bytes.NewBufferString("1").Bytes(), 676 }, nil 677 } 678 c.Fatal("invalid count") 679 return nil, nil 680 } 681 _, err := runner.NewRunner(ctx, s.paths, execFunc).RunAction("juju-exec") 682 c.Assert(execCount, gc.Equals, 2) 683 c.Assert(err, jc.ErrorIsNil) 684 c.Assert(ctx.flushBadge, gc.Equals, "juju-exec") 685 c.Assert(ctx.actionResults["return-code"], gc.Equals, 0) 686 c.Assert(strings.TrimRight(ctx.actionResults["stdout"].(string), "\r\n"), gc.Equals, "1") 687 c.Assert(ctx.actionResults["stderr"], gc.Equals, nil) 688 } 689 690 func (s *RunMockContextSuite) TestRunActionCAASCorrectEnv(c *gc.C) { 691 params := map[string]interface{}{ 692 "command": "echo 1", 693 "timeout": 0, 694 "workload-context": true, 695 } 696 ctx := &MockContext{ 697 modelType: model.CAAS, 698 actionData: &context.ActionData{ 699 Params: params, 700 }, 701 actionParams: params, 702 actionResults: map[string]interface{}{}, 703 } 704 execCount := 0 705 execFunc := func(params runner.ExecParams) (*exec.ExecResponse, error) { 706 execCount++ 707 switch execCount { 708 case 1: 709 c.Assert(params.Commands, gc.DeepEquals, []string{"unset _; export"}) 710 return &exec.ExecResponse{ 711 Stdout: []byte(` 712 export BLA='bla' 713 export PATH='important-path' 714 `[1:]), 715 }, nil 716 case 2: 717 path := "" 718 for _, v := range params.Env { 719 if strings.HasPrefix(v, "PATH=") { 720 path = v 721 } 722 } 723 c.Assert(path, gc.Equals, "PATH=pathypathpath;important-path") 724 return &exec.ExecResponse{ 725 Stdout: bytes.NewBufferString("1").Bytes(), 726 }, nil 727 } 728 c.Fatal("invalid count") 729 return nil, nil 730 } 731 _, err := runner.NewRunner(ctx, s.paths, execFunc).RunAction("juju-exec") 732 c.Assert(execCount, gc.Equals, 2) 733 c.Assert(err, jc.ErrorIsNil) 734 c.Assert(ctx.flushBadge, gc.Equals, "juju-exec") 735 c.Assert(ctx.actionResults["return-code"], gc.Equals, 0) 736 c.Assert(strings.TrimRight(ctx.actionResults["stdout"].(string), "\r\n"), gc.Equals, "1") 737 c.Assert(ctx.actionResults["stderr"], gc.Equals, nil) 738 } 739 740 func (s *RunMockContextSuite) TestRunActionOnWorkloadIgnoredIAAS(c *gc.C) { 741 params := map[string]interface{}{ 742 "command": "echo 1", 743 "timeout": 0, 744 "workload-context": true, 745 } 746 ctx := &MockContext{ 747 modelType: model.IAAS, 748 actionData: &context.ActionData{ 749 Params: params, 750 }, 751 actionParams: params, 752 actionResults: map[string]interface{}{}, 753 } 754 _, err := runner.NewRunner(ctx, s.paths, nil).RunAction("juju-exec") 755 c.Assert(err, jc.ErrorIsNil) 756 c.Assert(ctx.flushBadge, gc.Equals, "juju-exec") 757 c.Assert(ctx.flushFailure, gc.IsNil) 758 c.Assert(ctx.actionResults["return-code"], gc.Equals, 0) 759 c.Assert(strings.TrimRight(ctx.actionResults["stdout"].(string), "\r\n"), gc.Equals, "1") 760 c.Assert(ctx.actionResults["stderr"], gc.Equals, nil) 761 } 762 763 func (s *RunMockContextSuite) TestOperatorActionCAASSuccess(c *gc.C) { 764 expectErr := errors.New("pew pew pew") 765 params := map[string]interface{}{ 766 "workload-context": false, 767 } 768 ctx := &MockContext{ 769 modelType: model.CAAS, 770 actionData: &context.ActionData{ 771 Params: params, 772 }, 773 actionParams: params, 774 actionResults: map[string]interface{}{}, 775 flushResult: expectErr} 776 makeCharm(c, hookSpec{ 777 dir: "actions", 778 name: hookName, 779 perm: 0700, 780 stdout: "hello", 781 stderr: "world", 782 }, s.paths.GetCharmDir()) 783 hookType, actualErr := runner.NewRunner(ctx, s.paths, nil).RunAction("something-happened") 784 c.Assert(actualErr, gc.Equals, expectErr) 785 c.Assert(hookType, gc.Equals, runner.ExplicitHookHandler) 786 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 787 c.Assert(ctx.flushFailure, gc.IsNil) 788 s.assertRecordedPid(c, ctx.expectPid) 789 c.Assert(ctx.actionResults, jc.DeepEquals, map[string]interface{}{ 790 "return-code": 0, "stderr": "world\n", "stdout": "hello\n", 791 }) 792 }