blob: 57695ba47f8d3c0943e81318a7ff85cb7a16b00b [file] [log] [blame]
package main
import (
"bytes"
"fmt"
"io"
"time"
idl_test_base "v.io/core/veyron/tools/vrpc/test_base"
"v.io/core/veyron2"
"v.io/core/veyron2/context"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/vdl/vdlutil"
"v.io/core/veyron2/vom"
"v.io/core/veyron2/wiretype"
"v.io/lib/cmdline"
idl_binary "v.io/core/veyron2/services/mgmt/binary"
idl_device "v.io/core/veyron2/services/mgmt/device"
)
const serverDesc = `
<server> identifies the Veyron RPC server. It can either be the object address of
the server or an Object name in which case the vrpc will use Veyron's name
resolution to match this name to an end-point.
`
const methodDesc = `
<method> identifies the name of the method to be invoked.
`
const argsDesc = `
<args> identifies the arguments of the method to be invoked. It should be a list
of values in a VOM JSON format that can be reflected to the correct type
using Go's reflection.
`
var cmdDescribe = &cmdline.Command{
Run: runDescribe,
Name: "describe",
Short: "Describe the API of an Veyron RPC server",
Long: `
Describe connects to the Veyron RPC server identified by <server>, finds out what
its API is, and outputs a succint summary of this API to the standard output.
`,
ArgsName: "<server>",
ArgsLong: serverDesc,
}
func runDescribe(cmd *cmdline.Command, args []string) error {
if len(args) != 1 {
return cmd.UsageErrorf("describe: incorrect number of arguments, expected 1, got %d", len(args))
}
client, err := setupClient(cmd, runtime)
if err != nil {
return err
}
defer client.Close()
ctx, cancel := runtime.NewContext().WithTimeout(time.Minute)
defer cancel()
signature, err := getSignature(ctx, cmd, args[0], client)
if err != nil {
return err
}
for methodName, methodSignature := range signature.Methods {
fmt.Fprintf(cmd.Stdout(), "%s\n", formatSignature(methodName, methodSignature, signature.TypeDefs))
}
return nil
}
var cmdInvoke = &cmdline.Command{
Run: runInvoke,
Name: "invoke",
Short: "Invoke a method of an Veyron RPC server",
Long: `
Invoke connects to the Veyron RPC server identified by <server>, invokes the method
identified by <method>, supplying the arguments identified by <args>, and outputs
the results of the invocation to the standard output.
`,
ArgsName: "<server> <method> <args>",
ArgsLong: serverDesc + methodDesc + argsDesc,
}
func runInvoke(cmd *cmdline.Command, args []string) error {
if len(args) < 2 {
return cmd.UsageErrorf("invoke: incorrect number of arguments, expected at least 2, got %d", len(args))
}
server, method, args := args[0], args[1], args[2:]
client, err := setupClient(cmd, runtime)
if err != nil {
return err
}
defer client.Close()
ctx, cancel := runtime.NewContext().WithTimeout(time.Minute)
defer cancel()
signature, err := getSignature(ctx, cmd, server, client)
if err != nil {
return fmt.Errorf("invoke: failed to get signature for %v: %v", server, err)
}
if len(signature.Methods) == 0 {
return fmt.Errorf("invoke: empty signature for %v", server)
}
methodSignature, found := signature.Methods[method]
if !found {
return fmt.Errorf("invoke: method %s not found", method)
}
if len(args) != len(methodSignature.InArgs) {
return cmd.UsageErrorf("invoke: incorrect number of arguments, expected %d, got %d", len(methodSignature.InArgs), len(args))
}
// Register all user-defined types you would like to use.
//
// TODO(jsimsa): This is a temporary hack to get vrpc to work. When
// Benj implements support for decoding arbitrary structs to an
// empty interface, this will no longer be needed.
var x1 idl_test_base.Struct
vdlutil.Register(x1)
var x2 idl_device.Description
vdlutil.Register(x2)
var x3 idl_binary.Description
vdlutil.Register(x3)
var x4 naming.VDLMountedServer
vdlutil.Register(x4)
var x5 naming.VDLMountEntry
vdlutil.Register(x5)
// Decode the inputs from vomJSON-formatted command-line arguments.
inputs := make([]interface{}, len(args))
for i := range args {
var buf bytes.Buffer
buf.WriteString(args[i])
buf.WriteByte(0)
decoder := vom.NewDecoder(&buf)
if err := decoder.Decode(&inputs[i]); err != nil {
return fmt.Errorf("decoder.Decode() failed for %s with %v", args[i], err)
}
}
// Initiate the method invocation.
call, err := client.StartCall(ctx, server, method, inputs)
if err != nil {
return fmt.Errorf("client.StartCall(%s, %q, %v) failed with %v", server, method, inputs, err)
}
fmt.Fprintf(cmd.Stdout(), "%s = ", formatInput(method, inputs))
// Handle streaming results, if the method happens to be streaming.
var item interface{}
nStream := 0
forloop:
for ; ; nStream++ {
switch err := call.Recv(&item); err {
case io.EOF:
break forloop
case nil:
if nStream == 0 {
fmt.Fprintln(cmd.Stdout(), "<<")
}
fmt.Fprintf(cmd.Stdout(), "%d: %v\n", nStream, item)
default:
return fmt.Errorf("call.Recv failed with %v", err)
}
}
if nStream > 0 {
fmt.Fprintf(cmd.Stdout(), ">> ")
}
// Receive the outputs of the method invocation.
outputs := make([]interface{}, len(methodSignature.OutArgs))
outputPtrs := make([]interface{}, len(methodSignature.OutArgs))
for i := range outputs {
outputPtrs[i] = &outputs[i]
}
if err := call.Finish(outputPtrs...); err != nil {
return fmt.Errorf("call.Finish() failed with %v", err)
}
fmt.Fprintf(cmd.Stdout(), "%s\n", formatOutput(outputs))
return nil
}
// root returns the root command for the vrpc tool.
func root() *cmdline.Command {
return &cmdline.Command{
Name: "vrpc",
Short: "Tool for interacting with Veyron RPC servers.",
Long: `
The vrpc tool facilitates interaction with Veyron RPC servers. In particular,
it can be used to 1) find out what API a Veyron RPC server exports and
2) send requests to a Veyron RPC server.
`,
Children: []*cmdline.Command{cmdDescribe, cmdInvoke},
}
}
func setupClient(cmd *cmdline.Command, r veyron2.Runtime) (ipc.Client, error) {
client, err := r.NewClient()
if err != nil {
return nil, fmt.Errorf("NewClient failed: %v", err)
}
return client, nil
}
func getSignature(ctx *context.T, cmd *cmdline.Command, server string, client ipc.Client) (ipc.ServiceSignature, error) {
call, err := client.StartCall(ctx, server, "Signature", nil)
if err != nil {
return ipc.ServiceSignature{}, fmt.Errorf("client.StartCall(%s, Signature, nil) failed with %v", server, err)
}
var signature ipc.ServiceSignature
var sigerr error
if err = call.Finish(&signature, &sigerr); err != nil {
return ipc.ServiceSignature{}, fmt.Errorf("client.Finish(&signature, &sigerr) failed with %v", err)
}
return signature, sigerr
}
// formatWiretype generates a string representation of the specified type.
func formatWiretype(td []vdlutil.Any, tid wiretype.TypeID) string {
var wt vdlutil.Any
if tid >= wiretype.TypeIDFirst {
wt = td[tid-wiretype.TypeIDFirst]
} else {
for _, pair := range wiretype.BootstrapTypes {
if pair.TID == tid {
wt = pair.WT
}
}
if wt == nil {
return fmt.Sprintf("UNKNOWN_TYPE[%v]", tid)
}
}
switch t := wt.(type) {
case wiretype.NamedPrimitiveType:
if t.Name != "" {
return t.Name
}
return tid.Name()
case wiretype.SliceType:
return fmt.Sprintf("[]%s", formatWiretype(td, t.Elem))
case wiretype.ArrayType:
return fmt.Sprintf("[%d]%s", t.Len, formatWiretype(td, t.Elem))
case wiretype.MapType:
return fmt.Sprintf("map[%s]%s", formatWiretype(td, t.Key), formatWiretype(td, t.Elem))
case wiretype.StructType:
var buf bytes.Buffer
buf.WriteString("struct{")
for i, fld := range t.Fields {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(fld.Name)
buf.WriteString(" ")
buf.WriteString(formatWiretype(td, fld.Type))
}
buf.WriteString("}")
return buf.String()
default:
panic(fmt.Sprintf("unknown writetype: %T", wt))
}
}
func formatSignature(name string, ms ipc.MethodSignature, defs []vdlutil.Any) string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "func %s(", name)
for index, arg := range ms.InArgs {
if index > 0 {
buf.WriteString(", ")
}
fmt.Fprintf(&buf, "%s %s", arg.Name, formatWiretype(defs, arg.Type))
}
buf.WriteString(") ")
if ms.InStream != wiretype.TypeIDInvalid || ms.OutStream != wiretype.TypeIDInvalid {
buf.WriteString("stream<")
if ms.InStream == wiretype.TypeIDInvalid {
buf.WriteString("_")
} else {
fmt.Fprintf(&buf, "%s", formatWiretype(defs, ms.InStream))
}
buf.WriteString(", ")
if ms.OutStream == wiretype.TypeIDInvalid {
buf.WriteString("_")
} else {
fmt.Fprintf(&buf, "%s", formatWiretype(defs, ms.OutStream))
}
buf.WriteString("> ")
}
buf.WriteString("(")
for index, arg := range ms.OutArgs {
if index > 0 {
buf.WriteString(", ")
}
if arg.Name != "" {
fmt.Fprintf(&buf, "%s ", arg.Name)
}
fmt.Fprintf(&buf, "%s", formatWiretype(defs, arg.Type))
}
buf.WriteString(")")
return buf.String()
}
func formatInput(name string, inputs []interface{}) string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "%s(", name)
for index, value := range inputs {
if index > 0 {
buf.WriteString(", ")
}
fmt.Fprintf(&buf, "%v", value)
}
buf.WriteString(")")
return buf.String()
}
func formatOutput(outputs []interface{}) string {
var buf bytes.Buffer
buf.WriteString("[")
for index, value := range outputs {
if index > 0 {
buf.WriteString(", ")
}
fmt.Fprintf(&buf, "%v", value)
}
buf.WriteString("]")
return buf.String()
}