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