Merge "veyron/tools/mgmt/nodex: create a unified node manager tool"
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 8451c3e..c4903d2 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -590,13 +590,15 @@
 // validation for a server's blessings are eanbled.
 // Returning zero values affects more than third-party caveats, so yeah, have
 // to remove them soon!
-func (c serverAuthContext) Timestamp() time.Time                    { return c.timestamp }
-func (serverAuthContext) Method() string                            { return "" }
-func (serverAuthContext) MethodTags() []interface{}                 { return nil }
-func (serverAuthContext) Name() string                              { return "" }
-func (serverAuthContext) Suffix() string                            { return "" }
-func (serverAuthContext) Label() (l security.Label)                 { return l }
-func (serverAuthContext) Discharges() map[string]security.Discharge { return nil }
+func (c serverAuthContext) Timestamp() time.Time    { return c.timestamp }
+func (serverAuthContext) Method() string            { return "" }
+func (serverAuthContext) MethodTags() []interface{} { return nil }
+func (serverAuthContext) Name() string              { return "" }
+func (serverAuthContext) Suffix() string            { return "" }
+func (serverAuthContext) Label() (l security.Label) { return l }
+
+// TODO(ataly): Remove this once the method is added to the flow type?
+func (serverAuthContext) RemoteDischarges() map[string]security.Discharge { return nil }
 
 func splitObjectName(name string) (mtPattern, serverPattern security.BlessingPattern, objectName string) {
 	objectName = name
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index c30c097..649e8b4 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -495,11 +495,14 @@
 	s.Lock()
 	defer s.Unlock()
 	ivtrace.FromContext(s.ctx).Annotate("Serving under name: " + name)
+	if len(name) == 0 {
+		return fmt.Errorf("empty name")
+	}
 	if s.stopped {
 		return errServerStopped
 	}
-	if len(name) == 0 {
-		return fmt.Errorf("empty name")
+	if s.disp == nil {
+		return fmt.Errorf("Adding name before calling Serve or ServeDispatcher is not allowed")
 	}
 	s.publisher.AddName(name)
 	// TODO(cnicolaou): remove this map when the publisher's RemoveName
@@ -515,6 +518,9 @@
 	if s.stopped {
 		return errServerStopped
 	}
+	if s.disp == nil {
+		return fmt.Errorf("Removing name before calling Serve or ServeDispatcher is not allowed")
+	}
 	if _, present := s.names[name]; !present {
 		return fmt.Errorf("%q has not been previously used for this server", name)
 	}
@@ -921,7 +927,7 @@
 
 // Implementations of ipc.ServerContext methods.
 
-func (fs *flowServer) Discharges() map[string]security.Discharge {
+func (fs *flowServer) RemoteDischarges() map[string]security.Discharge {
 	//nologcall
 	return fs.discharges
 }
diff --git a/runtimes/google/ipc/stream/vc/auth.go b/runtimes/google/ipc/stream/vc/auth.go
index 439ead8..5fe37cf 100644
--- a/runtimes/google/ipc/stream/vc/auth.go
+++ b/runtimes/google/ipc/stream/vc/auth.go
@@ -5,13 +5,11 @@
 	"errors"
 	"fmt"
 	"io"
-	"time"
 
 	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/crypto"
 	"veyron.io/veyron/veyron/runtimes/google/lib/iobuf"
 
 	"veyron.io/veyron/veyron2/ipc/version"
-	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/security"
 	"veyron.io/veyron/veyron2/vom"
 )
@@ -58,10 +56,9 @@
 	if server, err = readBlessings(conn, authServerContextTag, crypter, v); err != nil {
 		return nil, nil, err
 	}
-	serverB := server.ForContext(&serverAuthContext{
-		self:      principal,
-		remote:    server,
-		timestamp: time.Now(),
+	serverB := server.ForContext(security.NewContext(&security.ContextParams{
+		LocalPrincipal:  principal,
+		RemoteBlessings: server,
 		// TODO(ashankar): Get the local and remote endpoint here?
 		// There is also a bootstrapping problem here. For example, let's say
 		// (1) server has the blessing "provider/server" with a PeerIdentity caveat of "provider/client"
@@ -69,7 +66,7 @@
 		// How do we get that working?
 		// One option is to have a UnionOfBlessings of all blessings of the client in the BlessingStore
 		// made available to serverAuthContext.LocalBlessings for this call.
-	})
+	}))
 	client = principal.BlessingStore().ForPeer(serverB...)
 	if client == nil {
 		return nil, nil, fmt.Errorf("no blessing tagged for peer %v in the BlessingStore", serverB)
@@ -132,24 +129,3 @@
 	}
 	return b, nil
 }
-
-// security.Context implementation used when extracting blessings from what the
-// server presents during authentication.
-type serverAuthContext struct {
-	self      security.Principal
-	remote    security.Blessings
-	timestamp time.Time
-}
-
-func (c *serverAuthContext) Timestamp() time.Time                      { return c.timestamp }
-func (*serverAuthContext) Method() string                              { return "" }
-func (*serverAuthContext) MethodTags() []interface{}                   { return nil }
-func (*serverAuthContext) Name() string                                { return "" }
-func (*serverAuthContext) Suffix() string                              { return "" }
-func (*serverAuthContext) Label() (l security.Label)                   { return l }
-func (c *serverAuthContext) Discharges() map[string]security.Discharge { return nil }
-func (c *serverAuthContext) LocalPrincipal() security.Principal        { return c.self }
-func (c *serverAuthContext) LocalBlessings() security.Blessings        { return nil }
-func (c *serverAuthContext) RemoteBlessings() security.Blessings       { return c.remote }
-func (c *serverAuthContext) LocalEndpoint() naming.Endpoint            { return nil }
-func (c *serverAuthContext) RemoteEndpoint() naming.Endpoint           { return nil }
diff --git a/runtimes/google/rt/rt_test.go b/runtimes/google/rt/rt_test.go
index e391f1f..84085db 100644
--- a/runtimes/google/rt/rt_test.go
+++ b/runtimes/google/rt/rt_test.go
@@ -10,7 +10,6 @@
 	"testing"
 	"time"
 
-	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/options"
 	"veyron.io/veyron/veyron2/rt"
 	"veyron.io/veyron/veyron2/security"
@@ -23,23 +22,6 @@
 	vsecurity "veyron.io/veyron/veyron/security"
 )
 
