github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/jujud/run_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/juju/cmd"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/mutex"
    17  	jc "github.com/juju/testing/checkers"
    18  	"github.com/juju/utils"
    19  	"github.com/juju/utils/clock"
    20  	"github.com/juju/utils/exec"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/juju/names.v2"
    23  
    24  	cmdutil "github.com/juju/juju/cmd/jujud/util"
    25  	"github.com/juju/juju/testing"
    26  	"github.com/juju/juju/worker/uniter"
    27  	jujuos "github.com/juju/utils/os"
    28  )
    29  
    30  const testLockName = "juju-run-test"
    31  
    32  type RunTestSuite struct {
    33  	testing.BaseSuite
    34  }
    35  
    36  func (s *RunTestSuite) SetUpTest(c *gc.C) {
    37  	s.BaseSuite.SetUpTest(c)
    38  	s.PatchValue(&cmdutil.DataDir, c.MkDir())
    39  }
    40  
    41  var _ = gc.Suite(&RunTestSuite{})
    42  
    43  func (*RunTestSuite) TestArgParsing(c *gc.C) {
    44  	for i, test := range []struct {
    45  		title           string
    46  		args            []string
    47  		errMatch        string
    48  		unit            names.UnitTag
    49  		commands        string
    50  		avoidContext    bool
    51  		relationId      string
    52  		remoteUnit      string
    53  		forceRemoteUnit bool
    54  	}{{
    55  		title:    "no args",
    56  		errMatch: "missing unit-name",
    57  	}, {
    58  		title:    "one arg",
    59  		args:     []string{"foo"},
    60  		errMatch: `"foo" is not a valid tag`,
    61  	}, {
    62  		title:    "one arg",
    63  		args:     []string{"foo/2"},
    64  		errMatch: "missing commands",
    65  	}, {
    66  		title:    "more than two arg",
    67  		args:     []string{"foo/2", "bar", "baz"},
    68  		errMatch: `unrecognized args: \["baz"\]`,
    69  	}, {
    70  		title:      "unit and command assignment",
    71  		args:       []string{"unit-name-2", "command"},
    72  		unit:       names.NewUnitTag("name/2"),
    73  		commands:   "command",
    74  		relationId: "",
    75  	}, {
    76  		title:      "unit id converted to tag",
    77  		args:       []string{"foo/1", "command"},
    78  		unit:       names.NewUnitTag("foo/1"),
    79  		commands:   "command",
    80  		relationId: "",
    81  	}, {
    82  		title:           "execute not in a context",
    83  		args:            []string{"--no-context", "command"},
    84  		commands:        "command",
    85  		avoidContext:    true,
    86  		relationId:      "",
    87  		forceRemoteUnit: false,
    88  	}, {
    89  		title:           "relation-id",
    90  		args:            []string{"--relation", "db:1", "unit-name-2", "command"},
    91  		commands:        "command",
    92  		unit:            names.NewUnitTag("name/2"),
    93  		relationId:      "db:1",
    94  		remoteUnit:      "",
    95  		avoidContext:    false,
    96  		forceRemoteUnit: false,
    97  	}, {
    98  		title:           "remote-unit",
    99  		args:            []string{"--remote-unit", "unit-name-1", "unit-name-2", "command"},
   100  		commands:        "command",
   101  		unit:            names.NewUnitTag("name/2"),
   102  		avoidContext:    false,
   103  		relationId:      "",
   104  		remoteUnit:      "unit-name-1",
   105  		forceRemoteUnit: false,
   106  	}, {
   107  		title:           "no-remote-unit",
   108  		args:            []string{"--force-remote-unit", "--relation", "mongodb:1", "unit-name-2", "command"},
   109  		commands:        "command",
   110  		unit:            names.NewUnitTag("name/2"),
   111  		relationId:      "mongodb:1",
   112  		forceRemoteUnit: true,
   113  	},
   114  	} {
   115  		c.Logf("%d: %s", i, test.title)
   116  		runCommand := &RunCommand{}
   117  		err := testing.InitCommand(runCommand, test.args)
   118  		if test.errMatch == "" {
   119  			c.Assert(err, jc.ErrorIsNil)
   120  			c.Assert(runCommand.unit, gc.Equals, test.unit)
   121  			c.Assert(runCommand.commands, gc.Equals, test.commands)
   122  			c.Assert(runCommand.noContext, gc.Equals, test.avoidContext)
   123  			c.Assert(runCommand.relationId, gc.Equals, test.relationId)
   124  			c.Assert(runCommand.remoteUnitName, gc.Equals, test.remoteUnit)
   125  			c.Assert(runCommand.forceRemoteUnit, gc.Equals, test.forceRemoteUnit)
   126  		} else {
   127  			c.Assert(err, gc.ErrorMatches, test.errMatch)
   128  		}
   129  	}
   130  }
   131  
   132  func (s *RunTestSuite) runCommand() *RunCommand {
   133  	return &RunCommand{
   134  		MachineLockName: testLockName,
   135  	}
   136  }
   137  
   138  func (s *RunTestSuite) TestInsideContext(c *gc.C) {
   139  	s.PatchEnvironment("JUJU_CONTEXT_ID", "fake-id")
   140  	runCommand := s.runCommand()
   141  	err := runCommand.Init([]string{"foo", "bar"})
   142  	c.Assert(err, gc.ErrorMatches, "juju-run cannot be called from within a hook.*")
   143  }
   144  
   145  func (s *RunTestSuite) TestMissingAgentName(c *gc.C) {
   146  	_, err := testing.RunCommand(c, s.runCommand(), "foo/2", "bar")
   147  	c.Assert(err, gc.ErrorMatches, `unit "foo/2" not found on this machine`)
   148  }
   149  
   150  func (s *RunTestSuite) TestMissingAgentTag(c *gc.C) {
   151  	_, err := testing.RunCommand(c, s.runCommand(), "unit-foo-2", "bar")
   152  	c.Assert(err, gc.ErrorMatches, `unit "foo/2" not found on this machine`)
   153  }
   154  
   155  func waitForResult(running <-chan *cmd.Context, timeout time.Duration) (*cmd.Context, error) {
   156  	select {
   157  	case result := <-running:
   158  		return result, nil
   159  	case <-time.After(timeout):
   160  		return nil, fmt.Errorf("timeout")
   161  	}
   162  }
   163  
   164  func (s *RunTestSuite) startRunAsync(c *gc.C, params []string) <-chan *cmd.Context {
   165  	resultChannel := make(chan *cmd.Context)
   166  	go func() {
   167  		ctx, err := testing.RunCommand(c, s.runCommand(), params...)
   168  		c.Assert(err, jc.Satisfies, cmd.IsRcPassthroughError)
   169  		c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 0")
   170  		resultChannel <- ctx
   171  		close(resultChannel)
   172  	}()
   173  	return resultChannel
   174  }
   175  
   176  func (s *RunTestSuite) TestNoContext(c *gc.C) {
   177  	ctx, err := testing.RunCommand(c, s.runCommand(), "--no-context", "echo done")
   178  	c.Assert(err, jc.Satisfies, cmd.IsRcPassthroughError)
   179  	c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 0")
   180  	c.Assert(strings.TrimRight(testing.Stdout(ctx), "\r\n"), gc.Equals, "done")
   181  }
   182  
   183  func (s *RunTestSuite) TestNoContextAsync(c *gc.C) {
   184  	channel := s.startRunAsync(c, []string{"--no-context", "echo done"})
   185  	ctx, err := waitForResult(channel, testing.LongWait)
   186  	c.Assert(err, jc.ErrorIsNil)
   187  	c.Assert(strings.TrimRight(testing.Stdout(ctx), "\r\n"), gc.Equals, "done")
   188  }
   189  
   190  func (s *RunTestSuite) TestNoContextWithLock(c *gc.C) {
   191  	spec := mutex.Spec{
   192  		Name:  testLockName,
   193  		Clock: clock.WallClock,
   194  		Delay: 250 * time.Millisecond,
   195  	}
   196  	releaser, err := mutex.Acquire(spec)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	defer releaser.Release() // in case of failure
   199  
   200  	channel := s.startRunAsync(c, []string{"--no-context", "echo done"})
   201  	ctx, err := waitForResult(channel, testing.ShortWait)
   202  	c.Assert(err, gc.ErrorMatches, "timeout")
   203  
   204  	releaser.Release()
   205  
   206  	ctx, err = waitForResult(channel, testing.LongWait)
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	c.Assert(strings.TrimRight(testing.Stdout(ctx), "\r\n"), gc.Equals, "done")
   209  }
   210  
   211  func (s *RunTestSuite) TestMissingSocket(c *gc.C) {
   212  	if runtime.GOOS == "windows" {
   213  		c.Skip("Current implementation of named pipes loops if the socket is missing")
   214  	}
   215  	agentDir := filepath.Join(cmdutil.DataDir, "agents", "unit-foo-1")
   216  	err := os.MkdirAll(agentDir, 0755)
   217  	c.Assert(err, jc.ErrorIsNil)
   218  
   219  	_, err = testing.RunCommand(c, s.runCommand(), "foo/1", "bar")
   220  	c.Assert(err, gc.ErrorMatches, `dial unix .*/run.socket:.*`+utils.NoSuchFileErrRegexp)
   221  }
   222  
   223  func (s *RunTestSuite) TestRunning(c *gc.C) {
   224  	loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE)
   225  	s.runListenerForAgent(c, "unit-foo-1")
   226  
   227  	ctx, err := testing.RunCommand(c, s.runCommand(), "foo/1", "bar")
   228  	c.Check(cmd.IsRcPassthroughError(err), jc.IsTrue)
   229  	c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 42")
   230  	c.Assert(testing.Stdout(ctx), gc.Equals, "bar stdout")
   231  	c.Assert(testing.Stderr(ctx), gc.Equals, "bar stderr")
   232  }
   233  
   234  func (s *RunTestSuite) TestRunningRelation(c *gc.C) {
   235  	loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE)
   236  	s.runListenerForAgent(c, "unit-foo-1")
   237  
   238  	ctx, err := testing.RunCommand(c, s.runCommand(), "--relation", "db:1", "foo/1", "bar")
   239  	c.Check(cmd.IsRcPassthroughError(err), jc.IsTrue)
   240  	c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 42")
   241  	c.Assert(testing.Stdout(ctx), gc.Equals, "bar stdout")
   242  	c.Assert(testing.Stderr(ctx), gc.Equals, "bar stderr")
   243  }
   244  
   245  func (s *RunTestSuite) TestRunningBadRelation(c *gc.C) {
   246  	loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE)
   247  	s.runListenerForAgent(c, "unit-foo-1")
   248  
   249  	_, err := testing.RunCommand(c, s.runCommand(), "--relation", "badrelation:W", "foo/1", "bar")
   250  	c.Check(cmd.IsRcPassthroughError(err), jc.IsFalse)
   251  	c.Assert(err, gc.ErrorMatches, "invalid relation id")
   252  }
   253  
   254  func (s *RunTestSuite) TestRunningRemoteUnitNoRelation(c *gc.C) {
   255  	loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE)
   256  	s.runListenerForAgent(c, "unit-foo-1")
   257  
   258  	_, err := testing.RunCommand(c, s.runCommand(), "--remote-unit", "remote/0", "foo/1", "bar")
   259  	c.Check(cmd.IsRcPassthroughError(err), jc.IsFalse)
   260  	c.Assert(err, gc.ErrorMatches, "remote unit: remote/0, provided without a relation")
   261  }
   262  
   263  func (s *RunTestSuite) TestSkipCheckAndRemoteUnit(c *gc.C) {
   264  	loggo.GetLogger("worker.uniter").SetLogLevel(loggo.TRACE)
   265  	s.runListenerForAgent(c, "unit-foo-1")
   266  
   267  	ctx, err := testing.RunCommand(c, s.runCommand(), "--force-remote-unit", "--remote-unit", "unit-name-2", "--relation", "db:1", "foo/1", "bar")
   268  	c.Check(cmd.IsRcPassthroughError(err), jc.IsTrue)
   269  	c.Assert(err, gc.ErrorMatches, "subprocess encountered error code 42")
   270  	c.Assert(testing.Stdout(ctx), gc.Equals, "bar stdout")
   271  	c.Assert(testing.Stderr(ctx), gc.Equals, "bar stderr")
   272  }
   273  
   274  func (s *RunTestSuite) TestCheckRelationIdValid(c *gc.C) {
   275  	for i, test := range []struct {
   276  		title  string
   277  		input  string
   278  		output int
   279  		err    bool
   280  	}{
   281  		{
   282  			title:  "valid, id only",
   283  			input:  "0",
   284  			output: 0,
   285  			err:    false,
   286  		}, {
   287  			title:  "valid, relation:id",
   288  			input:  "db:1",
   289  			output: 1,
   290  			err:    false,
   291  		}, {
   292  			title:  "not valid, just relation",
   293  			input:  "db",
   294  			output: -1,
   295  			err:    true,
   296  		}, {
   297  			title:  "not valud, malformed relation:id",
   298  			input:  "db:X",
   299  			output: -1,
   300  			err:    true,
   301  		},
   302  	} {
   303  		c.Logf("%d: %s", i, test.title)
   304  		relationId, err := checkRelationId(test.input)
   305  		c.Assert(relationId, gc.Equals, test.output)
   306  		if test.err {
   307  			c.Assert(err, gc.NotNil)
   308  		}
   309  	}
   310  }
   311  
   312  func (s *RunTestSuite) runListenerForAgent(c *gc.C, agent string) {
   313  	agentDir := filepath.Join(cmdutil.DataDir, "agents", agent)
   314  	err := os.MkdirAll(agentDir, 0755)
   315  	c.Assert(err, jc.ErrorIsNil)
   316  	var socketPath string
   317  	switch jujuos.HostOS() {
   318  	case jujuos.Windows:
   319  		socketPath = fmt.Sprintf(`\\.\pipe\%s-run`, agent)
   320  	default:
   321  		socketPath = fmt.Sprintf("%s/run.socket", agentDir)
   322  	}
   323  	listener, err := uniter.NewRunListener(uniter.RunListenerConfig{
   324  		SocketPath:    socketPath,
   325  		CommandRunner: &mockRunner{c},
   326  	})
   327  	c.Assert(err, jc.ErrorIsNil)
   328  	c.Assert(listener, gc.NotNil)
   329  	s.AddCleanup(func(*gc.C) {
   330  		c.Assert(listener.Close(), jc.ErrorIsNil)
   331  	})
   332  }
   333  
   334  type mockRunner struct {
   335  	c *gc.C
   336  }
   337  
   338  var _ uniter.CommandRunner = (*mockRunner)(nil)
   339  
   340  func (r *mockRunner) RunCommands(args uniter.RunCommandsArgs) (results *exec.ExecResponse, err error) {
   341  	r.c.Log("mock runner: " + args.Commands)
   342  	return &exec.ExecResponse{
   343  		Code:   42,
   344  		Stdout: []byte(args.Commands + " stdout"),
   345  		Stderr: []byte(args.Commands + " stderr"),
   346  	}, nil
   347  }