veyron/services/mgmt/device: Add local namespace

This change adds a local mounttable and neighborhood service to the
device manager. The applications receive the address of this mounttable
as default namespace root.

The local namespace looks like:
  <root>/devmgr  -> device manager server
  <root>/global  -> the global namespace
  <root>/nh      -> the local neighborhood

Each app gets the local namespace as namespace root, and the local
mounttable is the only thing that gets published directly in the global
namespace.

The global namespace looks like:
  <root>/devices/<hostname>/devmgr -> <hostname>'s device manager
  <root>/devices/<hostname>/global -> points back to <root>
  <root>/devices/<hostname>/nh     -> <hostname>'s local neighborhood

Change-Id: I2b816acd3e3513bd6f82e3b818a6b72cd4d15128
diff --git a/services/mgmt/device/deviced/server.go b/services/mgmt/device/deviced/server.go
index 7362b43..0127f2e 100644
--- a/services/mgmt/device/deviced/server.go
+++ b/services/mgmt/device/deviced/server.go
@@ -2,6 +2,8 @@
 
 import (
 	"flag"
+	"net"
+	"strconv"
 	"time"
 
 	"v.io/lib/cmdline"
@@ -11,8 +13,12 @@
 	_ "v.io/core/veyron/profiles/roaming"
 	"v.io/core/veyron/services/mgmt/device/config"
 	"v.io/core/veyron/services/mgmt/device/impl"
+	mounttable "v.io/core/veyron/services/mounttable/lib"
+
 	"v.io/core/veyron2"
+	"v.io/core/veyron2/ipc"
 	"v.io/core/veyron2/mgmt"
+	"v.io/core/veyron2/naming"
 	"v.io/core/veyron2/vlog"
 )
 
@@ -21,6 +27,8 @@
 	// config?
 	publishAs       = flag.String("name", "", "name to publish the device manager at")
 	restartExitCode = flag.Int("restart_exit_code", 0, "exit code to return when device manager should be restarted")
+	nhName          = flag.String("neighborhood_name", "", `if provided, it will enable sharing with the local neighborhood with the provided name. The address of the local mounttable will be published to the neighboorhood and everything in the neighborhood will be visible on the local mounttable.`)
+	dmPort          = flag.Int("deviced_port", 0, "the port number of assign to the device manager service. The hostname/IP address part of --veyron.tcp.address is used along with this port. By default, the port is assigned by the OS.")
 )
 
 func runServer(*cmdline.Command, []string) error {
@@ -37,6 +45,24 @@
 			vlog.Infof("TEST MODE")
 		}
 	}
+	ls := veyron2.GetListenSpec(ctx)
+	if testMode {
+		ls.Addrs = ipc.ListenAddrs{{"tcp", "127.0.0.1:0"}}
+		ls.Proxy = ""
+	}
+
+	var publishName string
+	if !testMode {
+		publishName = *publishAs
+	}
+	// TODO(rthellend): Figure out how to integrate the mounttable ACLs.
+	mtName, stop, err := mounttable.StartServers(ctx, ls, publishName, *nhName, "" /* ACL File */)
+	if err != nil {
+		vlog.Errorf("mounttable.StartServers failed: %v", err)
+		return err
+	}
+	defer stop()
+	vlog.VI(0).Infof("Local mounttable published as: %v", publishName)
 
 	server, err := veyron2.NewServer(ctx)
 	if err != nil {
@@ -44,8 +70,18 @@
 		return err
 	}
 	defer server.Stop()
-	ls := veyron2.GetListenSpec(ctx)
-	endpoints, err := server.Listen(ls)
+
+	// Bring up the device manager with the same address as the mounttable.
+	dmListenSpec := ls
+	for i, a := range ls.Addrs {
+		host, _, err := net.SplitHostPort(a.Address)
+		if err != nil {
+			vlog.Errorf("net.SplitHostPort(%v) failed: %v", err)
+			return err
+		}
+		dmListenSpec.Addrs[i].Address = net.JoinHostPort(host, strconv.Itoa(*dmPort))
+	}
+	endpoints, err := server.Listen(dmListenSpec)
 	if err != nil {
 		vlog.Errorf("Listen(%s) failed: %v", ls, err)
 		return err
@@ -63,20 +99,33 @@
 	// implementation detail).
 
 	var exitErr error
-	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, testMode, func() { exitErr = cmdline.ErrExitCode(*restartExitCode) })
+	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, mtName, testMode, func() { exitErr = cmdline.ErrExitCode(*restartExitCode) })
 	if err != nil {
 		vlog.Errorf("Failed to create dispatcher: %v", err)
 		return err
 	}
