veyron/services/mgmt/node: Add namespace skeleton

This change adds the Globbable to the node manager interfaces and an
incomplete implementation of Glob. The complete implementation will
follow in other changes.

Change-Id: I7265f096c6c897a1170afa73a5e742e92a71e2e8
diff --git a/services/mgmt/debug/dispatcher.go b/services/mgmt/debug/dispatcher.go
index 4497217..7287f0b 100644
--- a/services/mgmt/debug/dispatcher.go
+++ b/services/mgmt/debug/dispatcher.go
@@ -4,16 +4,13 @@
 	"strings"
 	"time"
 
-	"veyron.io/veyron/veyron/lib/glob"
+	"veyron.io/veyron/veyron/services/mgmt/lib/toplevelglob"
 	logreaderimpl "veyron.io/veyron/veyron/services/mgmt/logreader/impl"
 	pprofimpl "veyron.io/veyron/veyron/services/mgmt/pprof/impl"
 	statsimpl "veyron.io/veyron/veyron/services/mgmt/stats/impl"
 
 	"veyron.io/veyron/veyron2/ipc"
-	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/security"
-	"veyron.io/veyron/veyron2/services/mounttable/types"
-	"veyron.io/veyron/veyron2/verror"
 )
 
 // dispatcher holds the state of the debug dispatcher.
@@ -32,7 +29,7 @@
 	}
 	if len(suffix) == 0 {
 		leaves := []string{"logs", "pprof", "stats"}
-		return ipc.ReflectInvoker(&topLevelGlobInvoker{d, leaves}), d.auth, nil
+		return toplevelglob.New(d, leaves), d.auth, nil
 	}
 	parts := strings.SplitN(suffix, "/", 2)
 	if len(parts) == 2 {
@@ -53,71 +50,3 @@
 	}
 	return nil, d.auth, nil
 }
