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 }