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