Merge "veyron/services/mgmt/node/impl: identity management for child apps (agent-less)."
diff --git a/runtimes/google/ipc/glob.go b/runtimes/google/ipc/glob.go
new file mode 100644
index 0000000..3dfaa3b
--- /dev/null
+++ b/runtimes/google/ipc/glob.go
@@ -0,0 +1,155 @@
+package ipc
+
+import (
+	"strings"
+
+	"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"
+	"veyron.io/veyron/veyron2/vlog"
+
+	"veyron.io/veyron/veyron/lib/glob"
+)
+
+// globInternal handles ALL the Glob requests received by a server and
+// constructs a response from the state of internal server objects and the
+// service objects.
+//
+// Internal objects exist only at the root of the server and have a name that
+// starts with a double underscore ("__"). They are only visible in the Glob
+// response if the double underscore is explicitly part of the pattern, e.g.
+// "".Glob("__*/*"), or "".Glob("__debug/...").
+//
+// Service objects may choose to implement either VAllGlobber or
+// VChildrenGlobber. VAllGlobber is more flexible, but VChildrenGlobber is
+// simpler to implement and less prone to errors.
+//
+// If objects implement VAllGlobber, it must be able to handle recursive pattern
+// for the entire namespace below the receiver object, i.e. "a/b".Glob("...")
+// must return the name of all the objects under "a/b".
+//
+// If they implement VChildrenGlobber, it provides a list of the receiver's
+// immediate children names, or a non-nil error if the receiver doesn't exist.
+//
+// globInternal constructs the Glob response by internally accessing the
+// VAllGlobber or VChildrenGlobber interface of objects as many times as needed.
+//
+// Before accessing an object, globInternal ensures that the requester is
+// authorized to access it. Internal objects require either security.DebugLabel
+// or security.MonitoringLabel. Service objects require security.ResolveLabel.
+type globInternal struct {
+	fs       *flowServer
+	receiver string
+}
+
+// The maximum depth of recursion in Glob. We only count recursion levels
+// associated with a recursive glob pattern, e.g. a pattern like "..." will be
+// allowed to recurse up to 10 levels, but "*/*/*/*/..." will go up to 14
+// levels.
+const maxRecursiveGlobDepth = 10
+
+func (i *globInternal) Glob(call ipc.ServerCall, pattern string) error {
+	vlog.VI(3).Infof("ipc Glob: Incoming request: %q.Glob(%q)", i.receiver, pattern)
+	g, err := glob.Parse(pattern)
+	if err != nil {
+		return err
+	}
+	var disp ipc.Dispatcher
+	if naming.IsReserved(i.receiver) || (i.receiver == "" && naming.IsReserved(pattern)) {
+		disp = i.fs.reservedOpt.Dispatcher
+		i.fs.tags = []interface{}{security.DebugLabel | security.MonitoringLabel}
+	} else {
+		disp = i.fs.disp
+		i.fs.tags = []interface{}{security.ResolveLabel}
+	}
+	if disp == nil {
+		return verror.NoExistf("ipc: Glob is not implemented by %q", i.receiver)
+	}
+	return i.globStep(call, disp, "", g, 0)
+}
+
+func (i *globInternal) globStep(call ipc.ServerCall, disp ipc.Dispatcher, name string, g *glob.Glob, depth int) error {
+	suffix := naming.Join(i.receiver, name)
+	if depth > maxRecursiveGlobDepth {
+		err := verror.Internalf("ipc: Glob exceeded its recursion limit (%d): %q", maxRecursiveGlobDepth, suffix)
+		vlog.Error(err)
+		return err
+	}
+	invoker, auth, verr := lookupInvoker(disp, suffix, ipc.GlobMethod)
+	if verr != nil {
+		return verr
+	}
+	if invoker == nil {
+		return verror.NoExistf("ipc: invoker not found for %q.%s", suffix, ipc.GlobMethod)
+	}
+
+	// Verify that that requester is authorized for the current object.
+	i.fs.suffix = suffix
+	if err := i.fs.authorize(auth); err != nil {
+		return err
+	}
+
+	// If the object implements both VAllGlobber and VChildrenGlobber, we'll
+	// use VAllGlobber.
+	gs := invoker.VGlob()
+	if gs != nil && gs.VAllGlobber != nil {
+		vlog.VI(3).Infof("ipc Glob: %q implements VAllGlobber", suffix)
+		childCall := &localServerCall{ServerCall: call, basename: name}
+		return gs.VAllGlobber.Glob(childCall, g.String())
+	}
+	if gs != nil && gs.VChildrenGlobber != nil {
+		vlog.VI(3).Infof("ipc Glob: %q implements VChildrenGlobber", suffix)
+		children, err := gs.VChildrenGlobber.VGlobChildren()
+		if err != nil {
+			return nil
+		}
+		if g.Len() == 0 {
+			call.Send(types.MountEntry{Name: name})
+		}
+		if g.Finished() {
+			return nil
+		}
+		if g.Len() == 0 {
+			// This is a recursive pattern. Make sure we don't recurse forever.
+			depth++
+		}
+		for _, child := range children {
+			if len(child) == 0 || strings.Contains(child, "/") {
+				vlog.Errorf("ipc: %q.VGlobChildren() returned an invalid child name: %q", suffix, child)
+				continue
+			}
+			if ok, _, left := g.MatchInitialSegment(child); ok {
+				next := naming.Join(name, child)
+				if err := i.globStep(call, disp, next, left, depth); err != nil {
+					vlog.VI(1).Infof("ipc Glob: globStep(%q, %q): %v", next, left, err)
+				}
+			}
+		}
+		return nil
+	}
+
+	vlog.VI(3).Infof("ipc Glob: %q implements neither VAllGlobber nor VChildrenGlobber", suffix)
+	return verror.NoExistf("ipc: Glob is not implemented by %q", suffix)
+}
+
+// An ipc.ServerCall that prepends a name to all the names in the streamed
+// MountEntry objects.
+type localServerCall struct {
+	ipc.ServerCall
+	basename string
+}
+
+var _ ipc.ServerCall = (*localServerCall)(nil)
+var _ ipc.Stream = (*localServerCall)(nil)
+var _ ipc.ServerContext = (*localServerCall)(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.basename, me.Name)
+	return c.ServerCall.Send(me)
+}
diff --git a/runtimes/google/ipc/glob_test.go b/runtimes/google/ipc/glob_test.go
new file mode 100644
index 0000000..5af6586
--- /dev/null
+++ b/runtimes/google/ipc/glob_test.go
@@ -0,0 +1,281 @@
+package ipc_test
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+	"strings"
+	"testing"
+
+	"veyron.io/veyron/veyron2"
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/naming"
+	"veyron.io/veyron/veyron2/rt"
+	"veyron.io/veyron/veyron2/security"
+	"veyron.io/veyron/veyron2/services/mounttable"
+	"veyron.io/veyron/veyron2/services/mounttable/types"
+
+	"veyron.io/veyron/veyron/lib/glob"
+	"veyron.io/veyron/veyron/profiles"
+)
+
+func startServer(rt veyron2.Runtime, tree *node) (string, func(), error) {
+	server, err := rt.NewServer()
+	if err != nil {
+		return "", nil, fmt.Errorf("failed to start debug server: %v", err)
+	}
+	endpoint, err := server.Listen(profiles.LocalListenSpec)
+	if err != nil {
+		return "", nil, fmt.Errorf("failed to listen: %v", err)
+	}
+	if err := server.ServeDispatcher("", &disp{tree}); err != nil {
+		return "", nil, err
+	}
+	ep := endpoint.String()
+	return ep, func() { server.Stop() }, nil
+}
+
+func TestGlob(t *testing.T) {
+	runtime := rt.Init()
+	defer runtime.Cleanup()
+
+	namespace := []string{
+		"a/b/c1/d1",
+		"a/b/c1/d2",
+		"a/b/c2/d1",
+		"a/b/c2/d2",
+		"a/x/y/z",
+	}
+	tree := newNode()
+	for _, p := range namespace {
+		tree.find(strings.Split(p, "/"), true)
+	}
+
+	ep, stop, err := startServer(runtime, tree)
+	if err != nil {
+		t.Fatalf("startServer: %v", err)
+	}
+	defer stop()
+
+	testcases := []struct {
+		name, pattern string
+		expected      []string
+	}{
+		{"", "...", []string{
+			"",
+			"a",
+			"a/b",
+			"a/b/c1",
+			"a/b/c1/d1",
+			"a/b/c1/d2",
+			"a/b/c2",
+			"a/b/c2/d1",
+			"a/b/c2/d2",
+			"a/x",
+			"a/x/y",
+			"a/x/y/z",
+		}},
+		{"a", "...", []string{
+			"",
+			"b",
+			"b/c1",
+			"b/c1/d1",
+			"b/c1/d2",
+			"b/c2",
+			"b/c2/d1",
+			"b/c2/d2",
+			"x",
+			"x/y",
+			"x/y/z",
+		}},
+		{"a/b", "...", []string{
+			"",
+			"c1",
+			"c1/d1",
+			"c1/d2",
+			"c2",
+			"c2/d1",
+			"c2/d2",
+		}},
+		{"a/b/c1", "...", []string{
+			"",
+			"d1",
+			"d2",
+		}},
+		{"a/b/c1/d1", "...", []string{
+			"",
+		}},
+		{"a/x", "...", []string{
+			"",
+			"y",
+			"y/z",
+		}},
+		{"a/x/y", "...", []string{
+			"",
+			"z",
+		}},
+		{"a/x/y/z", "...", []string{
+			"",
+		}},
+		{"", "", []string{""}},
+		{"", "*", []string{"a"}},
+		{"a", "", []string{""}},
+		{"a", "*", []string{"b", "x"}},
+		{"a/b", "", []string{""}},
+		{"a/b", "*", []string{"c1", "c2"}},
+		{"a/b/c1", "", []string{""}},
+		{"a/b/c1", "*", []string{"d1", "d2"}},
+		{"a/b/c1/d1", "*", []string{}},
+		{"a/b/c1/d1", "", []string{""}},
+		{"a", "*/c?", []string{"b/c1", "b/c2"}},
+		{"a", "*/*", []string{"b/c1", "b/c2", "x/y"}},
+		{"a", "*/*/*", []string{"b/c1/d1", "b/c1/d2", "b/c2/d1", "b/c2/d2", "x/y/z"}},
+		{"a/x", "*/*", []string{"y/z"}},
+		{"bad", "", []string{}},
+		{"a/bad", "", []string{}},
+		{"a/b/bad", "", []string{}},
+		{"a/b/c1/bad", "", []string{}},
+		{"a/x/bad", "", []string{}},
+		{"a/x/y/bad", "", []string{}},
+		// muah is an infinite space to test rescursion limit.
+		{"muah", "*", []string{"ha"}},
+		{"muah", "*/*", []string{"ha/ha"}},
+		{"muah", "*/*/*/*/*/*/*/*/*/*/*/*", []string{"ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha/ha"}},
+		{"muah", "...", []string{
+			"",
+			"ha",
+			"ha/ha",
+			"ha/ha/ha",
+			"ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha/ha/ha/ha",
+			"ha/ha/ha/ha/ha/ha/ha/ha/ha/ha",
+		}},
+	}
+	for _, tc := range testcases {
+		c := mounttable.GlobbableClient(naming.JoinAddressName(ep, tc.name))
+
+		stream, err := c.Glob(runtime.NewContext(), tc.pattern)
+		if err != nil {
+			t.Fatalf("Glob failed: %v", err)
+		}
+		results := []string{}
+		iterator := stream.RecvStream()
+		for iterator.Advance() {
+			results = append(results, iterator.Value().Name)
+		}
+		sort.Strings(results)
+		if !reflect.DeepEqual(results, tc.expected) {
+			t.Errorf("unexpected result for (%q, %q). Got %q, want %q", tc.name, tc.pattern, results, tc.expected)
+		}
+		if err := iterator.Err(); err != nil {
+			t.Errorf("unexpected stream error for %q: %v", tc.name, err)
+		}
+		if err := stream.Finish(); err != nil {
+			t.Errorf("Finish failed for %q: %v", tc.name, err)
+		}
+	}
+}
+
+type disp struct {
+	tree *node
+}
+
+func (d *disp) Lookup(suffix, method string) (interface{}, security.Authorizer, error) {
+	elems := strings.Split(suffix, "/")
+	if len(elems) != 0 && elems[0] == "muah" {
+		// Infinite space. Each node has one child named "ha".
+		return ipc.VChildrenGlobberInvoker("ha"), nil, nil
+
+	}
+	if len(elems) <= 2 || (elems[0] == "a" && elems[1] == "x") {
+		return &vChildrenObject{d.tree, elems}, nil, nil
+	}
+	return &globObject{d.tree, elems}, nil, nil
+}
+
+type globObject struct {
+	n      *node
+	suffix []string
+}
+
+func (o *globObject) Glob(call ipc.ServerCall, pattern string) error {
+	g, err := glob.Parse(pattern)
+	if err != nil {
+		return err
+	}
+	n := o.n.find(o.suffix, false)
+	if n == nil {
+		return nil
+	}
+	o.globLoop(call, "", g, n)
+	return nil
+}
+
+func (o *globObject) globLoop(call ipc.ServerCall, name string, g *glob.Glob, n *node) {
+	if g.Len() == 0 {
+		call.Send(types.MountEntry{Name: name})
+	}
+	if g.Finished() {
+		return
+	}
+	for leaf, child := range n.children {
+		if ok, _, left := g.MatchInitialSegment(leaf); ok {
+			o.globLoop(call, naming.Join(name, leaf), left, child)
+		}
+	}
+}
+
+type vChildrenObject struct {
+	n      *node
+	suffix []string
+}
+
+func (o *vChildrenObject) VGlobChildren() ([]string, error) {
+	n := o.n.find(o.suffix, false)
+	if n == nil {
+		return nil, fmt.Errorf("object does not exist")
+	}
+	children := make([]string, len(n.children))
+	index := 0
+	for child, _ := range n.children {
+		children[index] = child
+		index++
+	}
+	return children, nil
+}
+
+type node struct {
+	children map[string]*node
+}
+
+func newNode() *node {
+	return &node{make(map[string]*node)}
+}
+
+func (n *node) find(names []string, create bool) *node {
+	if len(names) == 1 && names[0] == "" {
+		return n
+	}
+	for {
+		if len(names) == 0 {
+			return n
+		}
+		if next, ok := n.children[names[0]]; ok {
+			n = next
+			names = names[1:]
+			continue
+		}
+		if create {
+			nn := newNode()
+			n.children[names[0]] = nn
+			n = nn
+			names = names[1:]
+			continue
+		}
+		return nil
+	}
+}
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 18d8684..42172dd 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -16,13 +16,11 @@
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/options"
 	"veyron.io/veyron/veyron2/security"