-	var publishName string
-	if testMode == false {
-		publishName = *publishAs
-	}
-	if err := server.ServeDispatcher(publishName, dispatcher); err != nil {
-		vlog.Errorf("Serve(%v) failed: %v", publishName, err)
+	dmPublishName := naming.Join(mtName, "devmgr")
+	if err := server.ServeDispatcher(dmPublishName, dispatcher); err != nil {
+		vlog.Errorf("Serve(%v) failed: %v", dmPublishName, err)
 		return err
 	}
-	vlog.VI(0).Infof("Device manager published as: %v", publishName)
+	vlog.VI(0).Infof("Device manager published as: %v", dmPublishName)
+
+	// Mount the global namespace in the local namespace as "global".
+	ns := veyron2.GetNamespace(ctx)
+	for _, root := range ns.Roots() {
+		go func(r string) {
+			for {
+				err := ns.Mount(ctx, naming.Join(mtName, "global"), r, 0 /* forever */, naming.ServesMountTableOpt(true))
+				if err == nil {
+					break
+				}
+				vlog.Infof("Failed to Mount global namespace: %v", err)
+				time.Sleep(time.Second)
+			}
+		}(root)
+	}
+
 	impl.InvokeCallback(ctx, name)
 
 	// Wait until shutdown.  Ignore duplicate signals (sent by agent and
diff --git a/services/mgmt/device/impl/app_service.go b/services/mgmt/device/impl/app_service.go
index 383ab09..8ef508c 100644
--- a/services/mgmt/device/impl/app_service.go
+++ b/services/mgmt/device/impl/app_service.go
@@ -207,6 +207,8 @@
 	// securityAgent holds state related to the security agent (nil if not
 	// using the agent).
 	securityAgent *securityAgentState
+	// mtAddress is the address of the local mounttable.
+	mtAddress string
 }
 
 func saveEnvelope(dir string, envelope *application.Envelope) error {
@@ -700,7 +702,7 @@
 	return instanceDir, instanceID, nil
 }
 
