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  }