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