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 }