github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "fmt" 8 "io/ioutil" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "time" 13 14 "github.com/juju/errors" 15 "github.com/juju/proxy" 16 envtesting "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/utils/exec" 19 gc "gopkg.in/check.v1" 20 "gopkg.in/juju/charm.v6/hooks" 21 22 "github.com/juju/juju/worker/common/charmrunner" 23 "github.com/juju/juju/worker/uniter/hook" 24 "github.com/juju/juju/worker/uniter/runner" 25 "github.com/juju/juju/worker/uniter/runner/context" 26 runnertesting "github.com/juju/juju/worker/uniter/runner/testing" 27 ) 28 29 type RunCommandSuite struct { 30 ContextSuite 31 } 32 33 var _ = gc.Suite(&RunCommandSuite{}) 34 35 var noProxies = proxy.Settings{} 36 37 func (s *RunCommandSuite) TestRunCommandsEnvStdOutAndErrAndRC(c *gc.C) { 38 // TODO(bogdanteleaga): powershell throws another exit status code when 39 // outputting to stderr using Write-Error. Either find another way to 40 // output to stderr or change the checks 41 if runtime.GOOS == "windows" { 42 c.Skip("bug 1403084: Have to figure out a good way to output to stderr from powershell") 43 } 44 ctx, err := s.contextFactory.HookContext(hook.Info{Kind: hooks.ConfigChanged}) 45 c.Assert(err, jc.ErrorIsNil) 46 paths := runnertesting.NewRealPaths(c) 47 runner := runner.NewRunner(ctx, paths) 48 49 commands := ` 50 echo $JUJU_CHARM_DIR 51 echo this is standard err >&2 52 exit 42 53 ` 54 result, err := runner.RunCommands(commands) 55 c.Assert(err, jc.ErrorIsNil) 56 57 c.Assert(result.Code, gc.Equals, 42) 58 c.Assert(strings.TrimRight(string(result.Stdout), "\r\n"), gc.Equals, paths.GetCharmDir()) 59 c.Assert(strings.TrimRight(string(result.Stderr), "\r\n"), gc.Equals, "this is standard err") 60 c.Assert(ctx.GetProcess(), gc.NotNil) 61 } 62 63 type RunHookSuite struct { 64 ContextSuite 65 } 66 67 var _ = gc.Suite(&RunHookSuite{}) 68 69 // LineBufferSize matches the constant used when creating 70 // the bufio line reader. 71 const lineBufferSize = 4096 72 73 var runHookTests = []struct { 74 summary string 75 relid int 76 remote string 77 spec hookSpec 78 err string 79 }{ 80 { 81 summary: "missing hook is not an error", 82 relid: -1, 83 }, { 84 summary: "report error indicated by hook's exit status", 85 relid: -1, 86 spec: hookSpec{ 87 perm: 0700, 88 code: 99, 89 }, 90 err: "exit status 99", 91 }, { 92 summary: "output logging", 93 relid: -1, 94 spec: hookSpec{ 95 perm: 0700, 96 stdout: "stdout", 97 stderr: "stderr", 98 }, 99 }, { 100 summary: "output logging with background process", 101 relid: -1, 102 spec: hookSpec{ 103 perm: 0700, 104 stdout: "stdout", 105 background: "not printed", 106 }, 107 }, { 108 summary: "long line split", 109 relid: -1, 110 spec: hookSpec{ 111 perm: 0700, 112 stdout: strings.Repeat("a", lineBufferSize+10), 113 }, 114 }, 115 } 116 117 func (s *RunHookSuite) TestRunHook(c *gc.C) { 118 for i, t := range runHookTests { 119 c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm) 120 ctx, err := s.contextFactory.HookContext(hook.Info{Kind: hooks.ConfigChanged}) 121 c.Assert(err, jc.ErrorIsNil) 122 123 paths := runnertesting.NewRealPaths(c) 124 rnr := runner.NewRunner(ctx, paths) 125 var hookExists bool 126 if t.spec.perm != 0 { 127 spec := t.spec 128 spec.dir = "hooks" 129 spec.name = hookName 130 c.Logf("makeCharm %#v", spec) 131 makeCharm(c, spec, paths.GetCharmDir()) 132 hookExists = true 133 } 134 t0 := time.Now() 135 err = rnr.RunHook("something-happened") 136 if t.err == "" && hookExists { 137 c.Assert(err, jc.ErrorIsNil) 138 } else if !hookExists { 139 c.Assert(charmrunner.IsMissingHookError(err), jc.IsTrue) 140 } else { 141 c.Assert(err, gc.ErrorMatches, t.err) 142 } 143 if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second { 144 c.Errorf("background process holding up hook execution") 145 } 146 } 147 } 148 149 type MockContext struct { 150 runner.Context 151 actionData *context.ActionData 152 actionParams map[string]interface{} 153 actionParamsErr error 154 actionResults map[string]interface{} 155 expectPid int 156 flushBadge string 157 flushFailure error 158 flushResult error 159 } 160 161 func (ctx *MockContext) UnitName() string { 162 return "some-unit/999" 163 } 164 165 func (ctx *MockContext) HookVars(paths context.Paths) ([]string, error) { 166 return []string{"VAR=value"}, nil 167 } 168 169 func (ctx *MockContext) ActionData() (*context.ActionData, error) { 170 if ctx.actionData == nil { 171 return nil, errors.New("blam") 172 } 173 return ctx.actionData, nil 174 } 175 176 func (ctx *MockContext) SetProcess(process context.HookProcess) { 177 ctx.expectPid = process.Pid() 178 } 179 180 func (ctx *MockContext) Prepare() error { 181 return nil 182 } 183 184 func (ctx *MockContext) Flush(badge string, failure error) error { 185 ctx.flushBadge = badge 186 ctx.flushFailure = failure 187 return ctx.flushResult 188 } 189 190 func (ctx *MockContext) ActionParams() (map[string]interface{}, error) { 191 return ctx.actionParams, ctx.actionParamsErr 192 } 193 194 func (ctx *MockContext) UpdateActionResults(keys []string, value string) error { 195 for _, key := range keys { 196 ctx.actionResults[key] = value 197 } 198 return nil 199 } 200 201 type RunMockContextSuite struct { 202 envtesting.IsolationSuite 203 paths runnertesting.RealPaths 204 } 205 206 var _ = gc.Suite(&RunMockContextSuite{}) 207 208 func (s *RunMockContextSuite) SetUpTest(c *gc.C) { 209 s.IsolationSuite.SetUpTest(c) 210 s.paths = runnertesting.NewRealPaths(c) 211 } 212 213 func (s *RunMockContextSuite) assertRecordedPid(c *gc.C, expectPid int) { 214 path := filepath.Join(s.paths.GetCharmDir(), "pid") 215 content, err := ioutil.ReadFile(path) 216 c.Assert(err, jc.ErrorIsNil) 217 expectContent := fmt.Sprintf("%d", expectPid) 218 c.Assert(strings.TrimRight(string(content), "\r\n"), gc.Equals, expectContent) 219 } 220 221 func (s *RunMockContextSuite) TestRunHookFlushSuccess(c *gc.C) { 222 expectErr := errors.New("pew pew pew") 223 ctx := &MockContext{ 224 flushResult: expectErr, 225 } 226 makeCharm(c, hookSpec{ 227 dir: "hooks", 228 name: hookName, 229 perm: 0700, 230 }, s.paths.GetCharmDir()) 231 actualErr := runner.NewRunner(ctx, s.paths).RunHook("something-happened") 232 c.Assert(actualErr, gc.Equals, expectErr) 233 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 234 c.Assert(ctx.flushFailure, gc.IsNil) 235 s.assertRecordedPid(c, ctx.expectPid) 236 } 237 238 func (s *RunMockContextSuite) TestRunHookFlushFailure(c *gc.C) { 239 expectErr := errors.New("pew pew pew") 240 ctx := &MockContext{ 241 flushResult: expectErr, 242 } 243 makeCharm(c, hookSpec{ 244 dir: "hooks", 245 name: hookName, 246 perm: 0700, 247 code: 123, 248 }, s.paths.GetCharmDir()) 249 actualErr := runner.NewRunner(ctx, s.paths).RunHook("something-happened") 250 c.Assert(actualErr, gc.Equals, expectErr) 251 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 252 c.Assert(ctx.flushFailure, gc.ErrorMatches, "exit status 123") 253 s.assertRecordedPid(c, ctx.expectPid) 254 } 255 256 func (s *RunMockContextSuite) TestRunActionFlushSuccess(c *gc.C) { 257 expectErr := errors.New("pew pew pew") 258 ctx := &MockContext{ 259 flushResult: expectErr, 260 actionData: &context.ActionData{}, 261 } 262 makeCharm(c, hookSpec{ 263 dir: "actions", 264 name: hookName, 265 perm: 0700, 266 }, s.paths.GetCharmDir()) 267 actualErr := runner.NewRunner(ctx, s.paths).RunAction("something-happened") 268 c.Assert(actualErr, gc.Equals, expectErr) 269 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 270 c.Assert(ctx.flushFailure, gc.IsNil) 271 s.assertRecordedPid(c, ctx.expectPid) 272 } 273 274 func (s *RunMockContextSuite) TestRunActionFlushFailure(c *gc.C) { 275 expectErr := errors.New("pew pew pew") 276 ctx := &MockContext{ 277 flushResult: expectErr, 278 actionData: &context.ActionData{}, 279 } 280 makeCharm(c, hookSpec{ 281 dir: "actions", 282 name: hookName, 283 perm: 0700, 284 code: 123, 285 }, s.paths.GetCharmDir()) 286 actualErr := runner.NewRunner(ctx, s.paths).RunAction("something-happened") 287 c.Assert(actualErr, gc.Equals, expectErr) 288 c.Assert(ctx.flushBadge, gc.Equals, "something-happened") 289 c.Assert(ctx.flushFailure, gc.ErrorMatches, "exit status 123") 290 s.assertRecordedPid(c, ctx.expectPid) 291 } 292 293 func (s *RunMockContextSuite) TestRunActionParamsFailure(c *gc.C) { 294 expectErr := errors.New("stork") 295 ctx := &MockContext{ 296 actionData: &context.ActionData{}, 297 actionParamsErr: expectErr, 298 } 299 actualErr := runner.NewRunner(ctx, s.paths).RunAction("juju-run") 300 c.Assert(errors.Cause(actualErr), gc.Equals, expectErr) 301 } 302 303 func (s *RunMockContextSuite) TestRunActionSuccessful(c *gc.C) { 304 ctx := &MockContext{ 305 actionData: &context.ActionData{}, 306 actionParams: map[string]interface{}{ 307 "command": "echo 1", 308 "timeout": 0, 309 }, 310 actionResults: map[string]interface{}{}, 311 } 312 err := runner.NewRunner(ctx, s.paths).RunAction("juju-run") 313 c.Assert(err, jc.ErrorIsNil) 314 c.Assert(ctx.flushBadge, gc.Equals, "juju-run") 315 c.Assert(ctx.flushFailure, gc.IsNil) 316 c.Assert(ctx.actionResults["Code"], gc.Equals, "0") 317 c.Assert(strings.TrimRight(ctx.actionResults["Stdout"].(string), "\r\n"), gc.Equals, "1") 318 c.Assert(ctx.actionResults["Stderr"], gc.Equals, "") 319 } 320 321 func (s *RunMockContextSuite) TestRunActionCancelled(c *gc.C) { 322 timeout := 1 * time.Nanosecond 323 ctx := &MockContext{ 324 actionData: &context.ActionData{}, 325 actionParams: map[string]interface{}{ 326 "command": "sleep 10", 327 "timeout": float64(timeout.Nanoseconds()), 328 }, 329 actionResults: map[string]interface{}{}, 330 } 331 err := runner.NewRunner(ctx, s.paths).RunAction("juju-run") 332 c.Assert(err, jc.ErrorIsNil) 333 c.Assert(ctx.flushBadge, gc.Equals, "juju-run") 334 c.Assert(ctx.flushFailure, gc.Equals, exec.ErrCancelled) 335 c.Assert(ctx.actionResults["Code"], gc.Equals, nil) 336 c.Assert(ctx.actionResults["Stdout"], gc.Equals, nil) 337 c.Assert(ctx.actionResults["Stderr"], gc.Equals, nil) 338 } 339 340 func (s *RunMockContextSuite) TestRunCommandsFlushSuccess(c *gc.C) { 341 expectErr := errors.New("pew pew pew") 342 ctx := &MockContext{ 343 flushResult: expectErr, 344 } 345 _, actualErr := runner.NewRunner(ctx, s.paths).RunCommands(echoPidScript) 346 c.Assert(actualErr, gc.Equals, expectErr) 347 c.Assert(ctx.flushBadge, gc.Equals, "run commands") 348 c.Assert(ctx.flushFailure, gc.IsNil) 349 s.assertRecordedPid(c, ctx.expectPid) 350 } 351 352 func (s *RunMockContextSuite) TestRunCommandsFlushFailure(c *gc.C) { 353 expectErr := errors.New("pew pew pew") 354 ctx := &MockContext{ 355 flushResult: expectErr, 356 } 357 _, actualErr := runner.NewRunner(ctx, s.paths).RunCommands(echoPidScript + "; exit 123") 358 c.Assert(actualErr, gc.Equals, expectErr) 359 c.Assert(ctx.flushBadge, gc.Equals, "run commands") 360 c.Assert(ctx.flushFailure, gc.IsNil) // exit code in _ result, as tested elsewhere 361 s.assertRecordedPid(c, ctx.expectPid) 362 }