-	mttypes "veyron.io/veyron/veyron2/services/mounttable/types"
 	"veyron.io/veyron/veyron2/verror"
 	"veyron.io/veyron/veyron2/vlog"
 	"veyron.io/veyron/veyron2/vom"
 	"veyron.io/veyron/veyron2/vtrace"
 
-	"veyron.io/veyron/veyron/lib/glob"
 	"veyron.io/veyron/veyron/lib/netstate"
 	"veyron.io/veyron/veyron/runtimes/google/lib/publisher"
 	inaming "veyron.io/veyron/veyron/runtimes/google/naming"
@@ -809,6 +807,7 @@
 		return nil, verr
 	}
 	fs.method = req.Method
+	fs.suffix = req.Suffix
 
 	// TODO(mattr): Currently this allows users to trigger trace collection
 	// on the server even if they will not be allowed to collect the
@@ -861,14 +860,13 @@
 		fs.discharges[d.ID()] = d
 	}
 	// Lookup the invoker.
-	invoker, auth, suffix, verr := fs.lookup(req.Suffix, req.Method)
-	fs.suffix = suffix // with leading /'s stripped
+	invoker, auth, verr := fs.lookup(&fs.suffix, &fs.method)
 	if verr != nil {
 		return nil, verr
 	}
 	// Prepare invoker and decode args.
 	numArgs := int(req.NumPosArgs)