-type context struct {
-	local security.Principal
-}
-
-func (*context) Timestamp() (t time.Time)                  { return t }
-func (*context) Method() string                            { return "" }
-func (*context) MethodTags() []interface{}                 { return nil }
-func (*context) Name() string                              { return "" }
-func (*context) Suffix() string                            { return "" }
-func (*context) Label() (l security.Label)                 { return }
-func (*context) Discharges() map[string]security.Discharge { return nil }
-func (c *context) LocalPrincipal() security.Principal      { return c.local }
-func (*context) LocalBlessings() security.Blessings        { return nil }
-func (*context) RemoteBlessings() security.Blessings       { return nil }
-func (*context) LocalEndpoint() naming.Endpoint            { return nil }
-func (*context) RemoteEndpoint() naming.Endpoint           { return nil }
-
 func init() {
 	testutil.Init()
 	modules.RegisterChild("child", "", child)
@@ -120,7 +102,8 @@
 		return fmt.Errorf("rt.Principal().BlessingStore().Default() returned nil")
 
 	}
-	if n := len(blessings.ForContext(&context{local: p})); n != 1 {
+	ctx := security.NewContext(&security.ContextParams{LocalPrincipal: p})
+	if n := len(blessings.ForContext(ctx)); n != 1 {
 		fmt.Errorf("rt.Principal().BlessingStore().Default() returned Blessing %v with %d recognized blessings, want exactly one recognized blessing", blessings, n)
 	}
 	return nil
diff --git a/security/acl_authorizer_test.go b/security/acl_authorizer_test.go
index f8785fa..98b0e2a 100644
--- a/security/acl_authorizer_test.go
+++ b/security/acl_authorizer_test.go
@@ -5,33 +5,10 @@
 	"os"
 	"runtime"
 	"testing"
-	"time"
 
-	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/security"
 )
 
-// context implements security.Context.
-type context struct {
-	localPrincipal                  security.Principal
-	localBlessings, remoteBlessings security.Blessings
-	method                          string
-	label                           security.Label
-}
-
-func (c *context) Timestamp() (t time.Time)                  { return t }
-func (c *context) Method() string                            { return c.method }
-func (c *context) MethodTags() []interface{}                 { return nil }
-func (c *context) Name() string                              { return "" }
-func (c *context) Suffix() string                            { return "" }
-func (c *context) Label() security.Label                     { return c.label }
-func (c *context) Discharges() map[string]security.Discharge { return nil }
-func (c *context) LocalPrincipal() security.Principal        { return c.localPrincipal }
-func (c *context) LocalBlessings() security.Blessings        { return c.localBlessings }
-func (c *context) RemoteBlessings() security.Blessings       { return c.remoteBlessings }
-func (c *context) LocalEndpoint() naming.Endpoint            { return nil }
-func (c *context) RemoteEndpoint() naming.Endpoint           { return nil }
-
 func saveACLToTempFile(acl security.ACL) string {
 	f, err := ioutil.TempFile("", "saved_acl")
 	if err != nil {
@@ -62,31 +39,25 @@
 		pserver, server = newPrincipal("server")
 		_, imposter     = newPrincipal("server")
 		palice, alice   = newPrincipal("alice")
+		aliceServer     = bless(palice, pserver, alice, "server")
 
-		serverAlice = bless(pserver, palice, server, "alice")
-		aliceServer = bless(palice, pserver, alice, "server")
-
-		ctx = &context{
-			localPrincipal: pserver,
-			localBlessings: server,
-		}
-
+		ctxp  = &security.ContextParams{LocalPrincipal: pserver, LocalBlessings: server}
 		tests = []struct {
 			remote       security.Blessings
 			isAuthorized bool
 		}{
 			{server, true},
 			{imposter, false},
-			{serverAlice, false},
 			// A principal talking to itself (even if with a different blessing) is authorized.
 			// TODO(ashankar,ataly): Is this a desired property?
 			{aliceServer, true},
 		}
 	)
 	for _, test := range tests {
-		ctx.remoteBlessings = test.remote
+		ctxp.RemoteBlessings = test.remote
+		ctx := security.NewContext(ctxp)
 		if got, want := authorizer.Authorize(ctx), test.isAuthorized; (got == nil) != want {
-			t.Errorf("%s:%d: %+v.Authorize(&context{local: %v, remote: %v}) returned error: %v, want error: %v", file, line, authorizer, ctx.localBlessings, ctx.remoteBlessings, got, !want)
+			t.Errorf("%s:%d: %+v.Authorize(%v) returned error: %v, want error: %v", file, line, authorizer, ctx, got, !want)
 		}
 	}
 }
