veyron/runtimes/google/ipc: Add __debug dispatcher

With this change, requests for object names that begin with a special
keyword ("__debug") are intercepted and redirected to the debug
dispatcher.

If server s is mounted at a/b, a request to a/b/__debug will be routed
to the debug dispatcher. The __debug keyword is only recognized when it
is at the beginning of the object name on a given server.

Change-Id: Id333adc8cc5483d2605b70949235cd00ee3465e5
diff --git a/runtimes/google/ipc/debug_test.go b/runtimes/google/ipc/debug_test.go
new file mode 100644
index 0000000..737f6ac
--- /dev/null
+++ b/runtimes/google/ipc/debug_test.go
@@ -0,0 +1,106 @@
+package ipc
+
+import (
+	"reflect"
+	"sort"
+	"testing"
+
+	"veyron.io/veyron/veyron/lib/stats"
+	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/manager"
+	tnaming "veyron.io/veyron/veyron/runtimes/google/testing/mocks/naming"
+
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/naming"
+	"veyron.io/veyron/veyron2/services/mounttable/types"
+)
+
+func TestDebugServer(t *testing.T) {
+	sm := manager.InternalNew(naming.FixedRoutingID(0x555555555))
+	defer sm.Shutdown()
+	ns := tnaming.NewSimpleNamespace()
+	server, err := InternalNewServer(testContext(), sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewServer failed: %v", err)
+	}
+	defer server.Stop()
+	server.Serve("", ipc.LeafDispatcher(&testObject{}, nil))
+	ep, err := server.Listen("tcp4", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("server.Listen failed: %v", err)
+	}
+	client, err := InternalNewClient(sm, ns)
+	if err != nil {
+		t.Fatalf("InternalNewClient failed: %v", err)
+	}
+	defer client.Close()
+	ctx := testContext()
+	// Call the Foo method on ""
+	{
+		addr := naming.JoinAddressName(ep.String(), "//")
+		call, err := client.StartCall(ctx, addr, "Foo", nil)
+		if err != nil {
+			t.Fatalf("client.StartCall failed: %v", err)
+		}
+		var value string
+		if ferr := call.Finish(&value); ferr != nil {
+			t.Fatalf("call.Finish failed: %v", ferr)
+		}
+		if want := "BAR"; value != want {
+			t.Errorf("unexpected value: Got %v, want %v", value, want)
+		}
+	}
+	// Call Glob on __debug
+	{
+		addr := naming.JoinAddressName(ep.String(), "//__debug")
+		call, err := client.StartCall(ctx, addr, "Glob", []interface{}{"*"})
+		if err != nil {
+			t.Fatalf("client.StartCall failed: %v", err)
+		}
+		results := []string{}
+		for {
+			var me types.MountEntry
+			if err := call.Recv(&me); err != nil {
+				break
+			}
+			results = append(results, me.Name)
+		}
+		if ferr := call.Finish(&err); ferr != nil {
+			t.Fatalf("call.Finish failed: %v", ferr)
+		}
+		sort.Strings(results)
+		want := []string{
+			"logs",
+			"stats",
+		}
+		if !reflect.DeepEqual(want, results) {
+			t.Errorf("unexpected results. Got %v, want %v", results, want)
+		}
+	}
+	// Call Value on __debug/stats/testing/foo
+	{
+		foo := stats.NewString("testing/foo")
+		foo.Set("The quick brown fox jumps over the lazy dog")
+		addr := naming.JoinAddressName(ep.String(), "//__debug/stats/testing/foo")
+		call, err := client.StartCall(ctx, addr, "Value", nil)
+		if err != nil {
+			t.Fatalf("client.StartCall failed: %v", err)
+		}
+		var value string
+		if ferr := call.Finish(&value, &err); ferr != nil {
+			t.Fatalf("call.Finish failed: %v", ferr)
+		}
+		if err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		if want := foo.Value(); value != want {
+			t.Errorf("unexpected result: Got %v, want %v", value, want)
+		}
+	}
+}
+
+type testObject struct {
+}
+
+func (o testObject) Foo(ipc.ServerCall) string {
+	return "BAR"
+}
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 10862d1..d699ac7 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -15,6 +15,7 @@
 	isecurity "veyron.io/veyron/veyron/runtimes/google/security"
 	ivtrace "veyron.io/veyron/veyron/runtimes/google/vtrace"
 	vsecurity "veyron.io/veyron/veyron/security"
+	"veyron.io/veyron/veyron/services/mgmt/debug"
 
 	"veyron.io/veyron/veyron/profiles/internal"
 
@@ -53,6 +54,7 @@
 	ns               naming.Namespace
 	servesMountTable bool
 	debugAuthorizer  security.Authorizer
+	debugDisp        ipc.Dispatcher
 	// TODO(cnicolaou): add roaming stats to ipcStats
 	stats *ipcStats // stats for this server.
 }
@@ -87,6 +89,7 @@
 			s.debugAuthorizer = security.Authorizer(opt)
 		}
 	}
+	s.debugDisp = debug.NewDispatcher(vlog.Log.LogDir(), s.debugAuthorizer)
 	return s, nil
 }
 
@@ -540,11 +543,12 @@
 // flow that's already connected to the client.
 type flowServer struct {
 	context.T
-	server *server        // ipc.Server that this flow server belongs to
-	disp   ipc.Dispatcher // ipc.Dispatcher that will serve RPCs on this flow
-	dec    *vom.Decoder   // to decode requests and args from the client
-	enc    *vom.Encoder   // to encode responses and results to the client
-	flow   stream.Flow    // underlying flow
+	server    *server        // ipc.Server that this flow server belongs to
+	disp      ipc.Dispatcher // ipc.Dispatcher that will serve RPCs on this flow
+	dec       *vom.Decoder   // to decode requests and args from the client
+	enc       *vom.Encoder   // to encode responses and results to the client
+	flow      stream.Flow    // underlying flow
+	debugDisp ipc.Dispatcher // internal debug dispatcher
 	// Fields filled in during the server invocation.
 
 	// authorizedRemoteID is the PublicID obtained after authorizing the remoteID
@@ -573,6 +577,7 @@
 		dec:        vom.NewDecoder(flow),
 		enc:        vom.NewEncoder(flow),
 		flow:       flow,
+		debugDisp:  server.debugDisp,
 		discharges: make(map[string]security.Discharge),
 	}
 }
@@ -772,13 +777,20 @@
 }
 
 // lookup returns the invoker and authorizer responsible for serving the given
-// name and method.  The name is stripped of any leading slashes, and the
-// invoker is looked up in the server's dispatcher.  The (stripped) name
+// 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 fs.disp != nil {
-		invoker, auth, err := fs.disp.Lookup(name, method)
+	disp := fs.disp
+	if name == ipc.DebugKeyword || strings.HasPrefix(name, ipc.DebugKeyword+"/") {
+		name = strings.TrimPrefix(name, ipc.DebugKeyword)
+		name = strings.TrimLeft(name, "/")
+		disp = fs.debugDisp
+	}
+	if disp != nil {
+		invoker, auth, err := disp.Lookup(name, method)
 		switch {
 		case err != nil:
 			return nil, nil, "", verror.Convert(err)