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