@@ -119,13 +90,23 @@
 	// valid or invalid label.
 	for _, u := range users {
 		for _, l := range security.ValidLabels {
-			ctx := &context{localPrincipal: pserver, localBlessings: server, remoteBlessings: u, label: l}
+			ctx := security.NewContext(&security.ContextParams{
+				LocalPrincipal:  pserver,
+				LocalBlessings:  server,
+				RemoteBlessings: u,
+				MethodTags:      []interface{}{l},
+			})
 			if got := authorizer.Authorize(ctx); got == nil {
 				t.Errorf("%s:%d: %+v.Authorize(%v) returns nil, want error", file, line, authorizer, ctx)
 			}
 		}
 
-		ctx := &context{localPrincipal: pserver, localBlessings: server, remoteBlessings: u, label: invalidLabel}
+		ctx := security.NewContext(&security.ContextParams{
+			LocalPrincipal:  pserver,
+			LocalBlessings:  server,
+			RemoteBlessings: u,
+			MethodTags:      []interface{}{invalidLabel},
+		})
 		if got := authorizer.Authorize(ctx); got == nil {
 			t.Errorf("%s:%d: %+v.Authorize(%v) returns nil, want error", file, line, authorizer, ctx)
 		}
@@ -164,9 +145,14 @@
 			_, file, line, _ := runtime.Caller(1)
 			for user, labels := range expectations {
 				for _, l := range security.ValidLabels {
-					ctx := &context{remoteBlessings: user, localBlessings: server, localPrincipal: pserver, label: l}
+					ctx := security.NewContext(&security.ContextParams{
+						LocalPrincipal:  pserver,
+						LocalBlessings:  server,
+						RemoteBlessings: user,
+						MethodTags:      []interface{}{l},
+					})
 					if got, want := authorizer.Authorize(ctx), labels.HasLabel(l); (got == nil) != want {
-						t.Errorf("%s:%d: %+v.Authorize(&context{remoteBlessings: %v, label: %v}) returned error: %v, want error: %v", file, line, authorizer, user, l, got, !want)
+						t.Errorf("%s:%d: %+v.Authorize(%v) returned error: %v, want error: %v", file, line, authorizer, ctx, got, !want)
 					}
 				}
 			}
diff --git a/services/GO.PACKAGE b/services/GO.PACKAGE
index e93556b..7a09896 100644
--- a/services/GO.PACKAGE
+++ b/services/GO.PACKAGE
@@ -6,6 +6,7 @@
 			{"allow": "veyron.io/veyron/veyron/profiles/...", "comment":"temporarily allowing dependency from profiles"},
 			{"allow": "veyron.io/veyron/veyron/tools/...", "comment":"temporarily allowing dependency from veyron/tools"},
 			{"allow": "veyron.io/veyron/veyron/runtimes/google/...", "comment":"temporarily allowing dependency from veyron/runtimes/google"},
+			{"allow": "veyron.io/jni/veyron/runtimes/google/...", "comment":"temporarily allowing dependency from veyron/runtimes/google"},
 			{"deny": "..."}
 		]
 	}
diff --git a/services/mgmt/binary/binaryd/test.sh b/services/mgmt/binary/binaryd/test.sh
index 2fd1661..51b7201 100755
--- a/services/mgmt/binary/binaryd/test.sh
+++ b/services/mgmt/binary/binaryd/test.sh
@@ -23,7 +23,7 @@
 
   # Start the binary repository daemon.
   local -r REPO="binaryd-test-repo"
-  shell_test::start_server "${BINARYD_BIN}" --name="${REPO}" --veyron.tcp.address=127.0.0.1:0 \
+  shell_test::start_server "${BINARYD_BIN}" --name="${REPO}" --veyron.tcp.address=127.0.0.1:0 --http=127.0.0.1:0 \
     || shell_test::fail "line ${LINENO} failed to start binaryd"
   local -r HTTP_ADDR=$(grep 'HTTP server at: "' "${START_SERVER_LOG_FILE}" | sed -e 's/^.*HTTP server at: "//' | sed -e 's/"$//')
 
