github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/worker/uniter/jujuc/server_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package jujuc_test
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/rpc"
    11  	"os"
    12  	"path/filepath"
    13  	"sync"
    14  	"time"
    15  
    16  	"launchpad.net/gnuflag"
    17  	gc "launchpad.net/gocheck"
    18  
    19  	"launchpad.net/juju-core/cmd"
    20  	"launchpad.net/juju-core/testing"
    21  	jc "launchpad.net/juju-core/testing/checkers"
    22  	"launchpad.net/juju-core/testing/testbase"
    23  	"launchpad.net/juju-core/utils/exec"
    24  	"launchpad.net/juju-core/worker/uniter/jujuc"
    25  )
    26  
    27  type RpcCommand struct {
    28  	cmd.CommandBase
    29  	Value string
    30  	Slow  bool
    31  }
    32  
    33  func (c *RpcCommand) Info() *cmd.Info {
    34  	return &cmd.Info{
    35  		Name:    "remote",
    36  		Purpose: "act at a distance",
    37  		Doc:     "blah doc",
    38  	}
    39  }
    40  
    41  func (c *RpcCommand) SetFlags(f *gnuflag.FlagSet) {
    42  	f.StringVar(&c.Value, "value", "", "doc")
    43  	f.BoolVar(&c.Slow, "slow", false, "doc")
    44  }
    45  
    46  func (c *RpcCommand) Init(args []string) error {
    47  	return cmd.CheckEmpty(args)
    48  }
    49  
    50  func (c *RpcCommand) Run(ctx *cmd.Context) error {
    51  	if c.Value == "error" {
    52  		return errors.New("blam")
    53  	}
    54  	if c.Slow {
    55  		time.Sleep(testing.ShortWait)
    56  		return nil
    57  	}
    58  	ctx.Stdout.Write([]byte("eye of newt\n"))
    59  	ctx.Stderr.Write([]byte("toe of frog\n"))
    60  	return ioutil.WriteFile(ctx.AbsPath("local"), []byte(c.Value), 0644)
    61  }
    62  
    63  func factory(contextId, cmdName string) (cmd.Command, error) {
    64  	if contextId != "validCtx" {
    65  		return nil, fmt.Errorf("unknown context %q", contextId)
    66  	}
    67  	if cmdName != "remote" {
    68  		return nil, fmt.Errorf("unknown command %q", cmdName)
    69  	}
    70  	return &RpcCommand{}, nil
    71  }
    72  
    73  type ServerSuite struct {
    74  	testbase.LoggingSuite
    75  	server   *jujuc.Server
    76  	sockPath string
    77  	err      chan error
    78  }
    79  
    80  var _ = gc.Suite(&ServerSuite{})
    81  
    82  func (s *ServerSuite) SetUpTest(c *gc.C) {
    83  	s.LoggingSuite.SetUpTest(c)
    84  	s.sockPath = filepath.Join(c.MkDir(), "test.sock")
    85  	srv, err := jujuc.NewServer(factory, s.sockPath)
    86  	c.Assert(err, gc.IsNil)
    87  	c.Assert(srv, gc.NotNil)
    88  	s.server = srv
    89  	s.err = make(chan error)
    90  	go func() { s.err <- s.server.Run() }()
    91  }
    92  
    93  func (s *ServerSuite) TearDownTest(c *gc.C) {
    94  	s.server.Close()
    95  	c.Assert(<-s.err, gc.IsNil)
    96  	_, err := os.Open(s.sockPath)
    97  	c.Assert(err, jc.Satisfies, os.IsNotExist)
    98  	s.LoggingSuite.TearDownTest(c)
    99  }
   100  
   101  func (s *ServerSuite) Call(c *gc.C, req jujuc.Request) (resp exec.ExecResponse, err error) {
   102  	client, err := rpc.Dial("unix", s.sockPath)
   103  	c.Assert(err, gc.IsNil)
   104  	defer client.Close()
   105  	err = client.Call("Jujuc.Main", req, &resp)
   106  	return resp, err
   107  }
   108  
   109  func (s *ServerSuite) TestHappyPath(c *gc.C) {
   110  	dir := c.MkDir()
   111  	resp, err := s.Call(c, jujuc.Request{
   112  		"validCtx", dir, "remote", []string{"--value", "something"},
   113  	})
   114  	c.Assert(err, gc.IsNil)
   115  	c.Assert(resp.Code, gc.Equals, 0)
   116  	c.Assert(string(resp.Stdout), gc.Equals, "eye of newt\n")
   117  	c.Assert(string(resp.Stderr), gc.Equals, "toe of frog\n")
   118  	content, err := ioutil.ReadFile(filepath.Join(dir, "local"))
   119  	c.Assert(err, gc.IsNil)
   120  	c.Assert(string(content), gc.Equals, "something")
   121  }
   122  
   123  func (s *ServerSuite) TestLocks(c *gc.C) {
   124  	var wg sync.WaitGroup
   125  	t0 := time.Now()
   126  	for i := 0; i < 4; i++ {
   127  		wg.Add(1)
   128  		go func() {
   129  			dir := c.MkDir()
   130  			resp, err := s.Call(c, jujuc.Request{
   131  				"validCtx", dir, "remote", []string{"--slow"},
   132  			})
   133  			c.Assert(err, gc.IsNil)
   134  			c.Assert(resp.Code, gc.Equals, 0)
   135  			wg.Done()
   136  		}()
   137  	}
   138  	wg.Wait()
   139  	t1 := time.Now()
   140  	c.Assert(t0.Add(4*testing.ShortWait).Before(t1), gc.Equals, true)
   141  }
   142  
   143  func (s *ServerSuite) TestBadCommandName(c *gc.C) {
   144  	dir := c.MkDir()
   145  	_, err := s.Call(c, jujuc.Request{"validCtx", dir, "", nil})
   146  	c.Assert(err, gc.ErrorMatches, "bad request: command not specified")
   147  	_, err = s.Call(c, jujuc.Request{"validCtx", dir, "witchcraft", nil})
   148  	c.Assert(err, gc.ErrorMatches, `bad request: unknown command "witchcraft"`)
   149  }
   150  
   151  func (s *ServerSuite) TestBadDir(c *gc.C) {
   152  	for _, req := range []jujuc.Request{
   153  		{"validCtx", "", "anything", nil},
   154  		{"validCtx", "foo/bar", "anything", nil},
   155  	} {
   156  		_, err := s.Call(c, req)
   157  		c.Assert(err, gc.ErrorMatches, "bad request: Dir is not absolute")
   158  	}
   159  }
   160  
   161  func (s *ServerSuite) TestBadContextId(c *gc.C) {
   162  	_, err := s.Call(c, jujuc.Request{"whatever", c.MkDir(), "remote", nil})
   163  	c.Assert(err, gc.ErrorMatches, `bad request: unknown context "whatever"`)
   164  }
   165  
   166  func (s *ServerSuite) AssertBadCommand(c *gc.C, args []string, code int) exec.ExecResponse {
   167  	resp, err := s.Call(c, jujuc.Request{"validCtx", c.MkDir(), args[0], args[1:]})
   168  	c.Assert(err, gc.IsNil)
   169  	c.Assert(resp.Code, gc.Equals, code)
   170  	return resp
   171  }
   172  
   173  func (s *ServerSuite) TestParseError(c *gc.C) {
   174  	resp := s.AssertBadCommand(c, []string{"remote", "--cheese"}, 2)
   175  	c.Assert(string(resp.Stdout), gc.Equals, "")
   176  	c.Assert(string(resp.Stderr), gc.Equals, "error: flag provided but not defined: --cheese\n")
   177  }
   178  
   179  func (s *ServerSuite) TestBrokenCommand(c *gc.C) {
   180  	resp := s.AssertBadCommand(c, []string{"remote", "--value", "error"}, 1)
   181  	c.Assert(string(resp.Stdout), gc.Equals, "")
   182  	c.Assert(string(resp.Stderr), gc.Equals, "error: blam\n")
   183  }
   184  
   185  type NewCommandSuite struct {
   186  	ContextSuite
   187  }
   188  
   189  var _ = gc.Suite(&NewCommandSuite{})
   190  
   191  var newCommandTests = []struct {
   192  	name string
   193  	err  string
   194  }{
   195  	{"close-port", ""},
   196  	{"config-get", ""},
   197  	{"juju-log", ""},
   198  	{"open-port", ""},
   199  	{"relation-get", ""},
   200  	{"relation-ids", ""},
   201  	{"relation-list", ""},
   202  	{"relation-set", ""},
   203  	{"unit-get", ""},
   204  	{"random", "unknown command: random"},
   205  }
   206  
   207  func (s *NewCommandSuite) TestNewCommand(c *gc.C) {
   208  	ctx := s.GetHookContext(c, 0, "")
   209  	for _, t := range newCommandTests {
   210  		com, err := jujuc.NewCommand(ctx, t.name)
   211  		if t.err == "" {
   212  			// At this level, just check basic sanity; commands are tested in
   213  			// more detail elsewhere.
   214  			c.Assert(err, gc.IsNil)
   215  			c.Assert(com.Info().Name, gc.Equals, t.name)
   216  		} else {
   217  			c.Assert(com, gc.IsNil)
   218  			c.Assert(err, gc.ErrorMatches, t.err)
   219  		}
   220  	}
   221  }