Robin Thellend | 18205cf | 2014-10-21 13:53:59 -0700 | [diff] [blame] | 1 | package main |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "fmt" |
| 6 | "io" |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 7 | "time" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 8 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 9 | 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 Wang | 478fcf9 | 2014-12-26 12:37:37 -0800 | [diff] [blame^] | 17 | "v.io/lib/cmdline" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 18 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 19 | idl_binary "v.io/core/veyron2/services/mgmt/binary" |
| 20 | idl_device "v.io/core/veyron2/services/mgmt/device" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 21 | ) |
| 22 | |
| 23 | const serverDesc = ` |
| 24 | <server> identifies the Veyron RPC server. It can either be the object address of |
Bogdan Caprita | d9281a3 | 2014-07-02 14:40:39 -0700 | [diff] [blame] | 25 | the server or an Object name in which case the vrpc will use Veyron's name |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 26 | resolution to match this name to an end-point. |
| 27 | ` |
| 28 | const methodDesc = ` |
| 29 | <method> identifies the name of the method to be invoked. |
| 30 | ` |
| 31 | const argsDesc = ` |
| 32 | <args> identifies the arguments of the method to be invoked. It should be a list |
| 33 | of values in a VOM JSON format that can be reflected to the correct type |
| 34 | using Go's reflection. |
| 35 | ` |
| 36 | |
| 37 | var cmdDescribe = &cmdline.Command{ |
| 38 | Run: runDescribe, |
| 39 | Name: "describe", |
| 40 | Short: "Describe the API of an Veyron RPC server", |
| 41 | Long: ` |
| 42 | Describe connects to the Veyron RPC server identified by <server>, finds out what |
| 43 | its API is, and outputs a succint summary of this API to the standard output. |
| 44 | `, |
| 45 | ArgsName: "<server>", |
| 46 | ArgsLong: serverDesc, |
| 47 | } |
| 48 | |
| 49 | func runDescribe(cmd *cmdline.Command, args []string) error { |
| 50 | if len(args) != 1 { |
Todd Wang | a615e4d | 2014-09-29 16:56:05 -0700 | [diff] [blame] | 51 | return cmd.UsageErrorf("describe: incorrect number of arguments, expected 1, got %d", len(args)) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 52 | } |
| 53 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 54 | client, err := setupClient(cmd, runtime) |
| 55 | if err != nil { |
| 56 | return err |
| 57 | } |
| 58 | defer client.Close() |
| 59 | |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 60 | ctx, cancel := runtime.NewContext().WithTimeout(time.Minute) |
| 61 | defer cancel() |
Matt Rosencrantz | f5afcaf | 2014-06-02 11:31:22 -0700 | [diff] [blame] | 62 | signature, err := getSignature(ctx, cmd, args[0], client) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 63 | 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 | |
| 73 | var cmdInvoke = &cmdline.Command{ |
| 74 | Run: runInvoke, |
| 75 | Name: "invoke", |
| 76 | Short: "Invoke a method of an Veyron RPC server", |
| 77 | Long: ` |
| 78 | Invoke connects to the Veyron RPC server identified by <server>, invokes the method |
| 79 | identified by <method>, supplying the arguments identified by <args>, and outputs |
| 80 | the results of the invocation to the standard output. |
| 81 | `, |
| 82 | ArgsName: "<server> <method> <args>", |
| 83 | ArgsLong: serverDesc + methodDesc + argsDesc, |
| 84 | } |
| 85 | |
| 86 | func runInvoke(cmd *cmdline.Command, args []string) error { |
| 87 | if len(args) < 2 { |
Todd Wang | a615e4d | 2014-09-29 16:56:05 -0700 | [diff] [blame] | 88 | return cmd.UsageErrorf("invoke: incorrect number of arguments, expected at least 2, got %d", len(args)) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 89 | } |
| 90 | server, method, args := args[0], args[1], args[2:] |
| 91 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 92 | client, err := setupClient(cmd, runtime) |
| 93 | if err != nil { |
| 94 | return err |
| 95 | } |
| 96 | defer client.Close() |
| 97 | |
Matt Rosencrantz | 137b8d2 | 2014-08-18 09:56:15 -0700 | [diff] [blame] | 98 | ctx, cancel := runtime.NewContext().WithTimeout(time.Minute) |
| 99 | defer cancel() |
Matt Rosencrantz | f5afcaf | 2014-06-02 11:31:22 -0700 | [diff] [blame] | 100 | signature, err := getSignature(ctx, cmd, server, client) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 101 | 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 Wang | a615e4d | 2014-09-29 16:56:05 -0700 | [diff] [blame] | 114 | return cmd.UsageErrorf("invoke: incorrect number of arguments, expected %d, got %d", len(methodSignature.InArgs), len(args)) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 115 | } |
| 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 Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 123 | vdlutil.Register(x1) |
Bogdan Caprita | a456f47 | 2014-12-10 10:18:03 -0800 | [diff] [blame] | 124 | var x2 idl_device.Description |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 125 | vdlutil.Register(x2) |
Jiri Simsa | 2e7dd71 | 2014-07-11 16:19:47 -0700 | [diff] [blame] | 126 | var x3 idl_binary.Description |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 127 | vdlutil.Register(x3) |
Todd Wang | 1aa5769 | 2014-11-11 13:53:29 -0800 | [diff] [blame] | 128 | var x4 naming.VDLMountedServer |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 129 | vdlutil.Register(x4) |
Todd Wang | 1aa5769 | 2014-11-11 13:53:29 -0800 | [diff] [blame] | 130 | var x5 naming.VDLMountEntry |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 131 | vdlutil.Register(x5) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 132 | |
| 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 Rosencrantz | f5afcaf | 2014-06-02 11:31:22 -0700 | [diff] [blame] | 146 | call, err := client.StartCall(ctx, server, method, inputs) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 147 | 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 |
| 156 | forloop: |
| 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 Thellend | 18205cf | 2014-10-21 13:53:59 -0700 | [diff] [blame] | 188 | // root returns the root command for the vrpc tool. |
| 189 | func root() *cmdline.Command { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 190 | return &cmdline.Command{ |
| 191 | Name: "vrpc", |
Todd Wang | fcb72a5 | 2014-10-01 09:53:56 -0700 | [diff] [blame] | 192 | Short: "Tool for interacting with Veyron RPC servers.", |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 193 | Long: ` |
Todd Wang | a35aae2 | 2014-10-01 09:55:44 -0700 | [diff] [blame] | 194 | The vrpc tool facilitates interaction with Veyron RPC servers. In particular, |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 195 | it can be used to 1) find out what API a Veyron RPC server exports and |
| 196 | 2) send requests to a Veyron RPC server. |
| 197 | `, |
| 198 | Children: []*cmdline.Command{cmdDescribe, cmdInvoke}, |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | func 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 Rosencrantz | 29147f7 | 2014-06-06 12:46:01 -0700 | [diff] [blame] | 210 | func getSignature(ctx context.T, cmd *cmdline.Command, server string, client ipc.Client) (ipc.ServiceSignature, error) { |
Matt Rosencrantz | f5afcaf | 2014-06-02 11:31:22 -0700 | [diff] [blame] | 211 | call, err := client.StartCall(ctx, server, "Signature", nil) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 212 | 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 Wang | 0ecdd7a | 2014-07-14 16:24:38 -0700 | [diff] [blame] | 224 | func formatWiretype(td []vdlutil.Any, tid wiretype.TypeID) string { |
| 225 | var wt vdlutil.Any |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 226 | 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 Wang | 0ecdd7a | 2014-07-14 16:24:38 -0700 | [diff] [blame] | 269 | func formatSignature(name string, ms ipc.MethodSignature, defs []vdlutil.Any) string { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 270 | 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 | |
| 308 | func 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 | |
| 321 | func 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 | } |