diff --git a/services/mgmt/debug/dispatcher.go b/services/mgmt/debug/dispatcher.go
index 89154c5..0c855eb 100644
--- a/services/mgmt/debug/dispatcher.go
+++ b/services/mgmt/debug/dispatcher.go
@@ -54,9 +54,6 @@
 	}
 	switch parts[0] {
 	case "logs":
-		if method == ipc.GlobMethod {
-			return logreaderimpl.NewLogDirectoryInvoker(d.logsDir, suffix), d.auth, nil
-		}
 		return logreaderimpl.NewLogFileInvoker(d.logsDir, suffix), d.auth, nil
 	case "pprof":
 		return pprofimpl.NewInvoker(), d.auth, nil
diff --git a/services/mgmt/debug/dispatcher_test.go b/services/mgmt/debug/dispatcher_test.go
index 2a19ee3..2d9236b 100644
--- a/services/mgmt/debug/dispatcher_test.go
+++ b/services/mgmt/debug/dispatcher_test.go
@@ -83,8 +83,8 @@
 		if len(results) != 0 {
 			t.Errorf("unexpected result. Got %v, want ''", results)
 		}
-		if expected := verror.NoExist; !verror.Is(err, expected) {
-			t.Errorf("unexpected error value, got %v, want: %v", err, expected)
+		if err != nil {
+			t.Errorf("unexpected error value: %v", err)
 		}
 	}
 
diff --git a/services/mgmt/logreader/impl/common_test.go b/services/mgmt/logreader/impl/common_test.go
deleted file mode 100644
index 91194ad..0000000
--- a/services/mgmt/logreader/impl/common_test.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package impl_test
-
-import (
-	"testing"
-
-	"veyron.io/veyron/veyron2/ipc"
-	"veyron.io/veyron/veyron2/rt"
-
-	"veyron.io/veyron/veyron/profiles"
-)
-
-func startServer(t *testing.T, disp ipc.Dispatcher) (ipc.Server, string, error) {
-	server, err := rt.R().NewServer()
-	if err != nil {
-		t.Fatalf("NewServer failed: %v", err)
-		return nil, "", err
-	}
-	endpoint, err := server.Listen(profiles.LocalListenSpec)
-	if err != nil {
-		t.Fatalf("Listen failed: %v", err)
-		return nil, "", err
-	}
-	if err := server.ServeDispatcher("", disp); err != nil {
-		t.Fatalf("Serve failed: %v", err)
-		return nil, "", err
-	}
-	return server, endpoint.String(), nil
-}
-
-func stopServer(t *testing.T, server ipc.Server) {
-	if err := server.Stop(); err != nil {
-		t.Errorf("server.Stop failed: %v", err)
-	}
-}
diff --git a/services/mgmt/logreader/impl/logdir_invoker.go b/services/mgmt/logreader/impl/logdir_invoker.go
deleted file mode 100644
index ef7a60b..0000000
--- a/services/mgmt/logreader/impl/logdir_invoker.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package impl
-
-import (
-	"os"
-	"path"
-	"path/filepath"
-
-	"veyron.io/veyron/veyron/lib/glob"
-
-	"veyron.io/veyron/veyron2/ipc"
-	"veyron.io/veyron/veyron2/services/mounttable/types"
-	"veyron.io/veyron/veyron2/vlog"
-)
-
-// logDirectoryInvoker holds the state of an invocation.
-type logDirectoryInvoker struct {
-	// root is the root directory from which the object names are based.
-	root string
-	// suffix is the suffix of the current invocation that is assumed to
-	// be used as a relative object name to identify a log directory.
-	suffix string
-}
-
-// NewLogDirectoryInvoker is the invoker factory.
-func NewLogDirectoryInvoker(root, suffix string) ipc.Invoker {
-	return ipc.ReflectInvoker(&logDirectoryInvoker{filepath.Clean(root), suffix})
-}
-
-// Glob streams the name of all the objects that match pattern.
-func (i *logDirectoryInvoker) Glob(call ipc.ServerCall, pattern string) error {
-	vlog.VI(1).Infof("%v.Glob(%v)", i.suffix, pattern)
-	g, err := glob.Parse(pattern)
-	if err != nil {
-		return err
-	}
-	i.root, err = translateNameToFilename(i.root, i.suffix)
-	if err != nil {
-		return err
-	}
-	fi, err := os.Stat(i.root)
-	if err != nil {
-		if os.IsNotExist(err) {
-			return errNotFound
-		}
-		return errOperationFailed
-	}
-	return i.globStep("", g, fi.IsDir(), call)
-}
-
-// globStep applies a glob recursively.
-func (i *logDirectoryInvoker) globStep(name string, g *glob.Glob, isDir bool, call ipc.ServerCall) error {
-	if g.Len() == 0 {
-		if err := call.Send(types.MountEntry{Name: name}); err != nil {
-			return err
-		}
-	}
-	if g.Finished() || !isDir {
-		return nil
-	}
-	dirName, err := translateNameToFilename(i.root, name)
-	if err != nil {
-		return err
-	}
-	f, err := os.Open(dirName)
-	if err != nil {
-		if os.IsNotExist(err) {
-			return errNotFound
-		}
-		return errOperationFailed
-	}
-	fi, err := f.Readdir(0)
-	if err != nil {
-		return errOperationFailed
-	}
-	f.Close()
-	for _, file := range fi {
-		fileName := file.Name()
-		if fileName == "." || fileName == ".." {
-			continue
-		}
-		if ok, _, left := g.MatchInitialSegment(fileName); ok {
-			if err := i.globStep(path.Join(name, fileName), left, file.IsDir(), call); err != nil {
-				return err
-			}
-		}
-
-	}
-	return nil
-}
diff --git a/services/mgmt/logreader/impl/logdir_invoker_test.go b/services/mgmt/logreader/impl/logdir_invoker_test.go
deleted file mode 100644
index f2bdf87..0000000
--- a/services/mgmt/logreader/impl/logdir_invoker_test.go
+++ /dev/null
@@ -1,226 +0,0 @@
-package impl_test
-
-import (
-	"io/ioutil"
-	"os"
-	"path"
-	"reflect"
-	"sort"
-	"testing"
-
-	"veyron.io/veyron/veyron/services/mgmt/logreader/impl"
-
-	"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/verror"
-)
-
-type logDirectoryDispatcher struct {
-	root string
-}
-
-func (d *logDirectoryDispatcher) Lookup(suffix, _ string) (interface{}, security.Authorizer, error) {
-	return impl.NewLogDirectoryInvoker(d.root, suffix), nil, nil
-}
-
-func TestLogDirectory(t *testing.T) {
-	runtime := rt.Init()
-
-	workdir, err := ioutil.TempDir("", "logreadertest")
-	if err != nil {
-		t.Fatalf("ioutil.TempDir: %v", err)
-	}
-	defer os.RemoveAll(workdir)
-	server, endpoint, err := startServer(t, &logDirectoryDispatcher{workdir})
-	if err != nil {
-		t.Fatalf("startServer failed: %v", err)
-	}
-	defer stopServer(t, server)
-
-	if err := os.Mkdir(path.Join(workdir, "subdir"), os.FileMode(0755)); err != nil {
-		t.Fatalf("os.Mkdir failed: %v", err)
-	}
-	tests := []string{
-		"foo.txt",
-		"bar.txt",
-		"subdir/foo2.txt",
-		"subdir/bar2.txt",
-	}
-	for _, s := range tests {
-		if err := ioutil.WriteFile(path.Join(workdir, s), []byte(s), os.FileMode(0644)); err != nil {
-			t.Fatalf("ioutil.WriteFile failed: %v", err)
-		}
-	}
-
-	// Try to access a directory that doesn't exist.
-	{
-		ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "//doesntexist"))
-		stream, err := ld.Glob(runtime.NewContext(), "*")
-		if err != nil {
-			t.Errorf("unexpected error, got %v, want: nil", err)
-		}
-		if err := stream.Finish(); err != nil {
-			if expected := verror.NoExist; !verror.Is(err, expected) {
-				t.Errorf("unexpected error value, got %v, want: %v", err, expected)
-			}
-		}
-	}
-
-	// Try to access a directory that does exist.
-	{
-		ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "//"))
-		stream, err := ld.Glob(runtime.NewContext(), "...")
-		if err != nil {
-			t.Errorf("Glob failed: %v", err)
-		}
-		results := []string{}
-		expected := []string{"", "subdir"}
-		expected = append(expected, tests...)
-		iterator := stream.RecvStream()
-		for count := 0; iterator.Advance(); count++ {
-			results = append(results, iterator.Value().Name)
-		}
-		sort.Strings(expected)
-		sort.Strings(results)
-		if !reflect.DeepEqual(expected, results) {
-			t.Errorf("unexpected result. Got %v, want %v", results, expected)
-		}
-
-		if err := iterator.Err(); err != nil {
-			t.Errorf("unexpected stream error: %v", iterator.Err())
-		}
-		if err := stream.Finish(); err != nil {
-			t.Errorf("Finish failed: %v", err)
-		}
-
-		stream, err = ld.Glob(runtime.NewContext(), "*")
-		if err != nil {
-			t.Errorf("Glob failed: %v", err)
-		}
-		results = []string{}
-		iterator = stream.RecvStream()
-		for count := 0; iterator.Advance(); count++ {
-			results = append(results, iterator.Value().Name)
-		}
-		sort.Strings(results)
-		expected = []string{
-			"bar.txt",
-			"foo.txt",
-			"subdir",
-		}
-		if !reflect.DeepEqual(expected, results) {
-			t.Errorf("unexpected result. Got %v, want %v", results, expected)
-		}
-
-		if err := iterator.Err(); err != nil {
-			t.Errorf("unexpected stream error: %v", iterator.Err())
-		}
-		if err := stream.Finish(); err != nil {
-			t.Errorf("Finish failed: %v", err)
-		}
-
-		stream, err = ld.Glob(runtime.NewContext(), "subdir/*")
-		if err != nil {
-			t.Errorf("Glob failed: %v", err)
-		}
-		results = []string{}
-		iterator = stream.RecvStream()
-		for count := 0; iterator.Advance(); count++ {
-			results = append(results, iterator.Value().Name)
-		}
-		sort.Strings(results)
-		expected = []string{
-			"subdir/bar2.txt",
-			"subdir/foo2.txt",
-		}
-		if !reflect.DeepEqual(expected, results) {
-			t.Errorf("unexpected result. Got %v, want %v", results, expected)
-		}
-
-		if err := iterator.Err(); err != nil {
-			t.Errorf("unexpected stream error: %v", iterator.Err())
-		}
-		if err := stream.Finish(); err != nil {
-			t.Errorf("Finish failed: %v", err)
-		}
-	}
-
-	// Try to access a sub-directory.
-	{
-		ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "//subdir"))
-		stream, err := ld.Glob(runtime.NewContext(), "*")
-		if err != nil {
-			t.Errorf("Glob failed: %v", err)
-		}
-		results := []string{}
-		iterator := stream.RecvStream()
-		for count := 0; iterator.Advance(); count++ {
-			results = append(results, iterator.Value().Name)
-		}
-		sort.Strings(results)
-		expected := []string{
-			"bar2.txt",
-			"foo2.txt",
-		}
-		if !reflect.DeepEqual(expected, results) {
-			t.Errorf("unexpected result. Got %v, want %v", results, expected)
-		}
-
-		if err := iterator.Err(); err != nil {
-			t.Errorf("unexpected stream error: %v", iterator.Err())
-		}
-		if err := stream.Finish(); err != nil {
-			t.Errorf("Finish failed: %v", err)
-		}
-	}
-
-	// Try to access a file.
-	{
-		ld := mounttable.GlobbableClient(naming.JoinAddressName(endpoint, "//foo.txt"))
-		stream, err := ld.Glob(runtime.NewContext(), "*")
-		if err != nil {
-			t.Errorf("Glob failed: %v", err)
-		}
-		results := []string{}
-		iterator := stream.RecvStream()
-		for count := 0; iterator.Advance(); count++ {
-			results = append(results, iterator.Value().Name)
-		}
-		sort.Strings(results)
-		expected := []string{}
-		if !reflect.DeepEqual(expected, results) {
-			t.Errorf("unexpected result. Got %v, want %v", results, expected)
-		}
-
-		if err := iterator.Err(); err != nil {
-			t.Errorf("unexpected stream error: %v", iterator.Err())
-		}
-		if err := stream.Finish(); err != nil {
-			t.Errorf("Finish failed: %v", err)
-		}
-
-		stream, err = ld.Glob(runtime.NewContext(), "")
-		if err != nil {
-			t.Errorf("Glob failed: %v", err)
-		}
-		results = []string{}
-		iterator = stream.RecvStream()
-		for count := 0; iterator.Advance(); count++ {
-			results = append(results, iterator.Value().Name)
-		}
-		sort.Strings(results)
-		expected = []string{""}
-		if !reflect.DeepEqual(expected, results) {
-			t.Errorf("unexpected result. Got %#v, want %#v", results, expected)
-		}
-
-		if err := iterator.Err(); err != nil {
-			t.Errorf("unexpected stream error: %v", iterator.Err())
-		}
-		if err := stream.Finish(); err != nil {
-			t.Errorf("Finish failed: %v", err)
-		}
-	}
-}
diff --git a/services/mgmt/logreader/impl/logfile_invoker.go b/services/mgmt/logreader/impl/logfile_invoker.go
index 00a1b3e..8f4f88c 100644
--- a/services/mgmt/logreader/impl/logfile_invoker.go
+++ b/services/mgmt/logreader/impl/logfile_invoker.go
@@ -81,3 +81,42 @@
 	}
 	return reader.tell(), nil
 }
