namespace: Resolve goes all the way while ShallowResolve is the old behavior.

MultiPart: 1/4

Change-Id: I3f03f7f76c6a695d40201b74ba2b8d4400911bd7
diff --git a/cmd/namespace/doc.go b/cmd/namespace/doc.go
index 64a2ddb..a91480a 100644
--- a/cmd/namespace/doc.go
+++ b/cmd/namespace/doc.go
@@ -128,6 +128,8 @@
  -insecure=false
    Insecure mode: May return results from untrusted servers and invoke Resolve
    on untrusted mounttables
+ -s=false
+   True to perform a shallow resolution
 
 Namespace resolvetomt - Finds the address of the mounttable that holds an object name
 
diff --git a/cmd/namespace/impl.go b/cmd/namespace/impl.go
index 30eb4ec..b776ddf 100644
--- a/cmd/namespace/impl.go
+++ b/cmd/namespace/impl.go
@@ -40,11 +40,13 @@
 	flagInsecureResolve     bool
 	flagInsecureResolveToMT bool
 	flagDeleteSubtree       bool
+	flagShallowResolve      bool
 )
 
 func init() {
 	cmdGlob.Flags.BoolVar(&flagLongGlob, "l", false, "Long listing format.")
 	cmdResolve.Flags.BoolVar(&flagInsecureResolve, "insecure", false, "Insecure mode: May return results from untrusted servers and invoke Resolve on untrusted mounttables")
+	cmdResolve.Flags.BoolVar(&flagShallowResolve, "s", false, "True to perform a shallow resolution")
 	cmdResolveToMT.Flags.BoolVar(&flagInsecureResolveToMT, "insecure", false, "Insecure mode: May return results from untrusted servers and invoke Resolve on untrusted mounttables")
 	cmdDelete.Flags.BoolVar(&flagDeleteSubtree, "r", false, "Delete all children of the name in addition to the name itself.")
 }
@@ -213,7 +215,13 @@
 	if flagInsecureResolve {
 		opts = append(opts, options.NameResolutionAuthorizer{security.AllowEveryone()})
 	}
-	me, err := ns.Resolve(ctx, name, opts...)
+	var err error
+	var me *naming.MountEntry
+	if flagShallowResolve {
+		me, err = ns.ShallowResolve(ctx, name, opts...)
+	} else {
+		me, err = ns.Resolve(ctx, name, opts...)
+	}
 	if err != nil {
 		ctx.Infof("ns.Resolve(%q) failed: %v", name, err)
 		return err
diff --git a/cmd/vrpc/vrpc.go b/cmd/vrpc/vrpc.go
index af9538b..774cfd3 100644
--- a/cmd/vrpc/vrpc.go
+++ b/cmd/vrpc/vrpc.go
@@ -139,6 +139,16 @@
 	ArgsLong: serverDesc,
 }
 
