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/dispatcher.go b/services/mgmt/debug/dispatcher.go
new file mode 100644
index 0000000..43e7b41
--- /dev/null
+++ b/services/mgmt/debug/dispatcher.go
@@ -0,0 +1,89 @@
+package debug
+
+import (
+	"strings"
+	"time"
+
+	"veyron/lib/glob"
+	logreaderimpl "veyron/services/mgmt/logreader/impl"
+	statsimpl "veyron/services/mgmt/stats/impl"
+
+	"veyron2"
+	"veyron2/ipc"
+	"veyron2/naming"
+	"veyron2/security"
+	"veyron2/services/mounttable/types"
+	"veyron2/vlog"
+)
+
+// dispatcher holds the state of the debug dispatcher.
+type dispatcher struct {
+	rt      veyron2.Runtime
+	logsDir string // The root of the logs directory.
+	auth    security.Authorizer
+}
+
+func NewDispatcher(rt veyron2.Runtime, logsDir string, authorizer security.Authorizer) *dispatcher {
+	return &dispatcher{rt, logsDir, authorizer}
+}
+
+func (d *dispatcher) Lookup(suffix, method string) (ipc.Invoker, security.Authorizer, error) {
+	if len(suffix) == 0 {
+		leaves := []string{"logs", "stats"}
+		return ipc.ReflectInvoker(&topLevelGlobInvoker{d.rt, leaves}), d.auth, nil
+	}
+	parts := strings.SplitN(suffix, "/", 2)
+	if len(parts) == 2 {
+		suffix = parts[1]
+	} else {
+		suffix = ""
+	}
+	switch parts[0] {
+	case "logs":
+		if method == "Glob" {
+			return logreaderimpl.NewLogDirectoryInvoker(d.logsDir, suffix), d.auth, nil
+		}
+		return logreaderimpl.NewLogFileInvoker(d.logsDir, suffix), d.auth, nil
+	case "stats":
+		return statsimpl.NewStatsInvoker(suffix, 10*time.Second), d.auth, nil
+	}
+	return nil, d.auth, nil
+}
+
+type topLevelGlobInvoker struct {
+	rt     veyron2.Runtime
+	leaves []string
+}
+
+func (i *topLevelGlobInvoker) Glob(call ipc.ServerCall, pattern string) error {
+	vlog.VI(1).Infof("Glob(%v)", pattern)
+	g, err := glob.Parse(pattern)
+	if err != nil {
+		return err
+	}
+	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 *topLevelGlobInvoker) leafGlob(call ipc.ServerCall, leaf string, pattern string) error {
+	addr := naming.JoinAddressName(call.LocalEndpoint().String(), leaf)
+	pattern = naming.Join(addr, pattern)
+	c, err := i.rt.Namespace().Glob(call, pattern)
+	if err != nil {
+		vlog.VI(1).Infof("Glob(%v) failed: %v", pattern, err)
+		return err
+	}
+	for me := range c {
+		_, name := naming.SplitAddressName(me.Name)
+		if err := call.Send(types.MountEntry{Name: name}); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/services/mgmt/debug/server.go b/services/mgmt/debug/server.go
new file mode 100644
index 0000000..6dd2740
--- /dev/null
+++ b/services/mgmt/debug/server.go
@@ -0,0 +1,33 @@
+// Package debug implements a server that exports debugging information from
+// various sources: log files, stats, etc.
+package debug
+
+import (
+	"fmt"
+
+	"veyron2"
+	"veyron2/security"
+	"veyron2/vlog"
+)
+
+// StartDebugServer starts a debug server.
+func StartDebugServer(rt veyron2.Runtime, address, logsDir string, auth security.Authorizer) (string, func(), error) {
+	if len(logsDir) == 0 {
+		return "", nil, fmt.Errorf("logs directory missing")
+	}
+	disp := NewDispatcher(rt, logsDir, auth)
+	server, err := rt.NewServer()
+	if err != nil {
+		return "", nil, fmt.Errorf("failed to start debug server: %v", err)
+	}
+	endpoint, err := server.Listen("tcp", address)
+	if err != nil {
+		return "", nil, fmt.Errorf("failed to listen on %v: %v", address, err)
+	}
+	if err := server.Serve("", 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/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)
+		}
+	}
+}
diff --git a/services/mgmt/logreader/impl/common.go b/services/mgmt/logreader/impl/common.go
index ad59aae..d80d738 100644
--- a/services/mgmt/logreader/impl/common.go
+++ b/services/mgmt/logreader/impl/common.go
@@ -5,7 +5,7 @@
 package impl
 
 import (
-	"path"
+	"path/filepath"
 	"strings"
 
 	"veyron2/services/mgmt/logreader/types"
@@ -22,10 +22,11 @@
 // translateNameToFilename returns the file name that corresponds to the object
 // name.
 func translateNameToFilename(root, name string) (string, error) {
-	p := path.Join(root, name)
+	name = filepath.Join(strings.Split(name, "/")...)
+	p := filepath.Join(root, name)
 	// Make sure we're not asked to read a file outside of the root
 	// directory. This could happen if suffix contains "../", which get
-	// collapsed by path.Join().
+	// collapsed by filepath.Join().
 	if !strings.HasPrefix(p, root) {
 		return "", errOperationFailed
 	}
diff --git a/services/mgmt/logreader/impl/logdir_invoker.go b/services/mgmt/logreader/impl/logdir_invoker.go
index be4a741..e7c2c69 100644
--- a/services/mgmt/logreader/impl/logdir_invoker.go
+++ b/services/mgmt/logreader/impl/logdir_invoker.go
@@ -3,6 +3,7 @@
 import (
 	"os"
 	"path"
+	"path/filepath"
 
 	"veyron/lib/glob"
 
@@ -22,7 +23,7 @@
 
 // NewLogDirectoryInvoker is the invoker factory.
 func NewLogDirectoryInvoker(root, suffix string) ipc.Invoker {
-	return ipc.ReflectInvoker(&logDirectoryInvoker{path.Clean(root), suffix})
+	return ipc.ReflectInvoker(&logDirectoryInvoker{filepath.Clean(root), suffix})
 }
 
 // Glob streams the name of all the objects that match pattern.
@@ -32,8 +33,18 @@
 	if err != nil {
 		return err
 	}
-	i.root = path.Join(i.root, i.suffix)
-	return i.globStep("", g, true, call)
+	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.
diff --git a/services/mgmt/logreader/impl/logdir_invoker_test.go b/services/mgmt/logreader/impl/logdir_invoker_test.go
index f47e546..efde612 100644
--- a/services/mgmt/logreader/impl/logdir_invoker_test.go
+++ b/services/mgmt/logreader/impl/logdir_invoker_test.go
@@ -56,124 +56,182 @@
 	}
 
 	// Try to access a directory that doesn't exist.
-	ld, err := mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//doesntexist"))
-	if err != nil {
-		t.Errorf("BindLogDirectory: %v", err)
-	}
-	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.NotFound; !verror.Is(err, expected) {
-			t.Errorf("unexpected error value, got %v, want: %v", err, expected)
+	{
+		ld, err := mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//doesntexist"))
+		if err != nil {
+			t.Errorf("BindLogDirectory: %v", err)
+		}
+		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.NotFound; !verror.Is(err, expected) {
+				t.Errorf("unexpected error value, got %v, want: %v", err, expected)
+			}
 		}
 	}
 
 	// Try to access a directory that does exist.
-	ld, err = mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//"))
-	if err != nil {
-		t.Errorf("BindLogDirectory: %v", err)
+	{
+		ld, err := mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//"))
+		if err != nil {
+			t.Errorf("BindLogDirectory: %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(tests)
+		sort.Strings(results)
+		if !reflect.DeepEqual(tests, results) {
+			t.Errorf("unexpected result. Got %v, want %v", results, tests)
+		}
+
+		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",
+		}
+		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)
+		}
 	}
 
-	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(tests)
-	sort.Strings(results)
-	if !reflect.DeepEqual(tests, results) {
-		t.Errorf("unexpected result. Got %v, want %v", results, tests)
+	// Try to access a sub-directory.
+	{
+		ld, err := mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//subdir"))
+		if err != nil {
+			t.Errorf("BindLogDirectory: %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{
+			"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)
+		}
 	}
 
-	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, err := mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//foo.txt"))
+		if err != nil {
+			t.Errorf("BindLogDirectory: %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)
+		}
 
-	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",
-	}
-	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)
+		}
 
-	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)
+		}
 
-	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)
-	}
-
-	ld, err = mounttable.BindGlobbable(naming.JoinAddressName(endpoint, "//subdir"))
-	if err != nil {
-		t.Errorf("BindLogDirectory: %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{
-		"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)
+		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 951458d..2763aef 100644
--- a/services/mgmt/logreader/impl/logfile_invoker.go
+++ b/services/mgmt/logreader/impl/logfile_invoker.go
@@ -4,7 +4,7 @@
 	"io"
 	"math"
 	"os"
-	"path"
+	"path/filepath"
 
 	"veyron2/ipc"
 	"veyron2/services/mgmt/logreader/types"
@@ -22,7 +22,7 @@
 
 // NewLogFileInvoker is the invoker factory.
 func NewLogFileInvoker(root, suffix string) ipc.Invoker {
-	return ipc.ReflectInvoker(&logFileInvoker{path.Clean(root), suffix})
+	return ipc.ReflectInvoker(&logFileInvoker{filepath.Clean(root), suffix})
 }
 
 // Size returns the size of the log file, in bytes.