-
-type topLevelGlobInvoker struct {
-	d      *dispatcher
-	leaves []string
-}
-
-func (i *topLevelGlobInvoker) Glob(call ipc.ServerCall, pattern string) error {
-	g, err := glob.Parse(pattern)
-	if err != nil {
-		return err
-	}
-	for _, leaf := range i.leaves {
-		if ok, _, left := g.MatchInitialSegment(leaf); ok {
-			if err := i.leafGlob(call, leaf, left.String()); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-func (i *topLevelGlobInvoker) leafGlob(call ipc.ServerCall, leaf string, pattern string) error {
-	invoker, _, err := i.d.Lookup(leaf, "Glob")
-	if err != nil {
-		return err
-	}
-	argptrs := []interface{}{&pattern}
-	leafCall := &localServerCall{call, leaf}
-	results, err := invoker.Invoke("Glob", leafCall, argptrs)
-	if err != nil {
-		return err
-	}
-	if len(results) != 1 {
-		return verror.BadArgf("unexpected number of results. Got %d, want 1", len(results))
-	}
-	res := results[0]
-	if res == nil {
-		return nil
-	}
-	err, ok := res.(error)
-	if !ok {
-		return verror.BadArgf("unexpected result type. Got %T, want error", res)
-	}
-	return err
-}
-
-// An ipc.ServerCall implementation used to Invoke methods on the invokers
-// directly. Everything is the same as the original ServerCall, except the
-// Stream implementation.
-type localServerCall struct {
-	ipc.ServerCall
-	name string
-}
-
-// Re-Implement ipc.Stream
-func (c *localServerCall) Recv(v interface{}) error {
-	panic("Recv not implemented")
-	return nil
-}
-
-func (c *localServerCall) Send(v interface{}) error {
-	me, ok := v.(types.MountEntry)
-	if !ok {
-		return verror.BadArgf("unexpected stream type. Got %T, want MountEntry", v)
-	}
-	me.Name = naming.Join(c.name, me.Name)
-	return c.ServerCall.Send(me)
-}
diff --git a/services/mgmt/lib/toplevelglob/invoker.go b/services/mgmt/lib/toplevelglob/invoker.go
new file mode 100644
index 0000000..439d129
--- /dev/null
+++ b/services/mgmt/lib/toplevelglob/invoker.go
@@ -0,0 +1,87 @@
+// Package toplevelglob implements a Glob invoker that recurses into other
+// invokers.
+package toplevelglob
+
+import (
+	"veyron.io/veyron/veyron/lib/glob"
+
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/naming"
+	"veyron.io/veyron/veyron2/services/mounttable/types"
+	"veyron.io/veyron/veyron2/verror"
+)
+
+type invoker struct {
+	d      ipc.Dispatcher
+	leaves []string
+}
+
+// New returns a new top-level Glob invoker. The invoker implements a Glob
+// method that will match the given leaves, and recurse into leaf invokers
+// on the given dispatcher.
+func New(d ipc.Dispatcher, leaves []string) ipc.Invoker {
+	return ipc.ReflectInvoker(&invoker{d, leaves})
+}
+
+func (i *invoker) Glob(call ipc.ServerCall, pattern string) error {
+	g, err := glob.Parse(pattern)
+	if err != nil {
+		return err
+	}
+	for _, leaf := range i.leaves {
+		if ok, _, left := g.MatchInitialSegment(leaf); ok {
+			if err := i.leafGlob(call, leaf, left.String()); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (i *invoker) leafGlob(call ipc.ServerCall, leaf string, pattern string) error {
+	invoker, _, err := i.d.Lookup(leaf, "Glob")
+	if err != nil {
+		return err
+	}
+	argptrs := []interface{}{&pattern}
+	leafCall := &localServerCall{call, leaf}
+	results, err := invoker.Invoke("Glob", leafCall, argptrs)
+	if err != nil {
+		return err
+	}
+	if len(results) != 1 {
+		return verror.BadArgf("unexpected number of results. Got %d, want 1", len(results))
+	}
+	res := results[0]
+	if res == nil {
+		return nil
+	}
+	err, ok := res.(error)
+	if !ok {
+		return verror.BadArgf("unexpected result type. Got %T, want error", res)
+	}
+	return err
+}
+
+// An ipc.ServerCall implementation used to Invoke methods on the invokers
+// directly. Everything is the same as the original ServerCall, except the
+// Stream implementation.
+type localServerCall struct {
+	ipc.ServerCall
+	name string
+}
+
+// Re-Implement ipc.Stream
+func (c *localServerCall) Recv(v interface{}) error {
+	panic("Recv not implemented")
+	return nil
+}
+
+func (c *localServerCall) Send(v interface{}) error {
+	me, ok := v.(types.MountEntry)
+	if !ok {
+		return verror.BadArgf("unexpected stream type. Got %T, want MountEntry", v)
+	}
+	me.Name = naming.Join(c.name, me.Name)
+	return c.ServerCall.Send(me)
+}
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index b7e3a9a..5922123 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -103,9 +103,12 @@
 	"veyron.io/veyron/veyron2/rt"
 	"veyron.io/veyron/veyron2/services/mgmt/appcycle"
 	"veyron.io/veyron/veyron2/services/mgmt/application"
+	"veyron.io/veyron/veyron2/services/mounttable"
+	"veyron.io/veyron/veyron2/services/mounttable/types"
 	"veyron.io/veyron/veyron2/vlog"
 
 	vexec "veyron.io/veyron/veyron/lib/exec"
+	"veyron.io/veyron/veyron/lib/glob"
 	iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
 )
 
@@ -725,3 +728,15 @@
 	}
 	return updateLink(prevVersionDir, currLink)
 }
+
+func (i *appInvoker) Glob(ctx ipc.ServerContext, pattern string, stream mounttable.GlobbableServiceGlobStream) error {
+	// TODO(rthellend): Finish implementing Glob
+	g, err := glob.Parse(pattern)
+	if err != nil {
+		return err
+	}
+	if g.Len() == 0 {
+		return stream.SendStream().Send(types.MountEntry{Name: ""})
+	}
+	return nil
+}
diff --git a/services/mgmt/node/impl/dispatcher.go b/services/mgmt/node/impl/dispatcher.go
index fa6b7eb..c71e71a 100644
--- a/services/mgmt/node/impl/dispatcher.go
+++ b/services/mgmt/node/impl/dispatcher.go
@@ -14,6 +14,7 @@
 	vsecurity "veyron.io/veyron/veyron/security"
 	vflag "veyron.io/veyron/veyron/security/flag"
 	"veyron.io/veyron/veyron/security/serialization"
+	"veyron.io/veyron/veyron/services/mgmt/lib/toplevelglob"
 	inode "veyron.io/veyron/veyron/services/mgmt/node"
 	"veyron.io/veyron/veyron/services/mgmt/node/config"
 
@@ -216,6 +217,9 @@
 		}
 	}
 	if len(components) == 0 {
+		if method == "Glob" {
+			return toplevelglob.New(d, []string{nodeSuffix, appsSuffix}), d.auth, nil
+		}
 		return nil, nil, errInvalidSuffix
 	}
 	// The implementation of the node manager is split up into several
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 879a34a..c0f74d7 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -10,6 +10,8 @@
 	"os"
 	goexec "os/exec"
 	"path/filepath"
+	"reflect"
+	"sort"
 	"strconv"
 	"strings"
 	"syscall"
@@ -32,6 +34,7 @@
 	"veyron.io/veyron/veyron2/security"
 	"veyron.io/veyron/veyron2/services/mgmt/application"
 	"veyron.io/veyron/veyron2/services/mgmt/node"
+	"veyron.io/veyron/veyron2/services/mounttable"
 	"veyron.io/veyron/veyron2/verror"
 	"veyron.io/veyron/veyron2/vlog"
 )