-func genCmd(instanceDir, helperPath, systemName string, nsRoots []string) (*exec.Cmd, error) {
+func genCmd(instanceDir, helperPath, systemName string, nsRoot string) (*exec.Cmd, error) {
 	versionLink := filepath.Join(instanceDir, "version")
 	versionDir, err := filepath.EvalSymlinks(versionLink)
 	if err != nil {
@@ -728,11 +730,9 @@
 		cmd.Args = append(cmd.Args, "--username", systemName, "--dryrun")
 	}
 
-	var nsRootEnvs []string
-	for i, r := range nsRoots {
-		nsRootEnvs = append(nsRootEnvs, fmt.Sprintf("%s%d=%s", consts.NamespaceRootPrefix, i, r))
-	}
-	cmd.Env = append(nsRootEnvs, envelope.Env...)
+	// Set the app's default namespace root to the local namespace.
+	cmd.Env = []string{consts.NamespaceRootPrefix + "=" + nsRoot}
+	cmd.Env = append(cmd.Env, envelope.Env...)
 	rootDir := filepath.Join(instanceDir, "root")
 	if err := mkdir(rootDir); err != nil {
 		return nil, err
@@ -861,12 +861,12 @@
 	return nil
 }
 
-func (i *appService) run(nsRoots []string, instanceDir, systemName string) error {
+func (i *appService) run(instanceDir, systemName string) error {
 	if err := transitionInstance(instanceDir, suspended, starting); err != nil {
 		return err
 	}
 
-	cmd, err := genCmd(instanceDir, i.config.Helper, systemName, nsRoots)
+	cmd, err := genCmd(instanceDir, i.config.Helper, systemName, i.mtAddress)
 	if err == nil {
 		err = i.startCmd(instanceDir, cmd)
 	}
@@ -894,7 +894,7 @@
 
 	// For now, use the namespace roots of the device manager runtime to
 	// pass to the app.
-	if err = i.run(veyron2.GetNamespace(call.Context()).Roots(), instanceDir, systemName); err != nil {
+	if err = i.run(instanceDir, systemName); err != nil {
 		// TODO(caprita): We should call cleanupDir here, but we don't
 		// in order to not lose the logs for the instance (so we can
 		// debug why run failed).  Clean this up.
@@ -938,7 +938,7 @@
 	if startSystemName != systemName {
 		return verror2.Make(verror2.NoAccess, call.Context(), "Not allowed to resume an application under a different system name.")
 	}
-	return i.run(veyron2.GetNamespace(call.Context()).Roots(), instanceDir, systemName)
+	return i.run(instanceDir, systemName)
 }
 
 func stopAppRemotely(ctx *context.T, appVON string) error {
@@ -1377,7 +1377,7 @@
 	} else {
 		debugInfo.StartSystemName = startSystemName
 	}
-	if cmd, err := genCmd(instanceDir, i.config.Helper, debugInfo.SystemName, veyron2.GetNamespace(ctx.Context()).Roots()); err != nil {
+	if cmd, err := genCmd(instanceDir, i.config.Helper, debugInfo.SystemName, i.mtAddress); err != nil {
 		return "", err
 	} else {
 		debugInfo.Cmd = cmd
diff --git a/services/mgmt/device/impl/dispatcher.go b/services/mgmt/device/impl/dispatcher.go
index 7dc317e..a26933e 100644
--- a/services/mgmt/device/impl/dispatcher.go
+++ b/services/mgmt/device/impl/dispatcher.go
@@ -50,6 +50,8 @@
 	uat       BlessingSystemAssociationStore
 	locks     *acls.Locks
 	principal security.Principal
+	// Namespace
+	mtAddress string // The address of the local mounttable.
 }
 
 var _ ipc.Dispatcher = (*dispatcher)(nil)
@@ -73,7 +75,7 @@
 )
 
 // NewDispatcher is the device manager dispatcher factory.
-func NewDispatcher(principal security.Principal, config *config.State, testMode bool, restartHandler func()) (ipc.Dispatcher, error) {
+func NewDispatcher(principal security.Principal, config *config.State, mtAddress string, testMode bool, restartHandler func()) (ipc.Dispatcher, error) {
 	if err := config.Validate(); err != nil {
 		return nil, fmt.Errorf("invalid config %v: %v", config, err)
 	}
@@ -91,6 +93,7 @@
 	if err != nil {
 		return nil, fmt.Errorf("cannot create persistent store for identity to system account associations: %v", err)
 	}
+
 	d := &dispatcher{
 		internal: &internalState{
 			callback:       newCallbackState(config.Name),
@@ -102,6 +105,7 @@
 		uat:       uat,
 		locks:     acls.NewLocks(),
 		principal: principal,
+		mtAddress: mtAddress,
 	}
 
 	tam, err := flag.TaggedACLMapFromFlag()
@@ -275,6 +279,7 @@
 			uat:           d.uat,
 			locks:         d.locks,
 			securityAgent: d.internal.securityAgent,
+			mtAddress:     d.mtAddress,
 		})
 		appSpecificAuthorizer, err := newAppSpecificAuthorizer(auth, d.config, components[1:])
 		if err != nil {
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index 9660a1b..1aee70a 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -54,6 +54,7 @@
 	libbinary "v.io/core/veyron/services/mgmt/lib/binary"
 	mgmttest "v.io/core/veyron/services/mgmt/lib/testutil"
 	suidhelper "v.io/core/veyron/services/mgmt/suidhelper/impl"
+	mounttable "v.io/core/veyron/services/mounttable/lib"
 )
 
 const (
@@ -168,9 +169,21 @@
 		}
 		configState.Root, configState.Helper, configState.Origin, configState.CurrentLink = args[0], args[1], args[2], args[3]
 	}
+
+	mtListenSpec := ipc.ListenSpec{Addrs: ipc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
+	mtName, stop, err := mounttable.StartServers(ctx, mtListenSpec, "", "", "" /* ACL File */)
+	if err != nil {
+		vlog.Errorf("mounttable.StartServers failed: %v", err)
+		return err
+	}
+	defer stop()
+	// TODO(rthellend): Wire up the local mounttable like the real device
+	// manager, i.e. mount the device manager and the apps on it, and mount
+	// the local mounttable in the global namespace.
+
 	blessings := fmt.Sprint(veyron2.GetPrincipal(ctx).BlessingStore().Default())
 	testMode := strings.HasSuffix(blessings, "/testdm")
-	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, testMode, func() { fmt.Println("restart handler") })
+	dispatcher, err := impl.NewDispatcher(veyron2.GetPrincipal(ctx), configState, mtName, testMode, func() { fmt.Println("restart handler") })
 	if err != nil {
 		vlog.Fatalf("Failed to create device manager dispatcher: %v", err)
 	}