+func getNamespaceOpts(opts []rpc.CallOpt) []naming.NamespaceOpt {
+	var out []naming.NamespaceOpt
+	for _, o := range opts {
+		if co, ok := o.(naming.NamespaceOpt); ok {
+			out = append(out, co)
+		}
+	}
+	return out
+}
+
 func runSignature(ctx *context.T, env *cmdline.Env, args []string) error {
 	// Error-check args.
 	var server, method string
@@ -159,6 +169,14 @@
 	if flagInsecure {
 		opts = append(opts, insecureOpts...)
 	}
+	if flagShallowResolve {
+		// Find the containing mount table.
+		me, err := v23.GetNamespace(ctx).ShallowResolve(ctx, server, getNamespaceOpts(opts)...)
+		if err != nil {
+			return err
+		}
+		opts = append(opts, options.Preresolved{me})
+	}
 	if method != "" {
 		methodSig, err := reserved.MethodSignature(ctx, server, method, opts...)
 		if err != nil {
diff --git a/cmd/vrpc/vrpc_test.go b/cmd/vrpc/vrpc_test.go
index f004bb9..7cb9f94 100644
--- a/cmd/vrpc/vrpc_test.go
+++ b/cmd/vrpc/vrpc_test.go
@@ -131,7 +131,7 @@
 	defer shutdown()
 	var stdout, stderr bytes.Buffer
 	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
-	args := []string{"signature", fmt.Sprintf("-show-reserved=%v", showReserved), name}
+	args := []string{"signature", "-s", fmt.Sprintf("-show-reserved=%v", showReserved), name}
 	if err := v23cmd.ParseAndRunForTest(cmdVRPC, ctx, env, args); err != nil {
 		t.Fatalf("%s: %v", args, err)
 	}
diff --git a/runtime/internal/naming/endpoint.go b/runtime/internal/naming/endpoint.go
index 382de34..aa9a66e 100644
--- a/runtime/internal/naming/endpoint.go
+++ b/runtime/internal/naming/endpoint.go
@@ -47,9 +47,6 @@
 func NewEndpoint(input string) (*Endpoint, error) {
 	ep := new(Endpoint)
 
-	// We have to guess this is a mount table if we don't know.
-	ep.IsMountTable = true
-
 	// If the endpoint does not end in a @, it must be in [blessing@]host:port format.
 	if parts := hostportEP.FindStringSubmatch(input); len(parts) > 0 {
 		hostport := parts[len(parts)-1]
@@ -60,6 +57,7 @@
 		err := ep.parseHostPort(blessing, hostport)
 		return ep, err
 	}
+
 	// Trim the prefix and suffix and parse the rest.
 	input = strings.TrimPrefix(strings.TrimSuffix(input, suffix), separator)
 	parts := strings.Split(input, separator)
@@ -82,6 +80,11 @@
 	if _, _, err := net.SplitHostPort(hostport); err != nil {
 		return errInvalidEndpointString
 	}
+	if strings.HasSuffix(hostport, "#") {
+		hostport = strings.TrimSuffix(hostport, "#")
+	} else {
+		ep.IsMountTable = true
+	}
 	ep.Protocol = naming.UnknownProtocol
 	ep.Address = hostport
 	ep.RID = naming.NullRoutingID
diff --git a/runtime/internal/naming/namespace/all_test.go b/runtime/internal/naming/namespace/all_test.go
index 6c93b0c..1baed6a 100644
--- a/runtime/internal/naming/namespace/all_test.go
+++ b/runtime/internal/naming/namespace/all_test.go
@@ -31,9 +31,9 @@
 func resolveWithRetry(ctx *context.T, name string, opts ...naming.NamespaceOpt) *naming.MountEntry {
 	ns := v23.GetNamespace(ctx)
 	for {
-		mp, err := ns.Resolve(ctx, name, opts...)
+		me, err := ns.ShallowResolve(ctx, name, opts...)
 		if err == nil {
-			return mp
+			return me
 		}
 		time.Sleep(100 * time.Millisecond)
 	}
@@ -189,7 +189,7 @@
 }
 
 func testResolve(t *testing.T, ctx *context.T, ns namespace.T, name string, want ...string) {
-	doResolveTest(t, "Resolve", ns.Resolve, ctx, name, want)
+	doResolveTest(t, "Resolve", ns.ShallowResolve, ctx, name, want)
 }
 
 type serverEntry struct {
@@ -219,7 +219,9 @@
 	eps := s.Status().Endpoints
 	t.Logf("server %q -> %s", eps[0].Name(), mountPoint)
 	// Wait until the mount point appears in the mount table.
-	resolveWithRetry(ctx, mountPoint)
+	if len(mountPoint) > 0 {
+		resolveWithRetry(ctx, mountPoint)
+	}
 	return &serverEntry{mountPoint: mountPoint, stop: s.Stop, endpoint: eps[0], name: eps[0].Name()}
 }
 
@@ -672,14 +674,15 @@
 	// (which has different blessings)
 	hproot := fmt.Sprintf("(otherroot)@%v", rootmt.endpoint.Addr())
 	eproot := naming.FormatEndpoint(rootmt.endpoint.Addr().Network(), rootmt.endpoint.Addr().String(), rootmt.endpoint.RoutingID(), naming.BlessingOpt("otherroot"), naming.ServesMountTable(rootmt.endpoint.ServesMountTable()))
+	ns := v23.GetNamespace(ctx)
 	for _, root := range []string{hproot, eproot} {
 		name := naming.JoinAddressName(root, "mt")
-		// Rooted name resolutions should fail authorization because of the "otherrot"
-		if e, err := clientNs.Resolve(clientCtx, name); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+		// Rooted name resolutions should fail authorization because of the "otherroot"
+		if e, err := ns.ShallowResolve(clientCtx, name); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
 			t.Errorf("resolve(%q) returned (%v, errorid=%v %v), wanted errorid=%v", name, e, verror.ErrorID(err), err, verror.ErrNotTrusted.ID)
 		}
 		// But not fail if the server authorization is skipped.
-		if e, err := clientNs.Resolve(clientCtx, name, options.NameResolutionAuthorizer{security.AllowEveryone()}); err != nil {
+		if e, err := ns.ShallowResolve(clientCtx, name, options.NameResolutionAuthorizer{security.AllowEveryone()}); err != nil {
 			t.Errorf("resolve(%q): Got (%v, %v), expected resolution to succeed", name, e, err)
 		}
 
diff --git a/runtime/internal/naming/namespace/resolve.go b/runtime/internal/naming/namespace/resolve.go
index 4b0dfc7..2a0825d 100644
--- a/runtime/internal/naming/namespace/resolve.go
+++ b/runtime/internal/naming/namespace/resolve.go
@@ -96,6 +96,7 @@
 // Resolve implements v.io/v23/naming.Namespace.
 func (ns *namespace) Resolve(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
 	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+
 	// If caller supplied a mount entry, use it.
 	e, skipResolution := preresolved(opts)
 	if e != nil {
@@ -123,7 +124,7 @@
 		if ctx.V(2) {
 			ctx.Infof("Resolve(%s) loop %v", name, *e)
 		}
-		if !e.ServesMountTable || terminal(e) {
+		if !e.ServesMountTable {
 			if ctx.V(1) {
 				ctx.Infof("Resolve(%s) -> %v", name, *e)
 			}
@@ -148,6 +149,26 @@
 	return nil, verror.New(naming.ErrResolutionDepthExceeded, ctx)
 }
 
+// ShallowResolve implements v.io/v23/naming.Namespace.
+func (ns *namespace) ShallowResolve(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
+	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
+
+	// Find the containing mount table.
+	me, err := ns.ResolveToMountTable(ctx, name, opts...)
+	if err != nil {
+		return nil, err
+	}
+	if terminal(me) {
+		return me, nil
+	}
+
+	// Resolve the entry directly.
+	client := v23.GetClient(ctx)
+	entry := new(naming.MountEntry)
+	err = client.Call(ctx, name, "ResolveStep", nil, []interface{}{entry}, append(getCallOpts(opts), options.Preresolved{me})...)
+	return entry, err
+}
+
 // ResolveToMountTable implements v.io/v23/naming.Namespace.
 func (ns *namespace) ResolveToMountTable(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
 	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
diff --git a/runtime/internal/rpc/resolve_test.go b/runtime/internal/rpc/resolve_test.go
index e5b1e0a..feace08 100644
--- a/runtime/internal/rpc/resolve_test.go
+++ b/runtime/internal/rpc/resolve_test.go
@@ -76,7 +76,7 @@
 	ctx := sh.Ctx
 	ns := v23.GetNamespace(ctx)
 
-	proxyEp, _ := inaming.NewEndpoint("proxy.v.io:123")
+	proxyEp, _ := inaming.NewEndpoint("proxy.v.io:123#")
 	proxyEpStr := proxyEp.String()
 	proxyAddr := naming.JoinAddressName(proxyEpStr, "")
 	if err := ns.Mount(ctx, "proxy", proxyAddr, time.Hour); err != nil {
@@ -94,7 +94,7 @@
 		result  string
 		err     error
 	}{
-		{"/proxy.v.io:123", proxyEpStr, nil},
+		{"/proxy.v.io:123#", proxyEpStr, nil},
 		{"proxy.v.io:123", "", notfound},
 		{"proxy", proxyEpStr, nil},
 		{naming.JoinAddressName(ns.Roots()[0], "proxy"), proxyEpStr, nil},
diff --git a/runtime/internal/testing/mocks/naming/namespace.go b/runtime/internal/testing/mocks/naming/namespace.go
index fd0e407..a15e6d3 100644
--- a/runtime/internal/testing/mocks/naming/namespace.go
+++ b/runtime/internal/testing/mocks/naming/namespace.go
@@ -135,6 +135,10 @@
 	return nil, verror.New(naming.ErrNoSuchName, ctx, fmt.Sprintf("Resolve name %q not found in %v", name, ns.mounts))
 }
 
+func (ns *namespaceMock) ShallowResolve(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
+	return ns.Resolve(ctx, name, opts...)
+}
+
 func (ns *namespaceMock) ResolveToMountTable(ctx *context.T, name string, opts ...naming.NamespaceOpt) (*naming.MountEntry, error) {
 	defer apilog.LogCallf(ctx, "name=%.10s...,opts...=%v", name, opts)(ctx, "") // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT
 	// TODO(mattr): Implement this method for tests that might need it.
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index f8821d2..85e3547 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -283,7 +283,7 @@
 		if err := testutil.RetryFor(10*time.Second, func() error {
 			// Set ExitErrorIsOk to true since we expect "namespace resolve" to fail
 			// if the name doesn't exist.
-			c := withArgs(namespaceBin, "resolve", name)
+			c := withArgs(namespaceBin, "resolve", "-s", name)
 			c.ExitErrorIsOk = true
 			c.AddStderrWriter(os.Stderr)
 			if res = tr(c.Stdout()); len(res) > 0 {
@@ -475,7 +475,7 @@
 		if err := testutil.RetryFor(10*time.Second, func() error {
 			// Set ExitErrorIsOk to true since we expect "namespace resolve" to fail
 			// if the name doesn't exist.
-			c := withArgs(namespaceBin, "resolve", name)
+			c := withArgs(namespaceBin, "resolve", "-s", name)
 			c.ExitErrorIsOk = true
 			c.AddStderrWriter(os.Stderr)
 			switch res = tr(c.Stdout()); {
@@ -518,7 +518,7 @@
 	resolve(mtEP + "/global")
 
 	namespaceRoot := sh.Vars[ref.EnvNamespacePrefix]
-	output = withArgs(namespaceBin, "resolve", mtEP+"/global").Stdout()
+	output = withArgs(namespaceBin, "resolve", "-s", mtEP+"/global").Stdout()
 	if got, want := tr(output), namespaceRoot; got != want {
 		t.Fatalf("got %q, want %q", got, want)
 	}
@@ -537,7 +537,7 @@
 		if err := testutil.RetryFor(10*time.Second, func() error {
 			// Set ExitErrorIsOk to true since we expect "namespace resolve" to fail
 			// if the name doesn't exist.
-			c := withArgs(namespaceBin, "resolve", name)
+			c := withArgs(namespaceBin, "resolve", "-s", name)
 			c.ExitErrorIsOk = true
 			c.AddStderrWriter(os.Stderr)
 			if res = tr(c.Stdout()); len(res) == 0 {
diff --git a/services/wspr/internal/namespace/namespace.vdl b/services/wspr/internal/namespace/namespace.vdl
index fba8427..0bc7e81 100644
--- a/services/wspr/internal/namespace/namespace.vdl
+++ b/services/wspr/internal/namespace/namespace.vdl
@@ -27,6 +27,12 @@
 	// ResolveToMountTable resolves a name to the address of the mounttable
 	// directly hosting it.
 	ResolveToMountTable(name string) ([]string | error)
+	// ShallowResolve resolves the object name into its mounted servers.  It is the same
+	// as Resolve except when mounttables are stacked below the same mount point.  For example,
+	// if service D is mounted onto /MTA/a/b and /MTA/a/b is mounted onto /MTB/x/y then
+	// Resolve(/MTB/x/y) will return a pointer to D while ShallowResolve(/MTB/x/y) will
+	// return a pointer to /MTA/a/b.
+	ShallowResolve(name string) ([]string | error)
 	// FlushCacheEntry removes the namespace cache entry for a given name.
 	FlushCacheEntry(name string) (bool | error)
 	// DisableCache disables the naming cache.
diff --git a/services/wspr/internal/namespace/namespace.vdl.go b/services/wspr/internal/namespace/namespace.vdl.go
index 3163740..8dbf960 100644
--- a/services/wspr/internal/namespace/namespace.vdl.go
+++ b/services/wspr/internal/namespace/namespace.vdl.go
@@ -39,6 +39,12 @@
 	// ResolveToMountTable resolves a name to the address of the mounttable
 	// directly hosting it.
 	ResolveToMountTable(_ *context.T, name string, _ ...rpc.CallOpt) ([]string, error)
+	// ShallowResolve resolves the object name into its mounted servers.  It is the same
+	// as Resolve except when mounttables are stacked below the same mount point.  For example,
+	// if service D is mounted onto /MTA/a/b and /MTA/a/b is mounted onto /MTB/x/y then
+	// Resolve(/MTB/x/y) will return a pointer to D while ShallowResolve(/MTB/x/y) will
+	// return a pointer to /MTA/a/b.
+	ShallowResolve(_ *context.T, name string, _ ...rpc.CallOpt) ([]string, error)
 	// FlushCacheEntry removes the namespace cache entry for a given name.
 	FlushCacheEntry(_ *context.T, name string, _ ...rpc.CallOpt) (bool, error)
 	// DisableCache disables the naming cache.
@@ -99,6 +105,11 @@
 	return
 }
 
+func (c implNamespaceClientStub) ShallowResolve(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 []string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "ShallowResolve", []interface{}{i0}, []interface{}{&o0}, opts...)
+	return
+}
+
 func (c implNamespaceClientStub) FlushCacheEntry(ctx *context.T, i0 string, opts ...rpc.CallOpt) (o0 bool, err error) {
 	err = v23.GetClient(ctx).Call(ctx, c.name, "FlushCacheEntry", []interface{}{i0}, []interface{}{&o0}, opts...)
 	return
@@ -216,6 +227,12 @@
 	// ResolveToMountTable resolves a name to the address of the mounttable
 	// directly hosting it.
 	ResolveToMountTable(_ *context.T, _ rpc.ServerCall, name string) ([]string, error)
+	// ShallowResolve resolves the object name into its mounted servers.  It is the same
+	// as Resolve except when mounttables are stacked below the same mount point.  For example,
+	// if service D is mounted onto /MTA/a/b and /MTA/a/b is mounted onto /MTB/x/y then
+	// Resolve(/MTB/x/y) will return a pointer to D while ShallowResolve(/MTB/x/y) will
+	// return a pointer to /MTA/a/b.
+	ShallowResolve(_ *context.T, _ rpc.ServerCall, name string) ([]string, error)
 	// FlushCacheEntry removes the namespace cache entry for a given name.
 	FlushCacheEntry(_ *context.T, _ rpc.ServerCall, name string) (bool, error)
 	// DisableCache disables the naming cache.
@@ -248,6 +265,12 @@
 	// ResolveToMountTable resolves a name to the address of the mounttable
 	// directly hosting it.
 	ResolveToMountTable(_ *context.T, _ rpc.ServerCall, name string) ([]string, error)
+	// ShallowResolve resolves the object name into its mounted servers.  It is the same
+	// as Resolve except when mounttables are stacked below the same mount point.  For example,
+	// if service D is mounted onto /MTA/a/b and /MTA/a/b is mounted onto /MTB/x/y then
+	// Resolve(/MTB/x/y) will return a pointer to D while ShallowResolve(/MTB/x/y) will
+	// return a pointer to /MTA/a/b.
+	ShallowResolve(_ *context.T, _ rpc.ServerCall, name string) ([]string, error)
 	// FlushCacheEntry removes the namespace cache entry for a given name.
 	FlushCacheEntry(_ *context.T, _ rpc.ServerCall, name string) (bool, error)
 	// DisableCache disables the naming cache.
@@ -313,6 +336,10 @@
 	return s.impl.ResolveToMountTable(ctx, call, i0)
 }
 
+func (s implNamespaceServerStub) ShallowResolve(ctx *context.T, call rpc.ServerCall, i0 string) ([]string, error) {
+	return s.impl.ShallowResolve(ctx, call, i0)
+}
+
 func (s implNamespaceServerStub) FlushCacheEntry(ctx *context.T, call rpc.ServerCall, i0 string) (bool, error) {
 	return s.impl.FlushCacheEntry(ctx, call, i0)
 }
@@ -403,6 +430,16 @@
 			},
 		},
 		{
+			Name: "ShallowResolve",
+			Doc:  "// ShallowResolve resolves the object name into its mounted servers.  It is the same\n// as Resolve except when mounttables are stacked below the same mount point.  For example,\n// if service D is mounted onto /MTA/a/b and /MTA/a/b is mounted onto /MTB/x/y then\n// Resolve(/MTB/x/y) will return a pointer to D while ShallowResolve(/MTB/x/y) will\n// return a pointer to /MTA/a/b.",
+			InArgs: []rpc.ArgDesc{
+				{"name", ``}, // string
+			},
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []string
+			},
+		},
+		{
 			Name: "FlushCacheEntry",
 			Doc:  "// FlushCacheEntry removes the namespace cache entry for a given name.",
 			InArgs: []rpc.ArgDesc{
diff --git a/services/wspr/internal/namespace/request_handler.go b/services/wspr/internal/namespace/request_handler.go
index c262a50..2fbf221 100644
--- a/services/wspr/internal/namespace/request_handler.go
+++ b/services/wspr/internal/namespace/request_handler.go
@@ -62,6 +62,14 @@
 	return me.Names(), nil
 }
 
+func (s *Server) ShallowResolve(ctx *context.T, _ rpc.ServerCall, name string) ([]string, error) {
+	me, err := s.ns.ShallowResolve(ctx, name)
+	if err != nil {
+		return nil, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	return me.Names(), nil
+}
+
 func (s *Server) ResolveToMountTable(ctx *context.T, _ rpc.ServerCall, name string) ([]string, error) {
 	me, err := s.ns.ResolveToMountTable(ctx, name)
 	if err != nil {