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  }