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)