Merge "Update the playground to use vdl and new init scripts."
diff --git a/services/mounttable/lib/neighborhood.go b/services/mounttable/lib/neighborhood.go
index 5f3a110..7b49c1b 100644
--- a/services/mounttable/lib/neighborhood.go
+++ b/services/mounttable/lib/neighborhood.go
@@ -132,10 +132,11 @@
 	var reply []mounttable.MountedServer
 	si := nh.mdns.ResolveInstance(instance, "veyron")
 
+	// Use a map to dedup any addresses seen
+	addrMap := make(map[string]uint32)
+
 	// Look for any TXT records with addresses.
 	for _, rr := range si.TxtRRs {
-		// Use a map to dedup any addresses seen
-		addrMap := make(map[string]uint32)
 		for _, s := range rr.Txt {
 			if !strings.HasPrefix(s, addressPrefix) {
 				continue
@@ -143,9 +144,9 @@
 			addr := s[len(addressPrefix):]
 			addrMap[addr] = rr.Header().Ttl
 		}
-		for addr, ttl := range addrMap {
-			reply = append(reply, mounttable.MountedServer{addr, ttl})
-		}
+	}
+	for addr, ttl := range addrMap {
+		reply = append(reply, mounttable.MountedServer{addr, ttl})
 	}
 
 	if reply != nil {
diff --git a/services/mounttable/mounttabled/mounttable.go b/services/mounttable/mounttabled/mounttable.go
index fadc4a8..23cf394 100644
--- a/services/mounttable/mounttabled/mounttable.go
+++ b/services/mounttable/mounttabled/mounttable.go
@@ -78,7 +78,7 @@
 	mtAddr := naming.JoinAddressName(mtEndpoint.String(), "")
 	r.Namespace().SetRoots(mtAddr)
 
-	vlog.Infof("Mount table service at: %q (%s)",
+	vlog.Infof("Mount table service at: %q endpoint: %s",
 		name,
 		naming.JoinAddressName(mtEndpoint.String(), ""))
 
@@ -98,7 +98,7 @@
 			vlog.Errorf("nhServer.Listen failed: %v", err)
 			return
 		}
-		nh, err := mounttable.NewNeighborhoodServer("", *nhName, mtAddr)
+		nh, err := mounttable.NewNeighborhoodServer(*nhName, mtAddr)
 		if err != nil {
 			vlog.Errorf("NewNeighborhoodServer failed: %v", err)
 			return
diff --git a/services/mounttable/mounttabled/test.sh b/services/mounttable/mounttabled/test.sh
new file mode 100755
index 0000000..1f15be1
--- /dev/null
+++ b/services/mounttable/mounttabled/test.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+# Test the mounttabled binary
+#
+# This test starts a mounttable server with the neighborhood option enabled,
+# and mounts 1) the mounttable on itself as 'myself', and 2) www.google.com:80
+# as 'google'.
+#
+# Then it verifies that <mounttable>.Glob(*) and <neighborhood>.Glob(nhname)
+# return the correct result.
+
+toplevel=$(git rev-parse --show-toplevel)
+go=${toplevel}/scripts/build/go
+thisscript=$0
+
+echo "Test directory: $(dirname $0)"
+
+builddir=$(mktemp -d --tmpdir=${toplevel}/go)
+tmplog=$(mktemp)
+trap onexit EXIT
+
+onexit() {
+	cd /
+	exec 2> /dev/null
+	kill -9 $(jobs -p)
+	rm -rf $builddir $tmplog
+}
+
+FAIL() {
+	[ $# -gt 0 ] && echo "$thisscript $*"
+	echo FAIL
+	exit 1
+}
+
+PASS() {
+	echo PASS
+	exit 0
+}
+
+# Build mounttabled and mounttable binaries.
+cd $builddir
+$go build veyron/services/mounttable/mounttabled || FAIL "line $LINENO: failed to build mounttabled"
+$go build veyron/tools/mounttable || FAIL "line $LINENO: failed to build mounttable"
+
+# Start mounttabled and find its endpoint.
+nhname=test$$
+./mounttabled --address=localhost:0 --neighborhood_name=$nhname > $tmplog 2>&1 &
+
+for i in 1 2 3 4; do
+	ep=$(grep "Mount table service at:" $tmplog | sed -re 's/^.*endpoint: ([^ ]*).*/\1/')
+	if [ -n "$ep" ]; then
+		break
+	fi
+	sleep 1
+done
+
+[ -z $ep ] && FAIL "line $LINENO: no server"
+
+# Get the neighborhood endpoint from the mounttable.
+nhep=$(./mounttable glob $ep nh | grep ^nh | cut -d' ' -f2)
+[ -z $nhep ] && FAIL "line $LINENO: no neighborhood server"
+
+# Mount objects and verify the result.
+./mounttable mount "${ep}/myself" "$ep" 5m > /dev/null || FAIL "line $LINENO: failed to mount the mounttable on itself"
+./mounttable mount "${ep}/google" /www.google.com:80 5m > /dev/null || FAIL "line $LINENO: failed to mount www.google.com"
+
+# <mounttable>.Glob('*')
+got=$(./mounttable glob $ep '*' | sed 's/TTL .m..s/TTL XmXXs/' | sort)
+want="[${ep}]
+google /www.google.com:80 (TTL XmXXs)
+myself ${ep} (TTL XmXXs)
+nh ${nhep} (TTL XmXXs)"
+
+if [ "$got" != "$want" ]; then
+	FAIL "line $LINENO: unexpected output. Got $got, want $want"
+fi
+
+# <neighborhood>.Glob('nhname')
+got=$(./mounttable glob $nhep $nhname | sed 's/TTL .m..s/TTL XmXXs/' | sort)
+want="[${nhep}]
+${nhname} ${ep} (TTL XmXXs)"
+
+if [ "$got" != "$want" ]; then
+	FAIL "line $LINENO: unexpected output. Got $got, want $want"
+fi
+
+PASS
diff --git a/tools/namespace/impl/impl.go b/tools/namespace/impl/impl.go
new file mode 100644
index 0000000..ad3cc43
--- /dev/null
+++ b/tools/namespace/impl/impl.go
@@ -0,0 +1,199 @@
+package impl
+
+import (
+	"fmt"
+	"time"
+
+	"veyron/lib/cmdline"
+
+	"veyron2/rt"
+	"veyron2/vlog"
+)
+
+var cmdGlob = &cmdline.Command{
+	Run:      runGlob,
+	Name:     "glob",
+	Short:    "Returns all matching entries from the namespace",
+	Long:     "Returns all matching entries from the namespace.",
+	ArgsName: "<pattern>",
+	ArgsLong: `
+<pattern> is a glob pattern that is matched against all the names below the
+specified mount name.
+`,
+}
+
+func runGlob(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	pattern := args[0]
+	ns := rt.R().Namespace()
+	c, err := ns.Glob(rt.R().NewContext(), pattern)
+	if err != nil {
+		vlog.Infof("ns.Glob(%q) failed: %v", pattern, err)
+		return err
+	}
+	for res := range c {
+		fmt.Fprint(cmd.Stdout(), res.Name)
+		for _, s := range res.Servers {
+			fmt.Fprintf(cmd.Stdout(), " %s (TTL %s)", s.Server, s.TTL)
+		}
+		fmt.Fprintln(cmd.Stdout())
+	}
+	return nil
+}
+
+var cmdMount = &cmdline.Command{
+	Run:      runMount,
+	Name:     "mount",
+	Short:    "Adds a server to the namespace",
+	Long:     "Adds server <server> to the namespace with name <name>.",
+	ArgsName: "<name> <server> <ttl>",
+	ArgsLong: `
+<name> is the name to add to the namespace.
+<server> is the object address of the server to add.
+<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)
+	}
+	name := args[0]
+	server := args[1]
+	ttlArg := args[2]
+
+	ttl, err := time.ParseDuration(ttlArg)
+	if err != nil {
+		return fmt.Errorf("TTL parse error: %v", err)
+	}
+	ns := rt.R().Namespace()
+	if err = ns.Mount(rt.R().NewContext(), name, server, ttl); err != nil {
+		vlog.Infof("ns.Mount(%q, %q, %s) failed: %v", name, server, ttl, err)
+		return err
+	}
+	fmt.Fprintln(cmd.Stdout(), "Server mounted successfully.")
+	return nil
+}
+
+var cmdUnmount = &cmdline.Command{
+	Run:      runUnmount,
+	Name:     "unmount",
+	Short:    "Removes a server from the namespace",
+	Long:     "Removes server <server> with name <name> from the namespace.",
+	ArgsName: "<name> <server>",
+	ArgsLong: `
+<name> is the name to remove from the namespace.
+<server> is the object address of the server to remove.
+`,
+}
+
+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)
+	}
+	name := args[0]
+	server := args[1]
+	ns := rt.R().Namespace()
+	if err := ns.Unmount(rt.R().NewContext(), name, server); err != nil {
+		vlog.Infof("ns.Unmount(%q, %q) failed: %v", name, server, err)
+		return err
+	}
+	fmt.Fprintln(cmd.Stdout(), "Server unmounted successfully.")
+	return nil
+}
+
+var cmdResolve = &cmdline.Command{
+	Run:      runResolve,
+	Name:     "resolve",
+	Short:    "Translates a object name to its object address(es)",
+	Long:     "Translates a object name to its object address(es).",
+	ArgsName: "<name>",
+	ArgsLong: "<name> is the name to resolve.",
+}
+
+func runResolve(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("resolve: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	ns := rt.R().Namespace()
+	servers, err := ns.Resolve(rt.R().NewContext(), name)
+	if err != nil {
+		vlog.Infof("ns.Resolve(%q) failed: %v", name, err)
+		return err
+	}
+	for _, s := range servers {
+		fmt.Fprintln(cmd.Stdout(), s)
+	}
+	return nil
+}
+
+var cmdResolveToMT = &cmdline.Command{
+	Run:      runResolveToMT,
+	Name:     "resolvetomt",
+	Short:    "Finds the address of the mounttable that holds an object name",
+	Long:     "Finds the address of the mounttable that holds an object name.",
+	ArgsName: "<name>",
+	ArgsLong: "<name> is the name to resolve.",
+}
+
+func runResolveToMT(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("resolvetomt: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	ns := rt.R().Namespace()
+	servers, err := ns.ResolveToMountTable(rt.R().NewContext(), name)
+	if err != nil {
+		vlog.Infof("ns.ResolveToMountTable(%q) failed: %v", name, err)
+		return err
+	}
+	for _, s := range servers {
+		fmt.Fprintln(cmd.Stdout(), s)
+	}
+	return nil
+}
+
+var cmdUnresolve = &cmdline.Command{
+	Run:      runUnresolve,
+	Name:     "unresolve",
+	Short:    "Returns the rooted object names for the given object name",
+	Long:     "Returns the rooted object names for the given object name.",
+	ArgsName: "<name>",
+	ArgsLong: "<name> is the object name to unresolve.",
+}
+
+func runUnresolve(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.Errorf("unresolve: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+	name := args[0]
+	ns := rt.R().Namespace()
+	servers, err := ns.Unresolve(rt.R().NewContext(), name)
+	if err != nil {
+		vlog.Infof("ns.Unresolve(%q) failed: %v", name, err)
+		return err
+	}
+	for _, s := range servers {
+		fmt.Fprintln(cmd.Stdout(), s)
+	}
+	return nil
+}
+
+func Root() *cmdline.Command {
+	return &cmdline.Command{
+		Name:  "namespace",
+		Short: "Command-line tool for interacting with the Veyron namespace",
+		Long: `
+Command-line tool for interacting with the Veyron namespace.
+
+The namespace roots are set from environment variables that have a name
+starting with NAMESPACE_ROOT, e.g. NAMESPACE_ROOT, NAMESPACE_ROOT_2,
+NAMESPACE_ROOT_GOOGLE, etc.
+`,
+		Children: []*cmdline.Command{cmdGlob, cmdMount, cmdUnmount, cmdResolve, cmdResolveToMT, cmdUnresolve},
+	}
+}
diff --git a/tools/namespace/main.go b/tools/namespace/main.go
new file mode 100644
index 0000000..4a0ff2d
--- /dev/null
+++ b/tools/namespace/main.go
@@ -0,0 +1,12 @@
+// A command-line tool to interface with the veyron namespace.
+package main
+
+import (
+	"veyron/tools/namespace/impl"
+	"veyron2/rt"
+)
+
+func main() {
+	defer rt.Init().Shutdown()
+	impl.Root().Main()
+}