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 }