+
+// VGlobChildren returns the list of files in a directory, or an empty list if
+// the object is a file.
+func (i *logFileInvoker) VGlobChildren() ([]string, error) {
+	vlog.VI(1).Infof("%v.VGlobChildren()", i.suffix)
+	dirName, err := translateNameToFilename(i.root, i.suffix)
+	if err != nil {
+		return nil, err
+	}
+	stat, err := os.Stat(dirName)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil, errNotFound
+		}
+		return nil, errOperationFailed
+	}
+	if !stat.IsDir() {
+		return nil, nil
+	}
+
+	f, err := os.Open(dirName)
+	if err != nil {
+		return nil, errOperationFailed
+	}
+	fi, err := f.Readdir(0)
+	if err != nil {
+		return nil, errOperationFailed
+	}
+	f.Close()
+	children := []string{}
+	for _, file := range fi {
+		fileName := file.Name()
+		if fileName == "." || fileName == ".." {
+			continue
+		}
+		children = append(children, file.Name())
+	}
+	return children, nil
+}
diff --git a/services/mgmt/logreader/impl/logfile_invoker_test.go b/services/mgmt/logreader/impl/logfile_invoker_test.go
index 3f6a478..3defb7e 100644
--- a/services/mgmt/logreader/impl/logfile_invoker_test.go
+++ b/services/mgmt/logreader/impl/logfile_invoker_test.go
@@ -6,8 +6,10 @@
 	"path"
 	"testing"
 