-	argptrs, tags, err := invoker.Prepare(req.Method, numArgs)
+	argptrs, tags, err := invoker.Prepare(fs.method, numArgs)
 	fs.tags = tags
 	if err != nil {
 		return nil, verror.Makef(verror.ErrorID(err), "%s: name: %q", err, req.Suffix)
@@ -893,42 +891,40 @@
 		fs.allowDebug = fs.authorizeForDebug(auth) == nil
 	}
 
-	results, err := invoker.Invoke(req.Method, fs, argptrs)
-	fs.server.stats.record(req.Method, time.Since(fs.starttime))
+	results, err := invoker.Invoke(fs.method, fs, argptrs)
+	fs.server.stats.record(fs.method, time.Since(fs.starttime))
 	return results, verror.Convert(err)
 }
 
 // lookup returns the invoker and authorizer responsible for serving the given
 // name and method.  The name is stripped of any leading slashes. If it begins
 // with ipc.DebugKeyword, we use the internal debug dispatcher to look up the
-// invoker. Otherwise, and we use the server's dispatcher. The (stripped) name
-// and dispatch suffix are also returned.
-func (fs *flowServer) lookup(name, method string) (ipc.Invoker, security.Authorizer, string, verror.E) {
-	name = strings.TrimLeft(name, "/")
-	if method == "Glob" && len(name) == 0 {
-		return ipc.ReflectInvoker(&globInvoker{"__debug", fs}), &acceptAllAuthorizer{}, name, nil
+// invoker. Otherwise, and we use the server's dispatcher. The name and method
+// value may be modified to match the actual name and method to use.
+func (fs *flowServer) lookup(name, method *string) (ipc.Invoker, security.Authorizer, verror.E) {
+	*name = strings.TrimLeft(*name, "/")
+	// TODO(rthellend): Remove "Glob" from the condition below after
+	// everything has transitioned to the new name.
+	if *method == "Glob" || *method == ipc.GlobMethod {
+		*method = "Glob"
+		return ipc.ReflectInvoker(&globInternal{fs, *name}), &acceptAllAuthorizer{}, nil
 	}
-	disp := fs.disp
-	if strings.HasPrefix(name, naming.ReservedNamePrefix) {
-		parts := strings.SplitN(name, "/", 2)
-		if len(parts) > 1 {
-			name = parts[1]
-		} else {
-			name = ""
-		}
+	var disp ipc.Dispatcher
+	if naming.IsReserved(*name) {
 		disp = fs.reservedOpt.Dispatcher
+	} else {
+		disp = fs.disp
 	}
-
 	if disp != nil {
-		invoker, auth, err := lookupInvoker(disp, name, method)
+		invoker, auth, err := lookupInvoker(disp, *name, *method)
 		switch {
 		case err != nil:
-			return nil, nil, "", verror.Convert(err)
+			return nil, nil, verror.Convert(err)
 		case invoker != nil:
-			return invoker, auth, name, nil
+			return invoker, auth, nil
 		}
 	}
-	return nil, nil, "", verror.NoExistf("ipc: invoker not found for %q", name)
+	return nil, nil, verror.NoExistf("ipc: invoker not found for %q", *name)
 }
 
 type acceptAllAuthorizer struct{}
@@ -937,99 +933,6 @@
 	return nil
 }
 
-type globInvoker struct {
-	prefix string
-	fs     *flowServer
-}
-
-// Glob matches the pattern against internal object names if the double-
-// underscore prefix is explicitly part of the pattern. Otherwise, it invokes
-// the service's Glob method.
-func (i *globInvoker) Glob(call ipc.ServerCall, pattern string) error {
-	g, err := glob.Parse(pattern)
-	if err != nil {
-		return err
-	}
-	if strings.HasPrefix(pattern, naming.ReservedNamePrefix) {
-		var err error
-		// Match against internal object names.
-		if ok, _, left := g.MatchInitialSegment(i.prefix); ok {
-			if ierr := i.invokeGlob(call, i.fs.reservedOpt.Dispatcher, i.prefix, left.String()); ierr != nil {
-				err = ierr
-			}
-		}
-		return err
-	}
-	// Invoke the service's method.
-	return i.invokeGlob(call, i.fs.disp, "", pattern)
-}
-
-func (i *globInvoker) invokeGlob(call ipc.ServerCall, d ipc.Dispatcher, prefix, pattern string) error {
-	if d == nil {
-		return nil
-	}
-	obj, auth, err := d.Lookup("", "Glob")
-	if err != nil {
-		return err
-	}
-	// TODO(cnicolaou): ipc.Serve TRANSITION
-	invoker, ok := obj.(ipc.Invoker)
-	if !ok {
-		panic(fmt.Errorf("Lookup should have returned an ipc.Invoker, returned %T", obj))
-	}
-	if obj == nil || !ok {
-		return verror.NoExistf("ipc: invoker not found for Glob")
-	}
-
-	argptrs, tags, err := invoker.Prepare("Glob", 1)
-	i.fs.tags = tags
-	if err != nil {
-		return verror.Makef(verror.ErrorID(err), "%s", err)
-	}
-	if err := i.fs.authorize(auth); err != nil {
-		return err
-	}
-	leafCall := &localServerCall{call, prefix}
-	argptrs[0] = &pattern
-	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 that prepends a prefix to all the names in the streamed
-// MountEntry objects.
-type localServerCall struct {
-	ipc.ServerCall
-	prefix string
-}
-
-var _ ipc.ServerCall = (*localServerCall)(nil)
-var _ ipc.Stream = (*localServerCall)(nil)
-var _ ipc.ServerContext = (*localServerCall)(nil)
-
-func (c *localServerCall) Send(v interface{}) error {
-	me, ok := v.(mttypes.MountEntry)
-	if !ok {
-		return verror.BadArgf("unexpected stream type. Got %T, want MountEntry", v)
-	}
-	me.Name = naming.Join(c.prefix, me.Name)
-	return c.ServerCall.Send(me)
-}
-
 func (fs *flowServer) authorize(auth security.Authorizer) verror.E {
 	if auth == nil {
 		auth = defaultAuthorizer{}
diff --git a/services/mgmt/debug/dispatcher.go b/services/mgmt/debug/dispatcher.go
index 9f03323..30a8750 100644
--- a/services/mgmt/debug/dispatcher.go
+++ b/services/mgmt/debug/dispatcher.go
@@ -4,13 +4,12 @@
 	"strings"
 	"time"
 
-	"veyron.io/veyron/veyron/services/mgmt/lib/toplevelglob"
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/security"
+
 	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/security"
 )
 
 // dispatcher holds the state of the debug dispatcher.
@@ -25,13 +24,24 @@
 	return &dispatcher{logsDir, authorizer}
 }
 
+// The first part of the names of the objects served by this dispatcher.
+var rootName = "__debug"
+
 func (d *dispatcher) Lookup(suffix, method string) (interface{}, security.Authorizer, error) {
+	if suffix == "" {
+		return ipc.VChildrenGlobberInvoker(rootName), d.auth, nil
+	}
+	if !strings.HasPrefix(suffix, rootName) {
+		return nil, nil, nil
+	}
+	suffix = strings.TrimPrefix(suffix, rootName)
+	suffix = strings.TrimLeft(suffix, "/")
+
 	if method == "Signature" {
 		return NewSignatureInvoker(suffix), d.auth, nil
 	}
-	if len(suffix) == 0 {
-		leaves := []string{"logs", "pprof", "stats"}
-		return toplevelglob.New(d, leaves), d.auth, nil
+	if suffix == "" {
+		return ipc.VChildrenGlobberInvoker("logs", "pprof", "stats"), d.auth, nil
 	}
 	parts := strings.SplitN(suffix, "/", 2)
 	if len(parts) == 2 {
@@ -41,7 +51,7 @@
 	}
 	switch parts[0] {
 	case "logs":
-		if method == "Glob" {
+		if method == ipc.GlobMethod {
 			return logreaderimpl.NewLogDirectoryInvoker(d.logsDir, suffix), d.auth, nil
 		}
 		return logreaderimpl.NewLogFileInvoker(d.logsDir, suffix), d.auth, nil
diff --git a/services/mgmt/debug/server_test.go b/services/mgmt/debug/dispatcher_test.go
similarity index 81%
rename from services/mgmt/debug/server_test.go
rename to services/mgmt/debug/dispatcher_test.go
index 137181e..4d6f6b9 100644
--- a/services/mgmt/debug/server_test.go
+++ b/services/mgmt/debug/dispatcher_test.go
@@ -1,6 +1,7 @@
-package debug_test
+package debug
 
 import (
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -10,6 +11,8 @@
 	"testing"
 	"time"
 
+	"veyron.io/veyron/veyron2"
+	"veyron.io/veyron/veyron2/ipc"
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/rt"
 	"veyron.io/veyron/veyron2/services/mgmt/logreader"
@@ -19,11 +22,32 @@
 
 	libstats "veyron.io/veyron/veyron/lib/stats"
 	"veyron.io/veyron/veyron/profiles"
-	"veyron.io/veyron/veyron/services/mgmt/debug"
 )
 
+// startDebugServer starts a debug server.
+func startDebugServer(rt veyron2.Runtime, listenSpec ipc.ListenSpec, logsDir string) (string, func(), error) {
+	if len(logsDir) == 0 {
+		return "", nil, fmt.Errorf("logs directory missing")
+	}
+	disp := NewDispatcher(logsDir, nil)
+	server, err := rt.NewServer()
+	if err != nil {
+		return "", nil, fmt.Errorf("failed to start debug server: %v", err)
+	}
+	endpoint, err := server.Listen(listenSpec)
+	if err != nil {
+		return "", nil, fmt.Errorf("failed to listen on %s: %v", listenSpec, err)
+	}
+	if err := server.ServeDispatcher("", disp); err != nil {
+		return "", nil, err
+	}
+	ep := endpoint.String()
+	return ep, func() { server.Stop() }, nil
+}
+
 func TestDebugServer(t *testing.T) {
 	runtime := rt.Init()
+	rootName = "debug"
 
 	workdir, err := ioutil.TempDir("", "logreadertest")
 	if err != nil {
@@ -34,7 +58,7 @@
 		t.Fatalf("ioutil.WriteFile failed: %v", err)
 	}
 
-	endpoint, stop, err := debug.StartDebugServer(runtime, profiles.LocalListenSpec, workdir, nil)
+	endpoint, stop, err := startDebugServer(runtime, profiles.LocalListenSpec, workdir)
 	if err != nil {
 		t.Fatalf("StartDebugServer failed: %v", err)
 	}
@@ -42,7 +66,7 @@
 
 	// Access a logs directory that exists.
 	{
-		ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "//logs"))
+		ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "debug/logs"))
 		stream, err := ld.Glob(runtime.NewContext(), "*")
 		if err != nil {
 			t.Errorf("Glob failed: %v", err)
@@ -65,7 +89,7 @@
 
 	// Access a logs directory that doesn't exist.
 	{
-		ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "//logs/nowheretobefound"))
+		ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "debug/logs/nowheretobefound"))
 		stream, err := ld.Glob(runtime.NewContext(), "*")
 		if err != nil {
 			t.Errorf("Glob failed: %v", err)
@@ -85,7 +109,7 @@
 
 	// Access a log file that exists.
 	{
-		lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "//logs/test.INFO"))
+		lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "debug/logs/test.INFO"))
 		size, err := lf.Size(runtime.NewContext())
 		if err != nil {
 			t.Errorf("Size failed: %v", err)
@@ -97,7 +121,7 @@
 
 	// Access a log file that doesn't exist.
 	{
-		lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "//logs/nosuchfile.INFO"))
+		lf := logreader.LogFileClient(naming.JoinAddressName(endpoint, "debug/logs/nosuchfile.INFO"))
 		_, err = lf.Size(runtime.NewContext())
 		if expected := verror.NoExist; !verror.Is(err, expected) {
 			t.Errorf("unexpected error value, got %v, want: %v", err, expected)
@@ -109,7 +133,7 @@
 		foo := libstats.NewInteger("testing/foo")
 		foo.Set(123)
 
-		st := stats.StatsClient(naming.JoinAddressName(endpoint, "//stats/testing/foo"))
+		st := stats.StatsClient(naming.JoinAddressName(endpoint, "debug/stats/testing/foo"))
 		v, err := st.Value(runtime.NewContext())
 		if err != nil {
 			t.Errorf("Value failed: %v", err)
@@ -121,7 +145,7 @@
 
 	// Access a stats object that doesn't exists.
 	{
-		st := stats.StatsClient(naming.JoinAddressName(endpoint, "//stats/testing/nobodyhome"))
+		st := stats.StatsClient(naming.JoinAddressName(endpoint, "debug/stats/testing/nobodyhome"))
 		_, err = st.Value(runtime.NewContext())
 		if expected := verror.NoExist; !verror.Is(err, expected) {
 			t.Errorf("unexpected error value, got %v, want: %v", err, expected)
@@ -131,7 +155,7 @@
 	// Glob from the root.
 	{
 		ns := rt.R().Namespace()
-		ns.SetRoots(naming.JoinAddressName(endpoint, "//"))
+		ns.SetRoots(naming.JoinAddressName(endpoint, "debug"))
 		ctx, cancel := rt.R().NewContext().WithTimeout(10 * time.Second)
 		defer cancel()
 		c, err := ns.Glob(ctx, "logs/...")
diff --git a/services/mgmt/debug/server.go b/services/mgmt/debug/server.go
deleted file mode 100644
index 4cff33a..0000000
--- a/services/mgmt/debug/server.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Package debug implements a server that exports debugging information from
-// various sources: log files, stats, etc.
-package debug
-
-import (
-	"fmt"
-
-	"veyron.io/veyron/veyron2"
-	"veyron.io/veyron/veyron2/ipc"
-	"veyron.io/veyron/veyron2/security"
-	"veyron.io/veyron/veyron2/vlog"
-)
-
-// StartDebugServer starts a debug server.
-func StartDebugServer(rt veyron2.Runtime, listenSpec ipc.ListenSpec, logsDir string, auth security.Authorizer) (string, func(), error) {
-	if len(logsDir) == 0 {
-		return "", nil, fmt.Errorf("logs directory missing")
-	}
-	disp := NewDispatcher(logsDir, auth)
-	server, err := rt.NewServer()
-	if err != nil {
-		return "", nil, fmt.Errorf("failed to start debug server: %v", err)
-	}
-	endpoint, err := server.Listen(listenSpec)
-	if err != nil {
-		return "", nil, fmt.Errorf("failed to listen on %s: %v", listenSpec, err)
-	}
-	if err := server.ServeDispatcher("", disp); err != nil {
-		return "", nil, err
-	}
-	ep := endpoint.String()
-	vlog.VI(1).Infof("Debug server listening on %v", ep)
-	return ep, func() { server.Stop() }, nil
-}
diff --git a/services/mgmt/lib/toplevelglob/invoker.go b/services/mgmt/lib/toplevelglob/invoker.go
deleted file mode 100644
index a73486e..0000000
--- a/services/mgmt/lib/toplevelglob/invoker.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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
-	}
-	if g.Len() == 0 {
-		call.Send(types.MountEntry{Name: ""})
-	}
-	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 {
-	obj, _, err := i.d.Lookup(leaf, "Glob")
-	if err != nil {
-		return err
-	}
-	// Lookup must return an invoker if it implements its own glob
-	invoker, ok := obj.(ipc.Invoker)
-	if !ok {
-		return nil
-	}
-	if invoker == nil {
-		return verror.BadArgf("failed to find invoker for %q", leaf)
-	}
-	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/dispatcher.go b/services/mgmt/node/impl/dispatcher.go
index 2b72dcc..f41df15 100644
--- a/services/mgmt/node/impl/dispatcher.go
+++ b/services/mgmt/node/impl/dispatcher.go
@@ -14,7 +14,6 @@
 	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"
 	logsimpl "veyron.io/veyron/veyron/services/mgmt/logreader/impl"
 	inode "veyron.io/veyron/veyron/services/mgmt/node"
 	"veyron.io/veyron/veyron/services/mgmt/node/config"
@@ -231,8 +230,8 @@
 		}
 	}
 	if len(components) == 0 {
-		if method == "Glob" {
-			return toplevelglob.New(d, []string{nodeSuffix, appsSuffix}), d.auth, nil
+		if method == ipc.GlobMethod {
+			return ipc.VChildrenGlobberInvoker(nodeSuffix, appsSuffix), d.auth, nil
 		}
 		return nil, nil, errInvalidSuffix
 	}
@@ -256,7 +255,7 @@
 		// Requests to apps/*/*/*/pprof are proxied to the apps' __debug/pprof object.
 		// Requests to apps/*/*/*/stats are proxied to the apps' __debug/stats object.
 		// Everything else is handled by appInvoker.
-		if len(components) >= 5 && (method != "Glob" || components[4] != "logs") {
+		if len(components) >= 5 && (method != ipc.GlobMethod || components[4] != "logs") {
 			appInstanceDir, err := instanceDir(d.config.Root, components[1:4])
 			if err != nil {
 				return nil, nil, err
diff --git a/services/mgmt/node/impl/proxy_invoker.go b/services/mgmt/node/impl/proxy_invoker.go
index fb52ef7..643ad5f 100644
--- a/services/mgmt/node/impl/proxy_invoker.go
+++ b/services/mgmt/node/impl/proxy_invoker.go
@@ -122,8 +122,26 @@
 }
 
 func (p *proxyInvoker) VGlob() *ipc.GlobState {
-	// TODO(rthellend): Add implementation
-	return nil
+	return &ipc.GlobState{VAllGlobber: p}
+}
+
+func (p *proxyInvoker) Glob(call ipc.ServerCall, pattern string) error {
+	argptrs := []interface{}{&pattern}
+	results, err := p.Invoke(ipc.GlobMethod, call, argptrs)
+	if err != nil {
+		return err
+	}
+	if len(results) != 1 {
+		return fmt.Errorf("unexpected number of result values. Got %d, want 1.", len(results))
+	}
+	if results[0] == nil {
+		return nil
+	}
+	err, ok := results[0].(error)
+	if !ok {
+		return fmt.Errorf("unexpected result value type. Got %T, want error.", err)
+	}
+	return err
 }
 
 // numResults returns the number of result values for the given method.
@@ -132,6 +150,9 @@
 	if err != nil {
 		return 0, err
 	}
+	if method == ipc.GlobMethod {
+		method = "Glob"
+	}
 	m, ok := sig.Methods[method]
 	if !ok {
 		return 0, fmt.Errorf("unknown method %q", method)