blob: a3d67b8a3337013edbea523cad7b265b25ad67bc [file] [log] [blame]
package mounttable_test
import (
"runtime/debug"
"testing"
"time"
_ "veyron/lib/testutil"
"veyron/runtimes/google/naming/mounttable"
service "veyron/services/mounttable/lib"
"veyron2"
"veyron2/ipc"
"veyron2/naming"
"veyron2/rt"
"veyron2/vlog"
)
func boom(t *testing.T, f string, v ...interface{}) {
t.Logf(f, v...)
t.Fatal(string(debug.Stack()))
}
func doGlob(t *testing.T, mt naming.MountTable, pattern string) []string {
var replies []string
rc, err := mt.Glob(pattern)
if err != nil {
boom(t, "Glob(%s): %s", pattern, err)
}
for s := range rc {
replies = append(replies, s.Name)
}
return replies
}
func checkMatch(t *testing.T, pattern string, expected []string, got []string) {
L:
for _, e := range expected {
for _, g := range got {
if g == e {
continue L
}
}
boom(t, "Glob %s expected %v got %v", pattern, expected, got)
}
L2:
for _, g := range got {
for _, e := range expected {
if g == e {
continue L2
}
}
boom(t, "Glob %s expected %v got %v", pattern, expected, got)
}
}
type testServer struct{}
func (*testServer) KnockKnock(call ipc.ServerCall) string {
return "Who's there?"
}
func knockKnock(t *testing.T, runtime veyron2.Runtime, name string) {
client := runtime.Client()
ctx := runtime.NewContext()
call, err := client.StartCall(ctx, name, "KnockKnock", nil)
if err != nil {
boom(t, "StartCall failed: %s", err)
}
var result string
if err := call.Finish(&result); err != nil {
boom(t, "Finish returned an error: %s", err)
}
if result != "Who's there?" {
boom(t, "Wrong result: %v", result)
}
}
func TestBadRoots(t *testing.T) {
r, _ := rt.New()
if _, err := mounttable.New(r); err != nil {
t.Errorf("mounttable.New should not have failed with no roots")
}
if _, err := mounttable.New(r, "not a rooted name"); err == nil {
t.Errorf("mounttable.New should have failed with an unrooted name")
}
}
const (
mt1Prefix = "mt1"
mt2Prefix = "mt2"
mt3Prefix = "mt3"
mt4Prefix = "mt4"
mt5Prefix = "mt5"
)
func testResolveToMountTable(t *testing.T, mt naming.MountTable, name, want string) {
servers, err := mt.ResolveToMountTable(name)
if err != nil {
boom(t, "Failed to ResolveToMountTable %q: %s", name, err)
}
if len(servers) != 1 || servers[0] != want {
boom(t, "ResolveToMountTable %q returned wrong servers: got %v, want %v", name, servers, want)
}
}
func testResolve(t *testing.T, mt naming.MountTable, name, want string) {
servers, err := mt.Resolve(name)
if err != nil {
boom(t, "Failed to Resolve %q: %s", name, err)
}
if len(servers) != 1 || servers[0] != want {
boom(t, "Resolve %q returned wrong servers: got %v, want %v", name, servers, want)
}
}
func newMountTable(t *testing.T) ipc.Dispatcher {
mt, err := service.NewMountTable("")
if err != nil {
boom(t, "NewMountTable returned error: %v", err)
}
return mt
}
func runServer(t *testing.T) (ipc.Server, naming.Endpoint) {
// We are also running a server on this runtime using stubs so we must
// use rt.Init(). If the server were in a separate address as per usual,
// this wouldn't be needed and we could use rt.New.
sr := rt.Init()
vlog.Infof("TestNamespace")
server, err := sr.NewServer()
if err != nil {
boom(t, "r.NewServer: %s", err)
}
// Add some mount table servers.
if err := server.Register(mt1Prefix, newMountTable(t)); err != nil {
boom(t, "Failed to register mount table: %s", err)
}
if err := server.Register(mt2Prefix, newMountTable(t)); err != nil {
boom(t, "Failed to register mount table: %s", err)
}
if err := server.Register(mt3Prefix, newMountTable(t)); err != nil {
boom(t, "Failed to register mount table: %s", err)
}
if err := server.Register(mt4Prefix, newMountTable(t)); err != nil {
boom(t, "Failed to register mount table: %s", err)
}
if err := server.Register(mt5Prefix, newMountTable(t)); err != nil {
boom(t, "Failed to register mount table: %s", err)
}
// Add a few simple services.
if err := server.Register("joke1", ipc.SoloDispatcher(new(testServer), nil)); err != nil {
boom(t, "Failed to register test service: %s", err)
}
if err := server.Register("joke2", ipc.SoloDispatcher(new(testServer), nil)); err != nil {
boom(t, "Failed to register test service: %s", err)
}
if err := server.Register("joke3", ipc.SoloDispatcher(new(testServer), nil)); err != nil {
boom(t, "Failed to register test service: %s", err)
}
// Start serving on a loopback address.
ep, err := server.Listen("tcp", "127.0.0.1:0")
if err != nil {
boom(t, "Failed to Listen: %s", err)
}
t.Logf("endpoint %s", ep)
return server, ep
}
func TestNamespace(t *testing.T) {
// Run a MountTable server, which is serving MountTables on:
// /<estr>/{mt1,mt2,mt3,mt4,mt5}
server, ep := runServer(t)
defer server.Stop()
estr := ep.String()
// Run a client, creating a new runtime for it and intializing its
// MountTable root to point to the server created above on /<ep>/mt1.
// This means that any relative names mounted using this local MountTable
// will appear below mt1.
r, err := rt.New(veyron2.MountTableRoots([]string{naming.JoinAddressName(estr, mt1Prefix)}))
if err != nil {
boom(t, "Failed to create client runtime: %s", err)
}
mt := r.MountTable()
// Create a DAG of mount table servers using relative addresses.
ttl := time.Duration(100) * time.Second
// Mount using a relative name starting with //. This means don't walk out of the
// namespace's root mount table even if there is already something mounted at mt2.
mt2Name := naming.JoinAddressName(estr, mt2Prefix)
if err := mt.Mount("//mt2", mt2Name, ttl); err != nil {
boom(t, "Failed to Mount //mt2: %s", err)
}
// Mount using the relative name not starting with //. This means walk through mt3
// if it already exists and mount at its root. However, since it doesn't exist, this is the
// same as if we'd mounted at //mt3.
//
// NB: if we mount two replica mount table servers at the same place in the namespace,
// we MUST use the // form or it will try to mount the second inside the first rather
// than at the same place as the first.
mt3Name := naming.JoinAddressName(estr, mt3Prefix)
if err := mt.Mount("mt3", mt3Name, ttl); err != nil {
boom(t, "Failed to Mount mt3: %s", err)
}
mt1MT := naming.MakeTerminal(naming.JoinAddressName(estr, mt1Prefix))
mt2MT := naming.MakeTerminal(naming.JoinAddressName(estr, naming.Join(mt1Prefix, mt2Prefix)))
mt3MT := naming.MakeTerminal(naming.JoinAddressName(estr, naming.Join(mt1Prefix, mt3Prefix)))
// After the mounts above we have MountTables at /<estr>/mt1{//mt2,//mt3},
// with server addresses as per below.
testResolveToMountTable(t, mt, "", mt1MT)
testResolveToMountTable(t, mt, "mt2", mt2MT)
testResolveToMountTable(t, mt, "mt3", mt3MT)
testResolveToMountTable(t, mt, "//mt3", naming.JoinAddressName(estr, "//mt1//mt3"))
// We can resolve to the MountTables using rooted, terminal names
// as follows, both mt1 and mt1/{mt2,mt3} are served by the
// top-level MountTable
testResolve(t, mt, naming.JoinAddressName(estr, "//mt1"), mt1MT)
testResolve(t, mt, naming.JoinAddressName(estr, "//mt1/mt2"), mt2MT)
testResolve(t, mt, naming.JoinAddressName(estr, "//mt1/mt3"), mt3MT)
// returns [mt2, mt3]
vlog.Infof("GLOB: %s", doGlob(t, mt, "*"))
// Perform two mounts that have to actually walk through other mount tables.
if err := mt.Mount("mt2/mt4", naming.JoinAddressName(estr, mt4Prefix), ttl); err != nil {
boom(t, "Failed to Mount mt2/mt4: %s", err)
}
if err := mt.Mount("mt3/mt4", naming.JoinAddressName(estr, mt4Prefix), ttl); err != nil {
boom(t, "Failed to Mount mt3/mt4: %s", err)
}
// After the mounts above we now have /<estr>{/mt1/mt2/mt4,/mt1/mt3/mt4}.
testResolveToMountTable(t, mt, "mt2/mt4", naming.JoinAddressName(estr, "//mt2/mt4"))
testResolveToMountTable(t, mt, "mt3/mt4", naming.JoinAddressName(estr, "//mt3/mt4"))
testResolve(t, mt, naming.JoinAddressName(estr, "//mt1/mt2/mt4"), naming.JoinAddressName(estr, "//mt1/mt2/mt4"))
// Perform a mount that uses a global name as the mount point rather than
// one relative to our namespace's root.
global := naming.JoinAddressName(estr, "mt3/mt4/mt5")
if err := mt.Mount(global, naming.JoinAddressName(estr, mt5Prefix), ttl); err != nil {
boom(t, "Failed to Mount %s: %s", global, err)
}
// This mounts the service OA (ep/joke1) as joke1.
if err := mt.Mount("joke1", naming.JoinAddressName(estr, "//joke1"), ttl); err != nil {
boom(t, "Failed to Mount joke1: %s", err)
}
// This mounts the raw server endpoint as joke2 -- like Publish would.
if err := mt.Mount("joke2", naming.JoinAddressName(estr, "")+"//", ttl); err != nil {
boom(t, "Failed to Mount joke2: %s", err)
}
// This mounts the raw server endpoint as joke3 in mt3 -- like Publish would.
if err := mt.Mount("mt3/joke3", naming.JoinAddressName(estr, "")+"//", ttl); err != nil {
boom(t, "Failed to Mount joke3: %s", err)
}
// After the mounts above we have:
// /<estr>/mt3/mt4/mt5 - the global mount above
// /<estr>/mt1/{joke1,joke2,mt3/joker3}
// Now try resolving inside the namespace. This guarantees both that the mounts did
// what we expected AND that we can actually resolve the results.
// Get back an error since this will walk through mt5 to its root.
_, err = mt.Resolve("mt3/mt4/mt5")
if err == nil {
boom(t, "Should have failed to mt3/mt4/mt5")
}
// Resolving m3/mt4/mt5 to a MountTable using the local MountTable gives
// us /<estr>//mt4/mt5.
testResolveToMountTable(t, mt, "mt3/mt4/mt5", naming.JoinAddressName(estr, "//mt4/mt5"))
testResolveToMountTable(t, mt, "mt3/mt4//mt5", naming.JoinAddressName(estr, "//mt4//mt5"))
// But looking up mt4/mt5 in the local MountTable will give us
// /<estr>//mt1/mt4/mt5 since the localMountTable has mt1 as its root!
testResolveToMountTable(t, mt, "mt4/mt5", naming.JoinAddressName(estr, "//mt1/mt4/mt5"))
// Looking mt3//mt4/mt5 will return the MountTable that serves //mt4/mt5.
testResolveToMountTable(t, mt, "mt3//mt4/mt5", naming.JoinAddressName(estr, "//mt3//mt4/mt5"))
// And the MountTable that serves //mt4/mt5 is /<epstr>//mt1/mt4/mt5
testResolveToMountTable(t, mt, "//mt4/mt5", naming.JoinAddressName(estr, "//mt1//mt4/mt5"))
vlog.Infof("\n-------------------------------------------------")
jokeTests := []struct {
name, resolved, resolvedToMT string
}{
{"joke1", naming.JoinAddressName(estr, "//joke1"), naming.JoinAddressName(estr, "//mt1/joke1")},
{"joke2", naming.JoinAddressName(estr, "") + "//", naming.JoinAddressName(estr, "//mt1/joke2")},
{"mt3/joke3", naming.JoinAddressName(estr, "") + "//", naming.JoinAddressName(estr, "//mt3/joke3")},
}
for _, test := range jokeTests {
servers, err := mt.Resolve(test.name)
if err != nil {
boom(t, "Failed to Resolve %s: %s", test.name, err)
}
if len(servers) != 1 || servers[0] != test.resolved {
boom(t, "Resolve %s returned wrong servers: %v, expected: %s", test.name, servers, test.resolved)
}
servers, err = mt.ResolveToMountTable(test.name)
if err != nil {
boom(t, "Failed to ResolveToMountTable %s: %s", test.name, err)
}
if len(servers) != 1 || servers[0] != test.resolvedToMT {
boom(t, "ResolveToMountTable %s returned wrong servers: %v, expected: %s", test.name, servers, test.resolvedToMT)
}
}
knockKnock(t, r, "joke1")
knockKnock(t, r, "joke2/joke2")
knockKnock(t, r, "mt3/joke3/joke3")
// Try various globs.
globTests := []struct {
pattern string
expected []string
}{
{"*", []string{"mt2", "mt3", "joke1", "joke2"}},
{"*/...", []string{"mt2", "mt3", "mt2/mt4", "mt3/mt4", "mt2/mt4/mt5", "mt3/mt4/mt5", "joke1", "joke2", "mt3/joke3"}},
{"*/m?4/*5", []string{"mt2/mt4/mt5", "mt3/mt4/mt5"}},
{"*2*/*/*5", []string{"mt2/mt4/mt5"}},
{"mt2/*/*5", []string{"mt2/mt4/mt5"}},
{"mt2/mt4/*5", []string{"mt2/mt4/mt5"}},
}
for _, test := range globTests {
out := doGlob(t, mt, test.pattern)
checkMatch(t, test.pattern, test.expected, out)
}
}