+	"veyron.io/veyron/veyron/profiles"
 	"veyron.io/veyron/veyron/services/mgmt/logreader/impl"
 
+	"veyron.io/veyron/veyron2/ipc"
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/rt"
 	"veyron.io/veyron/veyron2/security"
@@ -16,6 +18,30 @@
 	"veyron.io/veyron/veyron2/verror"
 )
 
+func startServer(t *testing.T, disp ipc.Dispatcher) (ipc.Server, string, error) {
+	server, err := rt.R().NewServer()
+	if err != nil {
+		t.Fatalf("NewServer failed: %v", err)
+		return nil, "", err
+	}
+	endpoint, err := server.Listen(profiles.LocalListenSpec)
+	if err != nil {
+		t.Fatalf("Listen failed: %v", err)
+		return nil, "", err
+	}
+	if err := server.ServeDispatcher("", disp); err != nil {
+		t.Fatalf("Serve failed: %v", err)
+		return nil, "", err
+	}
+	return server, endpoint.String(), nil
+}
+
+func stopServer(t *testing.T, server ipc.Server) {
+	if err := server.Stop(); err != nil {
+		t.Errorf("server.Stop failed: %v", err)
+	}
+}
+
 type logFileDispatcher struct {
 	root string
 }
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index f048999..7a00025 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -123,14 +123,11 @@
 	"veyron.io/veyron/veyron2/security"
 	"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/verror"
 	"veyron.io/veyron/veyron2/vlog"
 
 	vexec "veyron.io/veyron/veyron/lib/exec"
 	"veyron.io/veyron/veyron/lib/flags/consts"
-	"veyron.io/veyron/veyron/lib/glob"
 	vsecurity "veyron.io/veyron/veyron/security"
 	iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
 )
@@ -979,7 +976,6 @@
 
 type treeNode struct {
 	children map[string]*treeNode
-	remote   string // Used to implement Glob for remote objects.
 }
 
 func newTreeNode() *treeNode {
@@ -1007,20 +1003,13 @@
 	}
 }
 
-// scanConfigDir scans the config directory to build tree representation of all
-// the valid object names.
-func (i *appInvoker) scanConfigDir() *treeNode {
-	tree := newTreeNode()
-
-	// appIDMap[appID]title
-	appIDMap := make(map[string]string)
-
-	// Find all envelopes, extract appID and installID.
-	envGlob := []string{i.config.Root, "app-*", "installation-*", "*", "envelope"}
+func (i *appInvoker) scanEnvelopes(tree *treeNode, appDir string) {
+	// Find all envelopes, extract installID.
+	envGlob := []string{i.config.Root, appDir, "installation-*", "*", "envelope"}
 	envelopes, err := filepath.Glob(filepath.Join(envGlob...))
 	if err != nil {
 		vlog.Errorf("unexpected error: %v", err)
-		return nil
+		return
 	}
 	for _, path := range envelopes {
 		env, err := loadEnvelope(filepath.Dir(path))
@@ -1033,114 +1022,85 @@
 			vlog.Errorf("unexpected number of path components: %q (%q)", elems, path)
 			continue
 		}
-		appID := strings.TrimPrefix(elems[0], "app-")
 		installID := strings.TrimPrefix(elems[1], "installation-")
-		appIDMap[appID] = env.Title
 		tree.find([]string{env.Title, installID}, true)
 	}
+	return
+}
 
