blob: d75c8f8f6a5f33cc94e05a989c2346ea73b0300c [file] [log] [blame]
Robin Thellend18205cf2014-10-21 13:53:59 -07001package main
Jiri Simsa5293dcb2014-05-10 09:56:38 -07002
3import (
4 "bytes"
5 "fmt"
6 "io"
Matt Rosencrantz137b8d22014-08-18 09:56:15 -07007 "time"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07008
Jiri Simsa764efb72014-12-25 20:57:03 -08009 idl_test_base "v.io/core/veyron/tools/vrpc/test_base"
10 "v.io/core/veyron2"
11 "v.io/core/veyron2/context"
12 "v.io/core/veyron2/ipc"
13 "v.io/core/veyron2/naming"
14 "v.io/core/veyron2/vdl/vdlutil"
15 "v.io/core/veyron2/vom"
16 "v.io/core/veyron2/wiretype"
Todd Wang478fcf92014-12-26 12:37:37 -080017 "v.io/lib/cmdline"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070018
Jiri Simsa764efb72014-12-25 20:57:03 -080019 idl_binary "v.io/core/veyron2/services/mgmt/binary"
20 idl_device "v.io/core/veyron2/services/mgmt/device"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070021)
22
23const serverDesc = `
24<server> identifies the Veyron RPC server. It can either be the object address of
Bogdan Capritad9281a32014-07-02 14:40:39 -070025the server or an Object name in which case the vrpc will use Veyron's name
Jiri Simsa5293dcb2014-05-10 09:56:38 -070026resolution to match this name to an end-point.
27`
28const methodDesc = `
29<method> identifies the name of the method to be invoked.
30`
31const argsDesc = `
32<args> identifies the arguments of the method to be invoked. It should be a list
33of values in a VOM JSON format that can be reflected to the correct type
34using Go's reflection.
35`
36
37var cmdDescribe = &cmdline.Command{
38 Run: runDescribe,
39 Name: "describe",
40 Short: "Describe the API of an Veyron RPC server",
41 Long: `
42Describe connects to the Veyron RPC server identified by <server>, finds out what
43its API is, and outputs a succint summary of this API to the standard output.
44`,
45 ArgsName: "<server>",
46 ArgsLong: serverDesc,
47}
48
49func runDescribe(cmd *cmdline.Command, args []string) error {
50 if len(args) != 1 {
Todd Wanga615e4d2014-09-29 16:56:05 -070051 return cmd.UsageErrorf("describe: incorrect number of arguments, expected 1, got %d", len(args))
Jiri Simsa5293dcb2014-05-10 09:56:38 -070052 }
53
Jiri Simsa5293dcb2014-05-10 09:56:38 -070054 client, err := setupClient(cmd, runtime)
55 if err != nil {
56 return err
57 }
58 defer client.Close()
59
Matt Rosencrantz137b8d22014-08-18 09:56:15 -070060 ctx, cancel := runtime.NewContext().WithTimeout(time.Minute)
61 defer cancel()
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -070062 signature, err := getSignature(ctx, cmd, args[0], client)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070063 if err != nil {
64 return err
65 }
66
67 for methodName, methodSignature := range signature.Methods {
68 fmt.Fprintf(cmd.Stdout(), "%s\n", formatSignature(methodName, methodSignature, signature.TypeDefs))
69 }
70 return nil
71}
72
73var cmdInvoke = &cmdline.Command{
74 Run: runInvoke,
75 Name: "invoke",
76 Short: "Invoke a method of an Veyron RPC server",
77 Long: `
78Invoke connects to the Veyron RPC server identified by <server>, invokes the method
79identified by <method>, supplying the arguments identified by <args>, and outputs
80the results of the invocation to the standard output.
81`,
82 ArgsName: "<server> <method> <args>",
83 ArgsLong: serverDesc + methodDesc + argsDesc,
84}
85
86func runInvoke(cmd *cmdline.Command, args []string) error {
87 if len(args) < 2 {
Todd Wanga615e4d2014-09-29 16:56:05 -070088 return cmd.UsageErrorf("invoke: incorrect number of arguments, expected at least 2, got %d", len(args))
Jiri Simsa5293dcb2014-05-10 09:56:38 -070089 }
90 server, method, args := args[0], args[1], args[2:]
91
Jiri Simsa5293dcb2014-05-10 09:56:38 -070092 client, err := setupClient(cmd, runtime)
93 if err != nil {
94 return err
95 }
96 defer client.Close()
97
Matt Rosencrantz137b8d22014-08-18 09:56:15 -070098 ctx, cancel := runtime.NewContext().WithTimeout(time.Minute)
99 defer cancel()
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700100 signature, err := getSignature(ctx, cmd, server, client)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700101 if err != nil {
102 return fmt.Errorf("invoke: failed to get signature for %v: %v", server, err)
103 }
104 if len(signature.Methods) == 0 {
105 return fmt.Errorf("invoke: empty signature for %v", server)
106 }
107
108 methodSignature, found := signature.Methods[method]
109 if !found {
110 return fmt.Errorf("invoke: method %s not found", method)
111 }
112
113 if len(args) != len(methodSignature.InArgs) {
Todd Wanga615e4d2014-09-29 16:56:05 -0700114 return cmd.UsageErrorf("invoke: incorrect number of arguments, expected %d, got %d", len(methodSignature.InArgs), len(args))
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700115 }
116
117 // Register all user-defined types you would like to use.
118 //
119 // TODO(jsimsa): This is a temporary hack to get vrpc to work. When
120 // Benj implements support for decoding arbitrary structs to an
121 // empty interface, this will no longer be needed.
122 var x1 idl_test_base.Struct
Todd Wang34ed4c62014-11-26 15:15:52 -0800123 vdlutil.Register(x1)
Bogdan Capritaa456f472014-12-10 10:18:03 -0800124 var x2 idl_device.Description
Todd Wang34ed4c62014-11-26 15:15:52 -0800125 vdlutil.Register(x2)
Jiri Simsa2e7dd712014-07-11 16:19:47 -0700126 var x3 idl_binary.Description
Todd Wang34ed4c62014-11-26 15:15:52 -0800127 vdlutil.Register(x3)
Todd Wang1aa57692014-11-11 13:53:29 -0800128 var x4 naming.VDLMountedServer
Todd Wang34ed4c62014-11-26 15:15:52 -0800129 vdlutil.Register(x4)
Todd Wang1aa57692014-11-11 13:53:29 -0800130 var x5 naming.VDLMountEntry
Todd Wang34ed4c62014-11-26 15:15:52 -0800131 vdlutil.Register(x5)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700132
133 // Decode the inputs from vomJSON-formatted command-line arguments.
134 inputs := make([]interface{}, len(args))
135 for i := range args {
136 var buf bytes.Buffer
137 buf.WriteString(args[i])
138 buf.WriteByte(0)
139 decoder := vom.NewDecoder(&buf)
140 if err := decoder.Decode(&inputs[i]); err != nil {
141 return fmt.Errorf("decoder.Decode() failed for %s with %v", args[i], err)
142 }
143 }
144
145 // Initiate the method invocation.
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700146 call, err := client.StartCall(ctx, server, method, inputs)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700147 if err != nil {
148 return fmt.Errorf("client.StartCall(%s, %q, %v) failed with %v", server, method, inputs, err)
149 }
150
151 fmt.Fprintf(cmd.Stdout(), "%s = ", formatInput(method, inputs))
152
153 // Handle streaming results, if the method happens to be streaming.
154 var item interface{}
155 nStream := 0
156forloop:
157 for ; ; nStream++ {
158 switch err := call.Recv(&item); err {
159 case io.EOF:
160 break forloop
161 case nil:
162 if nStream == 0 {
163 fmt.Fprintln(cmd.Stdout(), "<<")
164 }
165 fmt.Fprintf(cmd.Stdout(), "%d: %v\n", nStream, item)
166 default:
167 return fmt.Errorf("call.Recv failed with %v", err)
168 }
169 }
170 if nStream > 0 {
171 fmt.Fprintf(cmd.Stdout(), ">> ")
172 }
173
174 // Receive the outputs of the method invocation.
175 outputs := make([]interface{}, len(methodSignature.OutArgs))
176 outputPtrs := make([]interface{}, len(methodSignature.OutArgs))
177 for i := range outputs {
178 outputPtrs[i] = &outputs[i]
179 }
180 if err := call.Finish(outputPtrs...); err != nil {
181 return fmt.Errorf("call.Finish() failed with %v", err)
182 }
183 fmt.Fprintf(cmd.Stdout(), "%s\n", formatOutput(outputs))
184
185 return nil
186}
187
Robin Thellend18205cf2014-10-21 13:53:59 -0700188// root returns the root command for the vrpc tool.
189func root() *cmdline.Command {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700190 return &cmdline.Command{
191 Name: "vrpc",
Todd Wangfcb72a52014-10-01 09:53:56 -0700192 Short: "Tool for interacting with Veyron RPC servers.",
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700193 Long: `
Todd Wanga35aae22014-10-01 09:55:44 -0700194The vrpc tool facilitates interaction with Veyron RPC servers. In particular,
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700195it can be used to 1) find out what API a Veyron RPC server exports and
1962) send requests to a Veyron RPC server.
197`,
198 Children: []*cmdline.Command{cmdDescribe, cmdInvoke},
199 }
200}
201
202func setupClient(cmd *cmdline.Command, r veyron2.Runtime) (ipc.Client, error) {
203 client, err := r.NewClient()
204 if err != nil {
205 return nil, fmt.Errorf("NewClient failed: %v", err)
206 }
207 return client, nil
208}
209
Matt Rosencrantz29147f72014-06-06 12:46:01 -0700210func getSignature(ctx context.T, cmd *cmdline.Command, server string, client ipc.Client) (ipc.ServiceSignature, error) {
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700211 call, err := client.StartCall(ctx, server, "Signature", nil)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700212 if err != nil {
213 return ipc.ServiceSignature{}, fmt.Errorf("client.StartCall(%s, Signature, nil) failed with %v", server, err)
214 }
215 var signature ipc.ServiceSignature
216 var sigerr error
217 if err = call.Finish(&signature, &sigerr); err != nil {
218 return ipc.ServiceSignature{}, fmt.Errorf("client.Finish(&signature, &sigerr) failed with %v", err)
219 }
220 return signature, sigerr
221}
222
223// formatWiretype generates a string representation of the specified type.
Todd Wang0ecdd7a2014-07-14 16:24:38 -0700224func formatWiretype(td []vdlutil.Any, tid wiretype.TypeID) string {
225 var wt vdlutil.Any
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700226 if tid >= wiretype.TypeIDFirst {
227 wt = td[tid-wiretype.TypeIDFirst]
228 } else {
229 for _, pair := range wiretype.BootstrapTypes {
230 if pair.TID == tid {
231 wt = pair.WT
232 }
233 }
234 if wt == nil {
235 return fmt.Sprintf("UNKNOWN_TYPE[%v]", tid)
236 }
237 }
238
239 switch t := wt.(type) {
240 case wiretype.NamedPrimitiveType:
241 if t.Name != "" {
242 return t.Name
243 }
244 return tid.Name()
245 case wiretype.SliceType:
246 return fmt.Sprintf("[]%s", formatWiretype(td, t.Elem))
247 case wiretype.ArrayType:
248 return fmt.Sprintf("[%d]%s", t.Len, formatWiretype(td, t.Elem))
249 case wiretype.MapType:
250 return fmt.Sprintf("map[%s]%s", formatWiretype(td, t.Key), formatWiretype(td, t.Elem))
251 case wiretype.StructType:
252 var buf bytes.Buffer
253 buf.WriteString("struct{")
254 for i, fld := range t.Fields {
255 if i > 0 {
256 buf.WriteString(", ")
257 }
258 buf.WriteString(fld.Name)
259 buf.WriteString(" ")
260 buf.WriteString(formatWiretype(td, fld.Type))
261 }
262 buf.WriteString("}")
263 return buf.String()
264 default:
265 panic(fmt.Sprintf("unknown writetype: %T", wt))
266 }
267}
268
Todd Wang0ecdd7a2014-07-14 16:24:38 -0700269func formatSignature(name string, ms ipc.MethodSignature, defs []vdlutil.Any) string {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700270 var buf bytes.Buffer
271 fmt.Fprintf(&buf, "func %s(", name)
272 for index, arg := range ms.InArgs {
273 if index > 0 {
274 buf.WriteString(", ")
275 }
276 fmt.Fprintf(&buf, "%s %s", arg.Name, formatWiretype(defs, arg.Type))
277 }
278 buf.WriteString(") ")
279 if ms.InStream != wiretype.TypeIDInvalid || ms.OutStream != wiretype.TypeIDInvalid {
280 buf.WriteString("stream<")
281 if ms.InStream == wiretype.TypeIDInvalid {
282 buf.WriteString("_")
283 } else {
284 fmt.Fprintf(&buf, "%s", formatWiretype(defs, ms.InStream))
285 }
286 buf.WriteString(", ")
287 if ms.OutStream == wiretype.TypeIDInvalid {
288 buf.WriteString("_")
289 } else {
290 fmt.Fprintf(&buf, "%s", formatWiretype(defs, ms.OutStream))
291 }
292 buf.WriteString("> ")
293 }
294 buf.WriteString("(")
295 for index, arg := range ms.OutArgs {
296 if index > 0 {
297 buf.WriteString(", ")
298 }
299 if arg.Name != "" {
300 fmt.Fprintf(&buf, "%s ", arg.Name)
301 }
302 fmt.Fprintf(&buf, "%s", formatWiretype(defs, arg.Type))
303 }
304 buf.WriteString(")")
305 return buf.String()
306}
307
308func formatInput(name string, inputs []interface{}) string {
309 var buf bytes.Buffer
310 fmt.Fprintf(&buf, "%s(", name)
311 for index, value := range inputs {
312 if index > 0 {
313 buf.WriteString(", ")
314 }
315 fmt.Fprintf(&buf, "%v", value)
316 }
317 buf.WriteString(")")
318 return buf.String()
319}
320
321func formatOutput(outputs []interface{}) string {
322 var buf bytes.Buffer
323 buf.WriteString("[")
324 for index, value := range outputs {
325 if index > 0 {
326 buf.WriteString(", ")
327 }
328 fmt.Fprintf(&buf, "%v", value)
329 }
330 buf.WriteString("]")
331 return buf.String()
332}