veyron/services/mgmt/debug: Add debug server
This change adds a debug server that uses the logreader and stats
implementations to allow remote access to logs and stats. It is not yet
hooked up to the runtime. We still need to figure out the address and
authorizer to use.
Change-Id: Ic662f67cdd6da1c222649c8fa42f5ebbe331044b
diff --git a/services/mgmt/debug/server_test.go b/services/mgmt/debug/server_test.go
new file mode 100644
index 0000000..207b3d9
--- /dev/null
+++ b/services/mgmt/debug/server_test.go
@@ -0,0 +1,211 @@
+package debug_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ libstats "veyron/lib/stats"
+ "veyron/services/mgmt/debug"
+
+ "veyron2/naming"
+ "veyron2/rt"
+ "veyron2/services/mgmt/logreader"
+ "veyron2/services/mgmt/stats"
+ "veyron2/services/mounttable"
+ "veyron2/verror"
+)
+
+func TestDebugServer(t *testing.T) {
+ runtime := rt.Init()
+
+ workdir, err := ioutil.TempDir("", "logreadertest")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir: %v", err)
+ }
+ defer os.RemoveAll(workdir)
+ if err = ioutil.WriteFile(filepath.Join(workdir, "test.INFO"), []byte("test"), os.FileMode(0644)); err != nil {
+ t.Fatalf("ioutil.WriteFile failed: %v", err)
+ }
+
+ endpoint, stop, err := debug.StartDebugServer(runtime, "localhost:0", workdir, nil)
+ if err != nil {
+ t.Fatalf("StartDebugServer failed: %v", err)
+ }
+ defer stop()
+
+ // Access a logs directory that exists.
+ {
+ ld, err := mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//logs"))
+ if err != nil {
+ t.Errorf("BindGlobbable: %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)
+ }
+ if len(results) != 1 || results[0] != "test.INFO" {
+ t.Errorf("unexpected result. Got %v, want 'test.INFO'", results)
+ }
+ 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)
+ }
+ }
+
+ // Access a logs directory that doesn't exist.
+ {
+ ld, err := mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//logs/nowheretobefound"))
+ if err != nil {
+ t.Errorf("BindGlobbable: %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)
+ }
+ if len(results) != 0 {
+ t.Errorf("unexpected result. Got %v, want ''", results)
+ }
+ if expected, got := verror.NotFound, stream.Finish(); !verror.Is(got, expected) {
+ t.Errorf("unexpected error value, got %v, want: %v", got, expected)
+ }
+ }
+
+ // Access a log file that exists.
+ {
+ lf, err := logreader.BindLogFile(naming.JoinAddressName(endpoint, "//logs/test.INFO"))
+ if err != nil {
+ t.Errorf("BindLogFile: %v", err)
+ }
+ size, err := lf.Size(runtime.NewContext())
+ if err != nil {
+ t.Errorf("Size failed: %v", err)
+ }
+ if expected := int64(len("test")); size != expected {
+ t.Errorf("unexpected result. Got %v, want %v", size, expected)
+ }
+ }
+
+ // Access a log file that doesn't exist.
+ {
+ lf, err := logreader.BindLogFile(naming.JoinAddressName(endpoint, "//logs/nosuchfile.INFO"))
+ if err != nil {
+ t.Errorf("BindLogFile: %v", err)
+ }
+ _, err = lf.Size(runtime.NewContext())
+ if expected := verror.NotFound; !verror.Is(err, expected) {
+ t.Errorf("unexpected error value, got %v, want: %v", err, expected)
+ }
+ }
+
+ // Access a stats object that exists.
+ {
+ foo := libstats.NewInteger("testing/foo")
+ foo.Set(123)
+
+ st, err := stats.BindStats(naming.JoinAddressName(endpoint, "//stats/testing/foo"))
+ if err != nil {
+ t.Errorf("BindStats: %v", err)
+ }
+ v, err := st.Value(runtime.NewContext())
+ if err != nil {
+ t.Errorf("Value failed: %v", err)
+ }
+ if expected := int64(123); v != expected {
+ t.Errorf("unexpected result. Got %v, want %v", v, expected)
+ }
+ }
+
+ // Access a stats object that doesn't exists.
+ {
+ st, err := stats.BindStats(naming.JoinAddressName(endpoint, "//stats/testing/nobodyhome"))
+ if err != nil {
+ t.Errorf("BindStats: %v", err)
+ }
+ _, err = st.Value(runtime.NewContext())
+ if expected := verror.NotFound; !verror.Is(err, expected) {
+ t.Errorf("unexpected error value, got %v, want: %v", err, expected)
+ }
+ }
+
+ // Glob from the root.
+ {
+ ns := rt.R().Namespace()
+ ns.SetRoots(naming.JoinAddressName(endpoint, "//"))
+ ctx, cancel := rt.R().NewContext().WithTimeout(10 * time.Second)
+ defer cancel()
+ c, err := ns.Glob(ctx, "logs/...")
+ if err != nil {
+ t.Errorf("ns.Glob failed: %v", err)
+ }
+ results := []string{}
+ for res := range c {
+ results = append(results, res.Name)
+ }
+ sort.Strings(results)
+ expected := []string{
+ "logs",
+ "logs/test.INFO",
+ }
+ if !reflect.DeepEqual(expected, results) {
+ t.Errorf("unexpected result. Got %v, want %v", results, expected)
+ }
+
+ c, err = ns.Glob(ctx, "stats/testing/*")
+ if err != nil {
+ t.Errorf("ns.Glob failed: %v", err)
+ }
+ results = []string{}
+ for res := range c {
+ results = append(results, res.Name)
+ }
+ sort.Strings(results)
+ expected = []string{
+ "stats/testing/foo",
+ }
+ if !reflect.DeepEqual(expected, results) {
+ t.Errorf("unexpected result. Got %v, want %v", results, expected)
+ }
+
+ c, err = ns.Glob(ctx, "...")
+ if err != nil {
+ t.Errorf("ns.Glob failed: %v", err)
+ }
+ results = []string{}
+ for res := range c {
+ if strings.HasPrefix(res.Name, "stats/") && !strings.HasPrefix(res.Name, "stats/testing/") {
+ // Skip any non-testing stats.
+ continue
+ }
+ results = append(results, res.Name)
+ }
+ sort.Strings(results)
+ expected = []string{
+ "",
+ "logs",
+ "logs/test.INFO",
+ "stats",
+ "stats/testing/foo",
+ }
+ if !reflect.DeepEqual(expected, results) {
+ t.Errorf("unexpected result. Got %v, want %v", results, expected)
+ }
+ }
+}