+func (i *appInvoker) scanInstances(tree *treeNode) {
+	if len(i.suffix) < 2 {
+		return
+	}
+	title := i.suffix[0]
+	installDir, err := installationDirCore(i.suffix[:2], i.config.Root)
+	if err != nil {
+		return
+	}
 	// Find all instances.
-	infoGlob := []string{i.config.Root, "app-*", "installation-*", "instances", "instance-*", "info"}
+	infoGlob := []string{installDir, "instances", "instance-*", "info"}
 	instances, err := filepath.Glob(filepath.Join(infoGlob...))
 	if err != nil {
 		vlog.Errorf("unexpected error: %v", err)
-		return nil
+		return
 	}
 	for _, path := range instances {
 		instanceDir := filepath.Dir(path)
-		info, err := loadInstanceInfo(instanceDir)
+		i.scanInstance(tree, title, instanceDir)
+	}
+	return
+}
+
+func (i *appInvoker) scanInstance(tree *treeNode, title, instanceDir string) {
+	if _, err := loadInstanceInfo(instanceDir); err != nil {
+		return
+	}
+	relpath, _ := filepath.Rel(i.config.Root, instanceDir)
+	elems := strings.Split(relpath, string(filepath.Separator))
+	if len(elems) < 4 {
+		vlog.Errorf("unexpected number of path components: %q (%q)", elems, instanceDir)
+		return
+	}
+	installID := strings.TrimPrefix(elems[1], "installation-")
+	instanceID := strings.TrimPrefix(elems[3], "instance-")
+	tree.find([]string{title, installID, instanceID, "logs"}, true)
+	if instanceStateIs(instanceDir, started) {
+		for _, obj := range []string{"pprof", "stats"} {
+			tree.find([]string{title, installID, instanceID, obj}, true)
+		}
+	}
+}
+
+func (i *appInvoker) VGlobChildren() ([]string, error) {
+	tree := newTreeNode()
+	switch len(i.suffix) {
+	case 0:
+		i.scanEnvelopes(tree, "app-*")
+	case 1:
+		appDir := applicationDirName(i.suffix[0])
+		i.scanEnvelopes(tree, appDir)
+	case 2:
+		i.scanInstances(tree)
+	case 3:
+		dir, err := i.instanceDir()
 		if err != nil {
-			continue
+			break
 		}
-		relpath, _ := filepath.Rel(i.config.Root, path)
-		elems := strings.Split(relpath, string(filepath.Separator))
-		if len(elems) != len(infoGlob)-1 {
-			vlog.Errorf("unexpected number of path components: %q (%q)", elems, path)
-			continue
-		}
-		appID := strings.TrimPrefix(elems[0], "app-")
-		installID := strings.TrimPrefix(elems[1], "installation-")
-		instanceID := strings.TrimPrefix(elems[3], "instance-")
-		if title, ok := appIDMap[appID]; ok {
-			n := tree.find([]string{title, installID, instanceID, "logs"}, true)
-			i.addLogFiles(n, filepath.Join(instanceDir, "logs"))
-
-			if instanceStateIs(instanceDir, started) {
-				// Set the name of the remote objects that should handle Glob.
-				for _, obj := range []string{"pprof", "stats"} {
-					n = tree.find([]string{title, installID, instanceID, obj}, true)
-					n.remote = naming.JoinAddressName(info.AppCycleMgrName, naming.Join("__debug", obj))
-				}
-			}
-		}
+		i.scanInstance(tree, i.suffix[0], dir)
+	default:
+		return nil, errNotExist
 	}
-	return tree
-}
-
-func (i *appInvoker) addLogFiles(n *treeNode, dir string) {
-	filepath.Walk(dir, func(path string, _ os.FileInfo, _ error) error {
-		if path == dir {
-			// Skip the logs directory itself.
-			return nil
-		}
-		relpath, _ := filepath.Rel(dir, path)
-		n.find(strings.Split(relpath, string(filepath.Separator)), true)
-		return nil
-	})
-}
-
-func (i *appInvoker) Glob(ctx mounttable.GlobbableGlobContext, pattern string) error {
-	g, err := glob.Parse(pattern)
-	if err != nil {
-		return err
-	}
-	n := i.scanConfigDir().find(i.suffix, false)
+	n := tree.find(i.suffix, false)
 	if n == nil {
-		return errInvalidSuffix
+		return nil, errInvalidSuffix
 	}
-	i.globStep(ctx, "", g, n)
-	return nil
-}
-
-func (i *appInvoker) globStep(ctx mounttable.GlobbableGlobContext, prefix string, g *glob.Glob, n *treeNode) {
-	if n.remote != "" {
-		remoteGlob(ctx, n.remote, prefix, g.String())
-		return
+	children := make([]string, len(n.children))
+	index := 0
+	for child, _ := range n.children {
+		children[index] = child
+		index++
 	}
-	if g.Len() == 0 {
-		ctx.SendStream().Send(types.MountEntry{Name: prefix})
-	}
-	if g.Finished() {
-		return
-	}
-	for name, child := range n.children {
-		if ok, _, left := g.MatchInitialSegment(name); ok {
-			i.globStep(ctx, naming.Join(prefix, name), left, child)
-		}
-	}
-}
-
-func remoteGlob(ctx mounttable.GlobbableGlobContext, remote, prefix, pattern string) {
-	call, err := mounttable.GlobbableClient(remote).Glob(ctx, pattern)
-	if err != nil {
-		vlog.VI(1).Infof("%q.Glob(%q): %v", remote, pattern, err)
-		return
-	}
-	it := call.RecvStream()
-	sender := ctx.SendStream()
-	for it.Advance() {
-		me := it.Value()
-		me.Name = naming.Join(prefix, me.Name)
-		sender.Send(me)
-	}
-	if err := it.Err(); err != nil {
-		vlog.VI(1).Infof("%q.Glob(%q): %v", remote, pattern, err)
-		return
-	}
-	if err := call.Finish(); err != nil {
-		vlog.VI(1).Infof("%q.Glob(%q): %v", remote, pattern, err)
-		return
-	}
+	return children, nil
 }
 
 // TODO(rjkroege): Refactor to eliminate redundancy with newAppSpecificAuthorizer.
diff --git a/services/mgmt/node/impl/dispatcher.go b/services/mgmt/node/impl/dispatcher.go
index b29347f..679a459 100644
--- a/services/mgmt/node/impl/dispatcher.go
+++ b/services/mgmt/node/impl/dispatcher.go
@@ -352,13 +352,11 @@
 		})
 		return ipc.ReflectInvoker(receiver), d.auth, nil
 	case appsSuffix:
