veyron/services/mounttable: Add a command-line tool.
Add a command-line tool to interact with mounttable servers. All the server
methods are supported.
Change-Id: Iebc80cf24826ce774851fbcd595a046ea3ddff81
diff --git a/services/mounttable/mounttable/impl/impl.go b/services/mounttable/mounttable/impl/impl.go
new file mode 100644
index 0000000..b878c68
--- /dev/null
+++ b/services/mounttable/mounttable/impl/impl.go
@@ -0,0 +1,158 @@
+package impl
+
+import (
+ "fmt"
+ "io"
+ "time"
+
+ "veyron/lib/cmdline"
+
+ "veyron2/services/mounttable"
+)
+
+var cmdGlob = &cmdline.Command{
+ Run: runGlob,
+ Name: "glob",
+ Short: "returns all matching entries in the mount table",
+ Long: "returns all matching entries in the mount table",
+ ArgsName: "<mount name> <pattern>",
+ ArgsLong: `
+<mount name> is a mount name on a mount table.
+<pattern> is a glob pattern that is matched against all the entries below the
+specified mount name.
+`,
+}
+
+func runGlob(cmd *cmdline.Command, args []string) error {
+ if expected, got := 2, len(args); expected != got {
+ return cmd.Errorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
+ }
+ c, err := mounttable.BindMountTable(args[0])
+ if err != nil {
+ return fmt.Errorf("bind error: %v", err)
+ }
+ stream, err := c.Glob(args[1])
+ if err != nil {
+ return err
+ }
+ for {
+ buf, err := stream.Recv()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return fmt.Errorf("recv error: %v", err)
+ }
+ fmt.Fprint(cmd.Stdout(), buf.Name)
+ for _, s := range buf.Servers {
+ fmt.Fprintf(cmd.Stdout(), " %s (TTL %s)", s.Server, time.Duration(s.TTL)*time.Second)
+ }
+ fmt.Fprintln(cmd.Stdout())
+ }
+ err = stream.Finish()
+ if err != nil {
+ return fmt.Errorf("finish error: %v", err)
+ }
+ return nil
+}
+
+var cmdMount = &cmdline.Command{
+ Run: runMount,
+ Name: "mount",
+ Short: "Mounts a server <name> onto a mount table",
+ Long: "Mounts a server <name> onto a mount table",
+ ArgsName: "<mount name> <name> <ttl>",
+ ArgsLong: `
+<mount name> is a mount name on a mount table.
+<name> is the rooted veyron name of the server.
+<ttl> is the TTL of the new entry. It is a decimal number followed by a unit
+suffix (s, m, h). A value of 0s represents an infinite duration.
+`,
+}
+
+func runMount(cmd *cmdline.Command, args []string) error {
+ if expected, got := 3, len(args); expected != got {
+ return cmd.Errorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+ }
+ c, err := mounttable.BindMountTable(args[0])
+ if err != nil {
+ return fmt.Errorf("bind error: %v", err)
+ }
+ ttl, err := time.ParseDuration(args[2])
+ if err != nil {
+ return fmt.Errorf("TTL parse error: %v", err)
+ }
+ err = c.Mount(args[1], uint32(ttl.Seconds()))
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintln(cmd.Stdout(), "Name mounted successfully.")
+ return nil
+}
+
+var cmdUnmount = &cmdline.Command{
+ Run: runUnmount,
+ Name: "unmount",
+ Short: "removes server <name> from the mount table",
+ Long: "removes server <name> from the mount table",
+ ArgsName: "<mount name> <name>",
+ ArgsLong: `
+<mount name> is a mount name on a mount table.
+<name> is the rooted veyron name of the server.
+`,
+}
+
+func runUnmount(cmd *cmdline.Command, args []string) error {
+ if expected, got := 2, len(args); expected != got {
+ return cmd.Errorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
+ }
+ c, err := mounttable.BindMountTable(args[0])
+ if err != nil {
+ return fmt.Errorf("bind error: %v", err)
+ }
+ err = c.Unmount(args[1])
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintln(cmd.Stdout(), "Name unmounted successfully.")
+ return nil
+}
+
+var cmdResolveStep = &cmdline.Command{
+ Run: runResolveStep,
+ Name: "resolvestep",
+ Short: "takes the next step in resolving a name.",
+ Long: "takes the next step in resolving a name.",
+ ArgsName: "<mount name>",
+ ArgsLong: `
+<mount name> is a mount name on a mount table.
+`,
+}
+
+func runResolveStep(cmd *cmdline.Command, args []string) error {
+ if expected, got := 1, len(args); expected != got {
+ return cmd.Errorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+ }
+ c, err := mounttable.BindMountTable(args[0])
+ if err != nil {
+ return fmt.Errorf("bind error: %v", err)
+ }
+ servers, suffix, err := c.ResolveStep()
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(cmd.Stdout(), "Servers: %v Suffix: %q\n", servers, suffix)
+ return nil
+}
+
+func Root() *cmdline.Command {
+ return &cmdline.Command{
+ Name: "mounttable",
+ Short: "Command-line tool for interacting with a Veyron mount table",
+ Long: "Command-line tool for interacting with a Veyron mount table",
+ Children: []*cmdline.Command{cmdGlob, cmdMount, cmdUnmount, cmdResolveStep},
+ }
+}
diff --git a/services/mounttable/mounttable/impl/impl_test.go b/services/mounttable/mounttable/impl/impl_test.go
new file mode 100644
index 0000000..fdd5125
--- /dev/null
+++ b/services/mounttable/mounttable/impl/impl_test.go
@@ -0,0 +1,127 @@
+package impl_test
+
+import (
+ "bytes"
+ "strings"
+ "testing"
+
+ "veyron/services/mounttable/mounttable/impl"
+
+ "veyron2"
+ "veyron2/ipc"
+ "veyron2/naming"
+ "veyron2/rt"
+ "veyron2/security"
+ "veyron2/services/mounttable"
+ "veyron2/vlog"
+)
+
+type server struct {
+ suffix string
+}
+
+func (s *server) Glob(_ ipc.Context, pattern string, stream mounttable.GlobableServiceGlobStream) error {
+ vlog.VI(2).Infof("Glob() was called. suffix=%v pattern=%q", s.suffix, pattern)
+ stream.Send(mounttable.MountEntry{"name1", []mounttable.MountedServer{{"server1", 123}}})
+ stream.Send(mounttable.MountEntry{"name2", []mounttable.MountedServer{{"server2", 456}, {"server3", 789}}})
+ return nil
+}
+
+func (s *server) Mount(_ ipc.Context, server string, ttl uint32) error {
+ vlog.VI(2).Infof("Mount() was called. suffix=%v server=%q ttl=%d", s.suffix, server, ttl)
+ return nil
+}
+
+func (s *server) Unmount(_ ipc.Context, server string) error {
+ vlog.VI(2).Infof("Unmount() was called. suffix=%v server=%q", s.suffix, server)
+ return nil
+}
+
+func (s *server) ResolveStep(ipc.Context) (servers []mounttable.MountedServer, suffix string, err error) {
+ vlog.VI(2).Infof("ResolveStep() was called. suffix=%v", s.suffix)
+ servers = []mounttable.MountedServer{{"server1", 123}}
+ suffix = s.suffix
+ return
+}
+
+type dispatcher struct {
+}
+
+func (d *dispatcher) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) {
+ invoker := ipc.ReflectInvoker(mounttable.NewServerMountTable(&server{suffix: suffix}))
+ return invoker, nil, nil
+}
+
+func startServer(t *testing.T, r veyron2.Runtime) (ipc.Server, naming.Endpoint, error) {
+ dispatcher := new(dispatcher)
+ server, err := r.NewServer()
+ if err != nil {
+ t.Errorf("NewServer failed: %v", err)
+ return nil, nil, err
+ }
+ if err := server.Register("", dispatcher); err != nil {
+ t.Errorf("Register failed: %v", err)
+ return nil, nil, err
+ }
+ endpoint, err := server.Listen("tcp", "localhost:0")
+ if err != nil {
+ t.Errorf("Listen failed: %v", err)
+ return nil, nil, err
+ }
+ return server, endpoint, nil
+}
+
+func stopServer(t *testing.T, server ipc.Server) {
+ if err := server.Stop(); err != nil {
+ t.Errorf("server.Stop failed: %v", err)
+ }
+}
+
+func TestMountTableClient(t *testing.T) {
+ runtime := rt.Init()
+ server, endpoint, err := startServer(t, runtime)
+ if err != nil {
+ return
+ }
+ defer stopServer(t, server)
+ // Setup the command-line.
+ cmd := impl.Root()
+ var stdout, stderr bytes.Buffer
+ cmd.Init(nil, &stdout, &stderr)
+
+ // Test the 'glob' command.
+ if err := cmd.Execute([]string{"glob", naming.JoinAddressNameFixed(endpoint.String(), ""), "*"}); err != nil {
+ t.Fatalf("%v", err)
+ }
+ if expected, got := "name1 server1 (TTL 2m3s)\nname2 server2 (TTL 7m36s) server3 (TTL 13m9s)", strings.TrimSpace(stdout.String()); got != expected {
+ t.Errorf("Got %q, expected %q", got, expected)
+ }
+ stdout.Reset()
+
+ // Test the 'mount' command.
+ if err := cmd.Execute([]string{"mount", naming.JoinAddressNameFixed(endpoint.String(), ""), "server", "123s"}); err != nil {
+ t.Fatalf("%v", err)
+ }
+ if expected, got := "Name mounted successfully.", strings.TrimSpace(stdout.String()); got != expected {
+ t.Errorf("Got %q, expected %q", got, expected)
+ }
+ stdout.Reset()
+
+ // Test the 'unmount' command.
+ if err := cmd.Execute([]string{"unmount", naming.JoinAddressNameFixed(endpoint.String(), ""), "server"}); err != nil {
+ t.Fatalf("%v", err)
+ }
+ if expected, got := "Name unmounted successfully.", strings.TrimSpace(stdout.String()); got != expected {
+ t.Errorf("Got %q, expected %q", got, expected)
+ }
+ stdout.Reset()
+
+ // Test the 'resolvestep' command.
+ if err := cmd.Execute([]string{"resolvestep", naming.JoinAddressNameFixed(endpoint.String(), "name")}); err != nil {
+ t.Fatalf("%v", err)
+ }
+ if expected, got := `Servers: [{server1 123}] Suffix: "name"`, strings.TrimSpace(stdout.String()); got != expected {
+ t.Errorf("Got %q, expected %q", got, expected)
+ }
+ stdout.Reset()
+}
diff --git a/services/mounttable/mounttable/main.go b/services/mounttable/mounttable/main.go
new file mode 100644
index 0000000..d62d991
--- /dev/null
+++ b/services/mounttable/mounttable/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "veyron/services/mounttable/mounttable/impl"
+
+ "veyron2/rt"
+)
+
+func main() {
+ r := rt.Init()
+ defer r.Shutdown()
+
+ impl.Root().Main()
+}