namespace: Don't return errors for Mount/Unmount if all routing ids have
successful returns.

A mount table may be mounted many times in one place if it has endpoints
on multiple networks.  Before this change Mount would return an error if
a Mount attempt on any of these networks failed.  Now it only returns an
error if all mounts to the same routing id fail.

Change-Id: I3ebfa06395768701367221e0e355303841611688
diff --git a/runtimes/google/naming/namespace/mount.go b/runtimes/google/naming/namespace/mount.go
index f82d5b3..ac2e875 100644
--- a/runtimes/google/naming/namespace/mount.go
+++ b/runtimes/google/naming/namespace/mount.go
@@ -3,6 +3,8 @@
 import (
 	"time"
 
+	inaming "veyron.io/veyron/veyron/runtimes/google/naming"
+
 	"veyron.io/veyron/veyron2/context"
 	"veyron.io/veyron/veyron2/ipc"
 	"veyron.io/veyron/veyron2/naming"
@@ -10,30 +12,93 @@
 	"veyron.io/veyron/veyron2/vlog"
 )
 
+type status struct {
+	id  string
+	err error
+}
+
 // mountIntoMountTable mounts a single server into a single mount table.
-func mountIntoMountTable(ctx context.T, client ipc.Client, name, server string, ttl time.Duration, flags naming.MountFlag) error {
+func mountIntoMountTable(ctx context.T, client ipc.Client, name, server string, ttl time.Duration, flags naming.MountFlag, id string) (s status) {
+	s.id = id
 	ctx, _ = ctx.WithTimeout(callTimeout)
 	call, err := client.StartCall(ctx, name, "Mount", []interface{}{server, uint32(ttl.Seconds()), flags}, options.NoResolve(true))
+	s.err = err
 	if err != nil {
-		return err
+		return
 	}
 	if ierr := call.Finish(&err); ierr != nil {
-		return ierr
+		s.err = ierr
 	}
-	return err
+	return
 }
 
 // unmountFromMountTable removes a single mounted server from a single mount table.
-func unmountFromMountTable(ctx context.T, client ipc.Client, name, server string) error {
+func unmountFromMountTable(ctx context.T, client ipc.Client, name, server string, id string) (s status) {
+	s.id = id
 	ctx, _ = ctx.WithTimeout(callTimeout)
 	call, err := client.StartCall(ctx, name, "Unmount", []interface{}{server}, options.NoResolve(true))
+	s.err = err
+	if err != nil {
+		return
+	}
+	if ierr := call.Finish(&err); ierr != nil {
+		s.err = ierr
+	}
+	return
+}
+
+// nameToRID converts a name to a routing ID string. If a routing ID can't be obtained,
+// it just returns the name.
+func nameToRID(name string) string {
+	address, _ := naming.SplitAddressName(name)
+	if ep, err := inaming.NewEndpoint(address); err == nil {
+		return ep.RID.String()
+	}
+	return name
+}
+
+// collectStati collects n status messages from channel c and returns an error if, for
+// any id, there is no successful reply.
+func collectStati(c chan status, n int) error {
+	// Make a map indexed by the routing id (or address if routing id not found) of
+	// each mount table.  A mount table may be reachable via multiple addresses but
+	// each address should have the same routing id.  We should only return an error
+	// if any of the ids had no successful mounts.
+	statusByID := make(map[string]error)
+	// Get the status of each request.
+	for i := 0; i < n; i++ {
+		s := <-c
+		if _, ok := statusByID[s.id]; !ok || s.err == nil {
+			statusByID[s.id] = s.err
+		}
+	}
+	// Return any error.
+	for _, s := range statusByID {
+		if s != nil {
+			return s
+		}
+	}
+	return nil
+}
+
+// dispatch executes f in parallel for each mount table implementing mTName.
+func (ns *namespace) dispatch(ctx context.T, mTName string, f func(context.T, string, string) status) error {
+	// Resolve to all the mount tables implementing name.
+	mts, err := ns.ResolveToMountTable(ctx, mTName)
 	if err != nil {
 		return err
 	}
-	if ierr := call.Finish(&err); ierr != nil {
-		return ierr
+	// Apply f to each of the returned mount tables.
+	c := make(chan status, len(mts))
+	for _, mt := range mts {
+		go func(mt string) {
+			c <- f(ctx, mt, nameToRID(mt))
+		}(mt)
 	}
-	return err
+	finalerr := collectStati(c, len(mts))
+	// Forget any previous cached information about these names.
+	ns.resolutionCache.forget(mts)
+	return finalerr
 }
 
 func (ns *namespace) Mount(ctx context.T, name, server string, ttl time.Duration, opts ...naming.MountOpt) error {
@@ -53,55 +118,22 @@
 			}
 		}
 	}
-
-	// Resolve to all the mount tables implementing name.
-	mtServers, err := ns.ResolveToMountTable(ctx, name)
-	if err != nil {
-		return err
-	}
 	// Mount the server in all the returned mount tables.
-	c := make(chan error, len(mtServers))
-	for _, mt := range mtServers {
-		go func() {
-			c <- mountIntoMountTable(ctx, ns.rt.Client(), mt, server, ttl, flags)
-		}()
+	f := func(ctx context.T, mt, id string) status {
+		return mountIntoMountTable(ctx, ns.rt.Client(), mt, server, ttl, flags, id)
 	}
-	// Return error if any mounts failed, since otherwise we'll get
-	// inconsistent resolutions down the road.
-	var finalerr error
-	for _ = range mtServers {
-		if err := <-c; err != nil {
-			finalerr = err
-		}
-	}
-	vlog.VI(1).Infof("Mount(%s, %s) -> %v", name, server, finalerr)
-	// Forget any previous cached information about these names.
-	ns.resolutionCache.forget(mtServers)
-	return finalerr
+	err := ns.dispatch(ctx, name, f)
+	vlog.VI(1).Infof("Mount(%s, %s) -> %v", name, server, err)
+	return err
 }
 
 func (ns *namespace) Unmount(ctx context.T, name, server string) error {
 	defer vlog.LogCall()()
-	mts, err := ns.ResolveToMountTable(ctx, name)
-	if err != nil {
-		return err
-	}
-
 	// Unmount the server from all the mount tables.
-	c := make(chan error, len(mts))
-	for _, mt := range mts {
-		go func() {
-			c <- unmountFromMountTable(ctx, ns.rt.Client(), mt, server)
-		}()
+	f := func(ctx context.T, mt, id string) status {
+		return unmountFromMountTable(ctx, ns.rt.Client(), mt, server, id)
 	}
-	// Return error if any mounts failed, since otherwise we'll get
-	// inconsistent resolutions down the road.
-	var finalerr error
-	for _ = range mts {
-		if err := <-c; err != nil {
-			finalerr = err
-		}
-	}
-	ns.resolutionCache.forget(mts)
-	return finalerr
+	err := ns.dispatch(ctx, name, f)
+	vlog.VI(1).Infof("Unmount(%s, %s) -> %v", name, server, err)
+	return err
 }