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