veyron/services/mgmt/binary: Add object names to namespace

This changes makes the name of the objects in the repository
discoverable via the namespace.

Change-Id: I0354e6f7935ff5da1bd93dd0dfe9313d2977cc29
diff --git a/services/mgmt/binary/impl/fs_utils.go b/services/mgmt/binary/impl/fs_utils.go
index 8cca7b6..431f434 100644
--- a/services/mgmt/binary/impl/fs_utils.go
+++ b/services/mgmt/binary/impl/fs_utils.go
@@ -6,6 +6,7 @@
 	"os"
 	"path/filepath"
 	"strconv"
+	"strings"
 
 	"veyron.io/veyron/veyron2/vlog"
 )
@@ -58,7 +59,13 @@
 		vlog.Errorf("ReadDir(%v) failed: %v", path, err)
 		return []string{}, errOperationFailed
 	}
-	result := make([]string, len(infos))
+	nDirs := 0
+	for _, info := range infos {
+		if info.IsDir() {
+			nDirs++
+		}
+	}
+	result := make([]string, nDirs)
 	for _, info := range infos {
 		if info.IsDir() {
 			partName := info.Name()
@@ -72,9 +79,65 @@
 			}
 			result[idx] = filepath.Join(path, partName)
 		} else {
+			if info.Name() == "name" {
+				continue
+			}
 			// The only entries should correspond to the part dirs.
 			return []string{}, errOperationFailed
 		}
 	}
 	return result, nil
 }
+
+// createObjectNameTree returns a tree of all the valid object names in the
+// repository.
+func (i *binaryService) createObjectNameTree() *treeNode {
+	pattern := i.state.root
+	for d := 0; d < i.state.depth; d++ {
+		pattern = filepath.Join(pattern, "*")
+	}
+	pattern = filepath.Join(pattern, "*", "name")
+	matches, err := filepath.Glob(pattern)
+	if err != nil {
+		return nil
+	}
+	tree := newTreeNode()
+	for _, m := range matches {
+		name, err := ioutil.ReadFile(m)
+		if err != nil {
+			continue
+		}
+		elems := strings.Split(string(name), string(filepath.Separator))
+		tree.find(elems, true)
+	}
+	return tree
+}
+
+type treeNode struct {
+	children map[string]*treeNode
+}
+
+func newTreeNode() *treeNode {
+	return &treeNode{children: make(map[string]*treeNode)}
+}
+
+func (n *treeNode) find(names []string, create bool) *treeNode {
+	for {
+		if len(names) == 0 {
+			return n
+		}
+		if next, ok := n.children[names[0]]; ok {
+			n = next
+			names = names[1:]
+			continue
+		}
+		if create {
+			nn := newTreeNode()
+			n.children[names[0]] = nn
+			n = nn
+			names = names[1:]
+			continue
+		}
+		return nil
+	}
+}
diff --git a/services/mgmt/binary/impl/http_test.go b/services/mgmt/binary/impl/http_test.go
index 5b31d0f..123540e 100644
--- a/services/mgmt/binary/impl/http_test.go
+++ b/services/mgmt/binary/impl/http_test.go
@@ -18,7 +18,7 @@
 	// TODO(caprita): This is based on TestMultiPart (impl_test.go).  Share
 	// the code where possible.
 	for length := 2; length < 5; length++ {
-		binary, url, cleanup := startServer(t, 2)
+		binary, _, url, cleanup := startServer(t, 2)
 		defer cleanup()
 		// Create <length> chunks of up to 4MB of random bytes.
 		data := make([][]byte, length)
diff --git a/services/mgmt/binary/impl/impl_test.go b/services/mgmt/binary/impl/impl_test.go
index a835440..75bb0e0 100644
--- a/services/mgmt/binary/impl/impl_test.go
+++ b/services/mgmt/binary/impl/impl_test.go
@@ -10,6 +10,7 @@
 	"net/http"
 	"os"
 	"path/filepath"
+	"reflect"
 	"testing"
 
 	"veyron.io/veyron/veyron2/naming"
@@ -95,7 +96,7 @@
 }
 
 // startServer starts the binary repository server.
