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