@@ -887,3 +890,49 @@
 		t.Fatalf("Install should have failed with claimer identity")
 	}
 }
+
+func TestNodeManagerGlob(t *testing.T) {
+	// Set up mount table.
+	defer setupLocalNamespace(t)()
+	root, cleanup := setupRootDir(t)
+	defer cleanup()
+
+	// Set up the node manager.  Since we won't do node manager updates,
+	// don't worry about its application envelope and current link.
+	nm := blackbox.HelperCommand(t, "nodeManager", "nm", root, "unused app repo name", "unused curr link")
+	defer setupChildCommand(nm)()
+	if err := nm.Cmd.Start(); err != nil {
+		t.Fatalf("Start() failed: %v", err)
+	}
+	defer nm.Cleanup()
+	readPID(t, nm)
+
+	c, err := mounttable.BindGlobbable("nm")
+	if err != nil {
+		t.Fatalf("BindGlobbable failed: %v", err)
+	}
+
+	stream, err := c.Glob(rt.R().NewContext(), "...")
+	if err != nil {
+		t.Errorf("Glob failed: %v", err)
+	}
+	results := []string{}
+	iterator := stream.RecvStream()
+	for iterator.Advance() {
+		results = append(results, iterator.Value().Name)
+	}
+	sort.Strings(results)
+	expected := []string{
+		"apps",
+		"nm",
+	}
+	if !reflect.DeepEqual(results, expected) {
+		t.Errorf("unexpected result. Got %v, want %v", results, expected)
+	}
+	if err := iterator.Err(); err != nil {
+		t.Errorf("unexpected stream error: %v", err)
+	}
+	if err := stream.Finish(); err != nil {
+		t.Errorf("Finish failed: %v", err)
+	}
+}
diff --git a/services/mgmt/node/impl/node_invoker.go b/services/mgmt/node/impl/node_invoker.go
index 9db7b75..2f3ea88 100644
--- a/services/mgmt/node/impl/node_invoker.go
+++ b/services/mgmt/node/impl/node_invoker.go
@@ -43,9 +43,12 @@
 	"veyron.io/veyron/veyron2/services/mgmt/application"
 	"veyron.io/veyron/veyron2/services/mgmt/binary"
 	"veyron.io/veyron/veyron2/services/mgmt/node"
+	"veyron.io/veyron/veyron2/services/mounttable"
+	"veyron.io/veyron/veyron2/services/mounttable/types"
 	"veyron.io/veyron/veyron2/vlog"
 
 	vexec "veyron.io/veyron/veyron/lib/exec"
+	"veyron.io/veyron/veyron/lib/glob"
 	iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
 	"veyron.io/veyron/veyron/services/mgmt/profile"
 )
@@ -375,3 +378,15 @@
 func (i *nodeInvoker) GetACL(_ ipc.ServerContext) (acl security.ACL, etag string, err error) {
 	return i.disp.getACL()
 }
+
+func (i *nodeInvoker) Glob(ctx ipc.ServerContext, pattern string, stream mounttable.GlobbableServiceGlobStream) error {
+	// TODO(rthellend): Finish implementing Glob
+	g, err := glob.Parse(pattern)
+	if err != nil {
+		return err
+	}
+	if g.Len() == 0 {
+		return stream.SendStream().Send(types.MountEntry{Name: ""})
+	}
+	return nil
+}