veyron/services/mounttable: Fix Glob bug.
The change fixes a bug where Glob is called on a name that is below a
mount point, e.g. "server" is mounted at "a/b" and we call
"a/b/c".Glob(*). The correct behavior is to return "" -> "server/c".
Previously, this would have returned nothing.
Change-Id: I5bbeb60488b34c9ecc2439e8270c9abe90185f5f
diff --git a/runtimes/google/naming/namespace/all_test.go b/runtimes/google/naming/namespace/all_test.go
index c29e4f9..6350997 100644
--- a/runtimes/google/naming/namespace/all_test.go
+++ b/runtimes/google/naming/namespace/all_test.go
@@ -9,6 +9,7 @@
"testing"
"time"
+ "veyron/lib/glob"
_ "veyron/lib/testutil"
"veyron/runtimes/google/naming/namespace"
service "veyron/services/mounttable/lib"
@@ -17,7 +18,9 @@
"veyron2/ipc"
"veyron2/naming"
"veyron2/rt"
+ "veyron2/security"
"veyron2/services/mounttable"
+ "veyron2/services/mounttable/types"
"veyron2/vlog"
)
@@ -52,12 +55,56 @@
return replies
}
-type testServer struct{}
+type testServer struct {
+ suffix string
+}
-func (*testServer) KnockKnock(call ipc.ServerCall) string {
+func (testServer) KnockKnock(call ipc.ServerCall) string {
return "Who's there?"
}
+// Glob applies pattern to the following tree:
+// "" -> {level1} -> {level2}
+// "".Glob("*") returns "level1"
+// "".Glob("...") returns "level1" and "level1/level2"
+// "level1".Glob("*") returns "level2"
+func (t *testServer) Glob(call ipc.ServerCall, pattern string) error {
+ g, err := glob.Parse(pattern)
+ if err != nil {
+ return err
+ }
+ tree := []string{"", "level1", "level2"}
+ for i, leaf := range tree {
+ if leaf == t.suffix {
+ return t.globLoop(call, "", g, tree[i+1:])
+ }
+ }
+ return nil
+}
+
+func (t *testServer) globLoop(call ipc.ServerCall, prefix string, g *glob.Glob, tree []string) error {
+ if g.Len() == 0 {
+ if err := call.Send(types.MountEntry{Name: prefix}); err != nil {
+ return err
+ }
+ }
+ if g.Finished() || len(tree) == 0 {
+ return nil
+ }
+ if ok, left := g.MatchInitialSegment(tree[0]); ok {
+ if err := t.globLoop(call, naming.Join(prefix, tree[0]), left, tree[1:]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+type dispatcher struct{}
+
+func (d *dispatcher) Lookup(suffix, method string) (ipc.Invoker, security.Authorizer, error) {
+ return ipc.ReflectInvoker(&testServer{suffix}), nil, nil
+}
+
func knockKnock(t *testing.T, runtime veyron2.Runtime, name string) {
client := runtime.Client()
ctx := runtime.NewContext()
@@ -167,7 +214,7 @@
return root, mps
}
-// createNamespace creates a hiearachy of mounttables and servers
+// createNamespace creates a hierarchy of mounttables and servers
// as follows:
// /mt1, /mt2, /mt3, /mt4, /mt5, /joke1, /joke2, /joke3.
// That is, mt1 is a mount table mounted in the root mount table,
@@ -177,7 +224,7 @@
jokes := make(map[string]*serverEntry)
// Let's run some non-mount table services.
for _, j := range []string{j1MP, j2MP, j3MP} {
- disp := ipc.LeafDispatcher(&testServer{}, nil)
+ disp := &dispatcher{}
jokes[j] = runServer(t, r, disp, j)
}
return root, mts, jokes, func() {
@@ -338,12 +385,12 @@
ns := r.Namespace()
ns.SetRoots(root.name)
- // Let's run some non-mount table servic
+ // Let's run some non-mount table services
for _, j := range []string{j1MP, j2MP, j3MP} {
testResolve(t, r, ns, j, jokes[j].name)
knockKnock(t, r, j)
globalName := naming.JoinAddressName(mts["mt4"].name, j)
- disp := ipc.LeafDispatcher(&testServer{}, nil)
+ disp := &dispatcher{}
gj := "g_" + j
jokes[gj] = runServer(t, r, disp, globalName)
testResolve(t, r, ns, "mt4/"+j, jokes[gj].name)
@@ -365,7 +412,9 @@
tln := []string{"baz", "mt1", "mt2", "mt3", "mt4", "mt5", "joke1", "joke2", "joke3"}
barbaz := []string{"mt4/foo/bar", "mt4/foo/baz"}
+ level12 := []string{"joke1/level1", "joke1/level1/level2", "joke2/level1", "joke2/level1/level2", "joke3/level1", "joke3/level1/level2"}
foo := append([]string{"mt4/foo"}, barbaz...)
+ foo = append(foo, level12...)
// Try various globs.
globTests := []struct {
pattern string
@@ -378,22 +427,27 @@
{"m*", []string{"mt1", "mt2", "mt3", "mt4", "mt5"}},
{"mt[2,3]", []string{"mt2", "mt3"}},
{"*z", []string{"baz"}},
+ {"joke1/*", []string{"joke1/level1"}},
+ {"j?ke1/level1/*", []string{"joke1/level1/level2"}},
+ {"joke1/level1/*", []string{"joke1/level1/level2"}},
+ {"joke1/level1/level2/...", []string{"joke1/level1/level2"}},
{"...", append(append(tln, foo...), "")},
{"*/...", append(tln, foo...)},
{"*/foo/*", barbaz},
{"*/*/*z", []string{"mt4/foo/baz"}},
{"*/f??/*z", []string{"mt4/foo/baz"}},
+ {"mt4/foo/baz", []string{"mt4/foo/baz"}},
}
for _, test := range globTests {
out := doGlob(t, r, ns, test.pattern, 0)
- compare(t, "Glob", test.pattern, test.expected, out)
+ compare(t, "Glob", test.pattern, out, test.expected)
// Do the same with a full rooted name.
out = doGlob(t, r, ns, naming.JoinAddressName(root.name, test.pattern), 0)
var expectedWithRoot []string
for _, s := range test.expected {
expectedWithRoot = append(expectedWithRoot, naming.JoinAddressName(root.name, s))
}
- compare(t, "Glob", test.pattern, expectedWithRoot, out)
+ compare(t, "Glob", test.pattern, out, expectedWithRoot)
}
}
diff --git a/services/mounttable/lib/mounttable.go b/services/mounttable/lib/mounttable.go
index 2a57fdc..f87605f 100644
--- a/services/mounttable/lib/mounttable.go
+++ b/services/mounttable/lib/mounttable.go
@@ -382,12 +382,27 @@
mt.Lock()
defer mt.Unlock()
- // If we can't find the node corresponding to the current name then there are no results.
+ // If the current name is not fully resolvable on this nameserver we
+ // don't need to evaluate the glob expression. Send a partially resolved
+ // name back to the client.
n := mt.findNode(ms.cleanedElems, false)
if n == nil {
+ ms.linkToLeaf(reply)
return nil
}
mt.globStep(n, "", g, context, reply)
return nil
}
+
+func (ms *mountContext) linkToLeaf(reply mounttable.GlobbableServiceGlobStream) {
+ n, elems := ms.mt.walk(ms.mt.root, ms.cleanedElems)
+ if n == nil {
+ return
+ }
+ servers := n.mount.servers.copyToSlice()
+ for i, s := range servers {
+ servers[i].Server = naming.Join(s.Server, slashSlashJoin(elems))
+ }
+ reply.SendStream().Send(types.MountEntry{Name: "", Servers: servers})
+}
diff --git a/services/mounttable/lib/mounttable_test.go b/services/mounttable/lib/mounttable_test.go
index 85cd332..76ed43d 100644
--- a/services/mounttable/lib/mounttable_test.go
+++ b/services/mounttable/lib/mounttable_test.go
@@ -18,6 +18,7 @@
"veyron2/rt"
"veyron2/security"
"veyron2/services/mounttable"
+ "veyron2/services/mounttable/types"
"veyron2/vlog"
)
@@ -333,7 +334,7 @@
defer server.Stop()
// set up a mount space
- fakeServer := naming.JoinAddressName(estr, "quux")
+ fakeServer := naming.JoinAddressName(estr, "//quux")
doMount(t, naming.JoinAddressName(estr, "//one/bright/day"), fakeServer, true, rootID)
doMount(t, naming.JoinAddressName(estr, "//in/the/middle"), fakeServer, true, rootID)
doMount(t, naming.JoinAddressName(estr, "//of/the/night"), fakeServer, true, rootID)
@@ -347,6 +348,7 @@
{"...", []string{"", "one", "in", "of", "one/bright", "in/the", "of/the", "one/bright/day", "in/the/middle", "of/the/night"}},
{"*/...", []string{"one", "in", "of", "one/bright", "in/the", "of/the", "one/bright/day", "in/the/middle", "of/the/night"}},
{"one/...", []string{"one", "one/bright", "one/bright/day"}},
+ {"of/the/night/two/dead/boys", []string{"of/the/night"}},
{"*/the", []string{"in/the", "of/the"}},
{"*/the/...", []string{"in/the", "of/the", "in/the/middle", "of/the/night"}},
{"o*", []string{"one", "of"}},
@@ -356,6 +358,39 @@
out := doGlob(t, naming.JoinAddressName(estr, "//"), test.in, rootID)
checkMatch(t, test.expected, out)
}
+
+ // Test Glob on a name that is under a mounted server. The result should the
+ // the address the mounted server with the extra suffix.
+ {
+ name := naming.JoinAddressName(estr, "//of/the/night/two/dead/boys/got/up/to/fight")
+ pattern := "*"
+ m, err := mounttable.BindGlobbable(name, quuxClient(rootID))
+ if err != nil {
+ boom(t, "Failed to BindMountTable: %s", err)
+ }
+ stream, err := m.Glob(rt.R().NewContext(), pattern)
+ if err != nil {
+ boom(t, "Failed call to %s.Glob(%s): %s", name, pattern, err)
+ }
+ var results []types.MountEntry
+ iterator := stream.RecvStream()
+ for iterator.Advance() {
+ results = append(results, iterator.Value())
+ }
+ if err := iterator.Err(); err != nil {
+ boom(t, "Glob %s: %s", name, err)
+ }
+ if len(results) != 1 {
+ boom(t, "Unexpected number of results. Got %v, want 1", len(results))
+ }
+ if results[0].Name != "" {
+ boom(t, "Unexpected name. Got %v, want ''", results[0].Name)
+ }
+ _, suffix := naming.SplitAddressName(results[0].Servers[0].Server)
+ if expected := "//quux/two/dead/boys/got/up/to/fight"; suffix != expected {
+ boom(t, "Unexpected suffix. Got %v, want %v", suffix, expected)
+ }
+ }
}
func TestGlobACLs(t *testing.T) {