github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/runner/jujuc/server_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Copyright 2014 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package jujuc_test
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/juju/cmd"
    19  	"github.com/juju/gnuflag"
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/juju/utils/exec"
    22  	gc "gopkg.in/check.v1"
    23  
    24  	jujucmd "github.com/juju/juju/cmd"
    25  	"github.com/juju/juju/juju/sockets"
    26  	"github.com/juju/juju/testing"
    27  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    28  )
    29  
    30  type RpcCommand struct {
    31  	cmd.CommandBase
    32  	Value string
    33  	Slow  bool
    34  	Echo  bool
    35  }
    36  
    37  func (c *RpcCommand) Info() *cmd.Info {
    38  	return jujucmd.Info(&cmd.Info{
    39  		Name:    "remote",
    40  		Purpose: "act at a distance",
    41  		Doc:     "blah doc",
    42  	})
    43  }
    44  
    45  func (c *RpcCommand) SetFlags(f *gnuflag.FlagSet) {
    46  	f.StringVar(&c.Value, "value", "", "doc")
    47  	f.BoolVar(&c.Slow, "slow", false, "doc")
    48  	f.BoolVar(&c.Echo, "echo", false, "doc")
    49  }
    50  
    51  func (c *RpcCommand) Init(args []string) error {
    52  	return cmd.CheckEmpty(args)
    53  }
    54  
    55  func (c *RpcCommand) Run(ctx *cmd.Context) error {
    56  	if c.Value == "error" {
    57  		return errors.New("blam")
    58  	}
    59  	if c.Slow {
    60  		time.Sleep(testing.ShortWait)
    61  		return nil
    62  	}
    63  	if c.Echo {
    64  		if _, err := io.Copy(ctx.Stdout, ctx.Stdin); err != nil {
    65  			return err
    66  		}
    67  	}
    68  	ctx.Stdout.Write([]byte("eye of newt\n"))
    69  	ctx.Stderr.Write([]byte("toe of frog\n"))
    70  	return ioutil.WriteFile(ctx.AbsPath("local"), []byte(c.Value), 0644)
    71  }
    72  
    73  func factory(contextId, cmdName string) (cmd.Command, error) {
    74  	if contextId != "validCtx" {
    75  		return nil, fmt.Errorf("unknown context %q", contextId)
    76  	}
    77  	if cmdName != "remote" {
    78  		return nil, fmt.Errorf("unknown command %q", cmdName)
    79  	}
    80  	return &RpcCommand{}, nil
    81  }
    82  
    83  type ServerSuite struct {
    84  	testing.BaseSuite
    85  	server   *jujuc.Server
    86  	sockPath string
    87  	err      chan error
    88  }
    89  
    90  var _ = gc.Suite(&ServerSuite{})
    91  
    92  func (s *ServerSuite) osDependentSockPath(c *gc.C) string {
    93  	pipeRoot := c.MkDir()
    94  	var sock string
    95  	if runtime.GOOS == "windows" {
    96  		sock = fmt.Sprintf(`\\.\pipe%s`, filepath.ToSlash(pipeRoot[2:]))
    97  	} else {
    98  		sock = filepath.Join(pipeRoot, "test.sock")
    99  	}
   100  	return sock
   101  }
   102  
   103  func (s *ServerSuite) SetUpTest(c *gc.C) {
   104  	s.BaseSuite.SetUpTest(c)
   105  	s.sockPath = s.osDependentSockPath(c)
   106  	srv, err := jujuc.NewServer(factory, s.sockPath)
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	c.Assert(srv, gc.NotNil)
   109  	s.server = srv
   110  	s.err = make(chan error)
   111  	go func() { s.err <- s.server.Run() }()
   112  }
   113  
   114  func (s *ServerSuite) TearDownTest(c *gc.C) {
   115  	s.server.Close()
   116  	c.Assert(<-s.err, gc.IsNil)
   117  	_, err := os.Open(s.sockPath)
   118  	c.Assert(err, jc.Satisfies, os.IsNotExist)
   119  	s.BaseSuite.TearDownTest(c)
   120  }
   121  
   122  func (s *ServerSuite) Call(c *gc.C, req jujuc.Request) (resp exec.ExecResponse, err error) {
   123  	client, err := sockets.Dial(s.sockPath)
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	defer client.Close()
   126  	err = client.Call("Jujuc.Main", req, &resp)
   127  	return resp, err
   128  }
   129  
   130  func (s *ServerSuite) TestHappyPath(c *gc.C) {
   131  	dir := c.MkDir()
   132  	resp, err := s.Call(c, jujuc.Request{
   133  		ContextId:   "validCtx",
   134  		Dir:         dir,
   135  		CommandName: "remote",
   136  		Args:        []string{"--value", "something", "--echo"},
   137  		StdinSet:    true,
   138  		Stdin:       []byte("wool of bat\n"),
   139  	})
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	c.Assert(resp.Code, gc.Equals, 0)
   142  	c.Assert(string(resp.Stdout), gc.Equals, "wool of bat\neye of newt\n")
   143  	c.Assert(string(resp.Stderr), gc.Equals, "toe of frog\n")
   144  	content, err := ioutil.ReadFile(filepath.Join(dir, "local"))
   145  	c.Assert(err, jc.ErrorIsNil)
   146  	c.Assert(string(content), gc.Equals, "something")
   147  }
   148  
   149  func (s *ServerSuite) TestNoStdin(c *gc.C) {
   150  	dir := c.MkDir()
   151  	_, err := s.Call(c, jujuc.Request{
   152  		ContextId:   "validCtx",
   153  		Dir:         dir,
   154  		CommandName: "remote",
   155  		Args:        []string{"--echo"},
   156  	})
   157  	c.Assert(err, gc.ErrorMatches, jujuc.ErrNoStdin.Error())
   158  }
   159  
   160  func (s *ServerSuite) TestLocks(c *gc.C) {
   161  	var wg sync.WaitGroup
   162  	t0 := time.Now()
   163  	for i := 0; i < 4; i++ {
   164  		wg.Add(1)
   165  		go func() {
   166  			dir := c.MkDir()
   167  			resp, err := s.Call(c, jujuc.Request{
   168  				ContextId:   "validCtx",
   169  				Dir:         dir,
   170  				CommandName: "remote",
   171  				Args:        []string{"--slow"},
   172  			})
   173  			c.Assert(err, jc.ErrorIsNil)
   174  			c.Assert(resp.Code, gc.Equals, 0)
   175  			wg.Done()
   176  		}()
   177  	}
   178  	wg.Wait()
   179  	t1 := time.Now()
   180  	c.Assert(t0.Add(4*testing.ShortWait).Before(t1), jc.IsTrue)
   181  }
   182  
   183  func (s *ServerSuite) TestBadCommandName(c *gc.C) {
   184  	dir := c.MkDir()
   185  	_, err := s.Call(c, jujuc.Request{
   186  		ContextId: "validCtx",
   187  		Dir:       dir,
   188  	})
   189  	c.Assert(err, gc.ErrorMatches, "bad request: command not specified")
   190  	_, err = s.Call(c, jujuc.Request{
   191  		ContextId:   "validCtx",
   192  		Dir:         dir,
   193  		CommandName: "witchcraft",
   194  	})
   195  	c.Assert(err, gc.ErrorMatches, `bad request: unknown command "witchcraft"`)
   196  }
   197  
   198  func (s *ServerSuite) TestBadDir(c *gc.C) {
   199  	for _, req := range []jujuc.Request{{
   200  		ContextId:   "validCtx",
   201  		CommandName: "anything",
   202  	}, {
   203  		ContextId:   "validCtx",
   204  		Dir:         "foo/bar",
   205  		CommandName: "anything",
   206  	}} {
   207  		_, err := s.Call(c, req)
   208  		c.Assert(err, gc.ErrorMatches, "bad request: Dir is not absolute")
   209  	}
   210  }
   211  
   212  func (s *ServerSuite) TestBadContextId(c *gc.C) {
   213  	_, err := s.Call(c, jujuc.Request{
   214  		ContextId:   "whatever",
   215  		Dir:         c.MkDir(),
   216  		CommandName: "remote",
   217  	})
   218  	c.Assert(err, gc.ErrorMatches, `bad request: unknown context "whatever"`)
   219  }
   220  
   221  func (s *ServerSuite) AssertBadCommand(c *gc.C, args []string, code int) exec.ExecResponse {
   222  	resp, err := s.Call(c, jujuc.Request{
   223  		ContextId:   "validCtx",
   224  		Dir:         c.MkDir(),
   225  		CommandName: args[0],
   226  		Args:        args[1:],
   227  	})
   228  	c.Assert(err, jc.ErrorIsNil)
   229  	c.Assert(resp.Code, gc.Equals, code)
   230  	return resp
   231  }
   232  
   233  func (s *ServerSuite) TestParseError(c *gc.C) {
   234  	resp := s.AssertBadCommand(c, []string{"remote", "--cheese"}, 2)
   235  	c.Assert(string(resp.Stdout), gc.Equals, "")
   236  	c.Assert(string(resp.Stderr), gc.Equals, "ERROR option provided but not defined: --cheese\n")
   237  }
   238  
   239  func (s *ServerSuite) TestBrokenCommand(c *gc.C) {
   240  	resp := s.AssertBadCommand(c, []string{"remote", "--value", "error"}, 1)
   241  	c.Assert(string(resp.Stdout), gc.Equals, "")
   242  	c.Assert(string(resp.Stderr), gc.Equals, "ERROR blam\n")
   243  }
   244  
   245  type NewCommandSuite struct {
   246  	relationSuite
   247  }
   248  
   249  var _ = gc.Suite(&NewCommandSuite{})
   250  
   251  var newCommandTests = []struct {
   252  	name string
   253  	err  string
   254  }{
   255  	{"close-port", ""},
   256  	{"config-get", ""},
   257  	{"juju-log", ""},
   258  	{"open-port", ""},
   259  	{"opened-ports", ""},
   260  	{"relation-get", ""},
   261  	{"relation-ids", ""},
   262  	{"relation-list", ""},
   263  	{"relation-set", ""},
   264  	{"unit-get", ""},
   265  	{"storage-add", ""},
   266  	{"storage-get", ""},
   267  	{"status-get", ""},
   268  	{"status-set", ""},
   269  	// The error message contains .exe on Windows
   270  	{"random", "unknown command: random(.exe)?"},
   271  }
   272  
   273  func (s *NewCommandSuite) TestNewCommand(c *gc.C) {
   274  	ctx, _ := s.newHookContext(0, "")
   275  	for _, t := range newCommandTests {
   276  		com, err := jujuc.NewCommand(ctx, cmdString(t.name))
   277  		if t.err == "" {
   278  			// At this level, just check basic sanity; commands are tested in
   279  			// more detail elsewhere.
   280  			c.Assert(err, jc.ErrorIsNil)
   281  			c.Assert(com.Info().Name, gc.Equals, t.name)
   282  		} else {
   283  			c.Assert(com, gc.IsNil)
   284  			c.Assert(err, gc.ErrorMatches, t.err)
   285  		}
   286  	}
   287  }