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