-		// Glob requests are handled by appInvoker, except for pprof and
-		// stats objects which handle Glob themselves.
 		// Requests to apps/*/*/*/logs are handled locally by LogFileInvoker.
 		// 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 != ipc.GlobMethod || components[4] != "logs") {
+		if len(components) >= 5 {
 			appInstanceDir, err := instanceDir(d.config.Root, components[1:4])
 			if err != nil {
 				return nil, nil, err
diff --git a/services/mgmt/node/impl/node_invoker.go b/services/mgmt/node/impl/node_invoker.go
index 0e50fdd..51a35cc 100644
--- a/services/mgmt/node/impl/node_invoker.go
+++ b/services/mgmt/node/impl/node_invoker.go
@@ -47,12 +47,9 @@
 	"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"
 	"veyron.io/veyron/veyron/lib/netstate"
 	"veyron.io/veyron/veyron/services/mgmt/node/config"
 	"veyron.io/veyron/veyron/services/mgmt/profile"
@@ -463,17 +460,6 @@
 	return i.disp.getACL()
 }
 
-func (i *nodeInvoker) Glob(ctx mounttable.GlobbableGlobContext, pattern string) error {
-	g, err := glob.Parse(pattern)
-	if err != nil {
-		return err
-	}
-	if g.Len() == 0 {
-		return ctx.SendStream().Send(types.MountEntry{Name: ""})
-	}
-	return nil
-}
-
 func sameMachineCheck(call ipc.ServerContext) error {
 	switch local, err := netstate.SameMachine(call.RemoteEndpoint().Addr()); {
 	case err != nil:
diff --git a/services/mgmt/pprof/impl/server.go b/services/mgmt/pprof/impl/server.go
index 54da509..e7f13ff 100644
--- a/services/mgmt/pprof/impl/server.go
+++ b/services/mgmt/pprof/impl/server.go
@@ -7,10 +7,7 @@
 	"runtime/pprof"
 	"time"
 
-	"veyron.io/veyron/veyron/lib/glob"
-
 	"veyron.io/veyron/veyron2/ipc"
-	"veyron.io/veyron/veyron2/services/mounttable/types"
 	"veyron.io/veyron/veyron2/verror"
 )
 
@@ -80,19 +77,6 @@
 	return results, nil
 }
 
-// Glob streams the name of all the objects that match pattern. In this case,
-// there is only the root object.
-func (pprofInvoker) Glob(call ipc.ServerCall, pattern string) error {
-	g, err := glob.Parse(pattern)
-	if err != nil {
-		return err
-	}
-	if g.Len() == 0 {
-		return call.Send(types.MountEntry{Name: ""})
-	}
-	return nil
-}
-
 type streamWriter struct {
 	call ipc.ServerCall
 }
diff --git a/services/mgmt/suidhelper/impl/args.go b/services/mgmt/suidhelper/impl/args.go
index 7ab9574..4e3f4fb 100644
--- a/services/mgmt/suidhelper/impl/args.go
+++ b/services/mgmt/suidhelper/impl/args.go
@@ -35,8 +35,8 @@
 
 var (
 	flagUsername, flagWorkspace, flagLogDir, flagRun *string
-	flagMinimumUid                                                     *int64
-	flagRemove                                                         *bool
+	flagMinimumUid                                   *int64
+	flagRemove                                       *bool
 )
 
 func init() {
diff --git a/services/mgmt/suidhelper/impl/flag/flag.go b/services/mgmt/suidhelper/impl/flag/flag.go
index 6a4cdb3..25782ab 100644
--- a/services/mgmt/suidhelper/impl/flag/flag.go
+++ b/services/mgmt/suidhelper/impl/flag/flag.go
@@ -14,8 +14,8 @@
 
 var (
 	Username, Workspace, LogDir, Run *string
-	MinimumUid                                     *int64
-	Remove                                         *bool
+	MinimumUid                       *int64
+	Remove                           *bool
 )
 
 func init() {