-func startServer(t *testing.T, depth int) (repository.BinaryClientMethods, string, func()) {
+func startServer(t *testing.T, depth int) (repository.BinaryClientMethods, string, string, func()) {
 	// Setup the root of the binary repository.
 	root, err := ioutil.TempDir("", veyronPrefix)
 	if err != nil {
@@ -134,7 +135,7 @@
 	}
 	name := naming.JoinAddressName(endpoint.String(), "test")
 	binary := repository.BinaryClient(name)
-	return binary, fmt.Sprintf("http://%s/test", listener.Addr()), func() {
+	return binary, endpoint.String(), fmt.Sprintf("http://%s/test", listener.Addr()), func() {
 		// Shutdown the binary repository server.
 		if err := server.Stop(); err != nil {
 			t.Fatalf("Stop() failed: %v", err)
@@ -155,7 +156,7 @@
 // hierarchy that stores binary objects in the local file system.
 func TestHierarchy(t *testing.T) {
 	for i := 0; i < md5.Size; i++ {
-		binary, _, cleanup := startServer(t, i)
+		binary, ep, _, cleanup := startServer(t, i)
 		defer cleanup()
 		// Create up to 4MB of random bytes.
 		size := testutil.Rand.Intn(1000 * bufferLength)
@@ -187,6 +188,13 @@
 		if bytes.Compare(output, data) != 0 {
 			t.Fatalf("Unexpected output: expected %v, got %v", data, output)
 		}
+		results, err := testutil.GlobName(naming.JoinAddressName(ep, ""), "...")
+		if err != nil {
+			t.Fatalf("GlobName failed: %v", err)
+		}
+		if expected := []string{"", "test"}; !reflect.DeepEqual(results, expected) {
+			t.Errorf("Unexpected results: expected %q, got %q", expected, results)
+		}
 		if err := binary.Delete(rt.R().NewContext()); err != nil {
 			t.Fatalf("Delete() failed: %v", err)
 		}
@@ -198,7 +206,7 @@
 // consists of.
 func TestMultiPart(t *testing.T) {
 	for length := 2; length < 5; length++ {
-		binary, _, cleanup := startServer(t, 2)
+		binary, _, _, cleanup := startServer(t, 2)
 		defer cleanup()
 		// Create <length> chunks of up to 4MB of random bytes.
 		data := make([][]byte, length)
@@ -248,7 +256,7 @@
 // of.
 func TestResumption(t *testing.T) {
 	for length := 2; length < 5; length++ {
-		binary, _, cleanup := startServer(t, 2)
+		binary, _, _, cleanup := startServer(t, 2)
 		defer cleanup()
 		// Create <length> chunks of up to 4MB of random bytes.
 		data := make([][]byte, length)
@@ -290,7 +298,7 @@
 
 // TestErrors checks that the binary interface correctly reports errors.
 func TestErrors(t *testing.T) {
-	binary, _, cleanup := startServer(t, 2)
+	binary, _, _, cleanup := startServer(t, 2)
 	defer cleanup()
 	const length = 2
 	data := make([][]byte, length)
@@ -351,3 +359,32 @@
 		t.Fatalf("Unexpected error: %v, expected error id %v", err, want)
 	}
 }
+
+func TestGlob(t *testing.T) {
+	_, ep, _, cleanup := startServer(t, 2)
+	defer cleanup()
+	// Create up to 4MB of random bytes.
+	size := testutil.Rand.Intn(1000 * bufferLength)
+	data := testutil.RandomBytes(size)
+
+	objects := []string{"foo", "bar", "hello world", "a/b/c"}
+	for _, obj := range objects {
+		name := naming.JoinAddressName(ep, obj)
+		binary := repository.BinaryClient(name)
+
+		if err := binary.Create(rt.R().NewContext(), 1); err != nil {
+			t.Fatalf("Create() failed: %v", err)
+		}
+		if streamErr, err := invokeUpload(t, binary, data, 0); streamErr != nil || err != nil {
+			t.FailNow()
+		}
+	}
+	results, err := testutil.GlobName(naming.JoinAddressName(ep, ""), "...")
+	if err != nil {
+		t.Fatalf("GlobName failed: %v", err)
+	}
+	expected := []string{"", "a", "a/b", "a/b/c", "bar", "foo", "hello world"}
+	if !reflect.DeepEqual(results, expected) {
+		t.Errorf("Unexpected results: expected %q, got %q", expected, results)
+	}
+}
diff --git a/services/mgmt/binary/impl/service.go b/services/mgmt/binary/impl/service.go
index 039e13f..8a01122 100644
--- a/services/mgmt/binary/impl/service.go
+++ b/services/mgmt/binary/impl/service.go
@@ -5,8 +5,9 @@
 // local filesystem: /<root>/<dir_1>/.../<dir_n>/<hash>. The root and
 // the directory depth are parameters of the implementation. The
 // contents of the directory include the checksum and data for each of
-// the individual parts of the binary:
+// the individual parts of the binary, and the name of the object:
 //
+// name
 // <part_1>/checksum
 // <part_1>/data
 // ...
@@ -25,6 +26,7 @@
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strings"
 	"syscall"
 
 	"veyron.io/veyron/veyron2/ipc"
@@ -89,6 +91,11 @@
 		vlog.Errorf("TempDir(%v, %v) failed: %v", parent, prefix, err)
 		return errOperationFailed
 	}
+	nameFile := filepath.Join(tmpDir, "name")
+	if err := ioutil.WriteFile(nameFile, []byte(i.suffix), os.FileMode(0600)); err != nil {
+		vlog.Errorf("WriteFile(%q) failed: %v", nameFile)
+		return errOperationFailed
+	}
 	for j := 0; j < int(nparts); j++ {
 		partPath, partPerm := generatePartPath(tmpDir, j), os.FileMode(0700)
 		if err := os.MkdirAll(partPath, partPerm); err != nil {
@@ -295,3 +302,21 @@
 	}
 	return nil
 }
+
+func (i *binaryService) VGlobChildren() ([]string, error) {
+	elems := strings.Split(i.suffix, "/")
+	if len(elems) == 1 && elems[0] == "" {
+		elems = nil
+	}
+	n := i.createObjectNameTree().find(elems, false)
+	if n == nil {
+		return nil, errOperationFailed
+	}
+	results := make([]string, len(n.children))
+	index := 0
+	for k, _ := range n.children {
+		results[index] = k
+		index++
+	}
+	return results, nil
+}