veyron/services/mgmt/device: add local proxy service to device manager

This is a (temporary) crutch to bootstrap the GCE instance: before we can
install the proxy server app, we need install-local to work, and install-local
needs a proxy to be able to serve application and binary services.

Change-Id: Ib6e21bce4b3977fd8c7efe16707dd8391e96fdd9
diff --git a/services/mgmt/device/deviced/server.go b/services/mgmt/device/deviced/server.go
index ed0aed2..4ab39c9 100644
--- a/services/mgmt/device/deviced/server.go
+++ b/services/mgmt/device/deviced/server.go
@@ -32,6 +32,7 @@
 	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.")
+	proxyPort       = flag.Int("proxy_port", 0, "the port number to assign to the proxy service. 0 means no proxy service.")
 	usePairingToken = flag.Bool("use_pairing_token", false, "generate a pairing token for the device manager that will need to be provided when a device is claimed")
 )
 
@@ -90,16 +91,17 @@
 		RestartCallback: func() { exitErr = cmdline.ErrExitCode(*restartExitCode) },
 		PairingToken:    pairingToken,
 	}
-	if dev.ListenSpec, err = newDeviceListenSpec(ns.ListenSpec, *dmPort); err != nil {
+	if dev.ListenSpec, err = derivedListenSpec(ns.ListenSpec, *dmPort); err != nil {
 		return err
 	}
+	proxy := starter.ProxyArgs{Port: *proxyPort}
 	// We grab the shutdown channel at this point in order to ensure that we
 	// register a listener for the app cycle manager Stop before we start
 	// running the device manager service.  Otherwise, any device manager
 	// method that calls Stop on the app cycle manager (e.g. the Stop RPC)
 	// will precipitate an immediate process exit.
 	shutdownChan := signals.ShutdownOnSignals(ctx)
-	stop, err := starter.Start(ctx, starter.Args{Namespace: ns, Device: dev, MountGlobalNamespaceInLocalNamespace: true})
+	stop, err := starter.Start(ctx, starter.Args{Namespace: ns, Device: dev, MountGlobalNamespaceInLocalNamespace: true, Proxy: proxy})
 	if err != nil {
 		return err
 	}
@@ -112,8 +114,8 @@
 	return exitErr
 }
 
-// newDeviceListenSpec returns a copy of ls, with the ports changed to port.
-func newDeviceListenSpec(ls ipc.ListenSpec, port int) (ipc.ListenSpec, error) {
+// derivedListenSpec returns a copy of ls, with the ports changed to port.
+func derivedListenSpec(ls ipc.ListenSpec, port int) (ipc.ListenSpec, error) {
 	orig := ls.Addrs
 	ls.Addrs = nil
 	for _, a := range orig {
diff --git a/services/mgmt/device/starter/starter.go b/services/mgmt/device/starter/starter.go
index a6dea2e..fdfeeee 100644
--- a/services/mgmt/device/starter/starter.go
+++ b/services/mgmt/device/starter/starter.go
@@ -5,10 +5,14 @@
 import (
 	"encoding/base64"
 	"fmt"
+	"net"
 	"os"
 	"path/filepath"
+	"strconv"
 	"time"
 
+	"v.io/core/veyron/lib/netstate"
+	"v.io/core/veyron/runtimes/google/ipc/stream/proxy"
 	"v.io/core/veyron/services/mgmt/device/config"
 	"v.io/core/veyron/services/mgmt/device/impl"
 	mounttable "v.io/core/veyron/services/mounttable/lib"
@@ -46,9 +50,14 @@
 	return naming.Join(mt, "devmgr")
 }
 
+type ProxyArgs struct {
+	Port int
+}
+
 type Args struct {
 	Namespace NamespaceArgs
 	Device    DeviceArgs
+	Proxy     ProxyArgs
 
 	// If true, the global namespace will be made available on the
 	// mounttable server under "global/".
@@ -161,23 +170,76 @@
 
 func startClaimedDevice(ctx *context.T, args Args) (func(), error) {
 	mtName, stopMT, err := startMounttable(ctx, args.Namespace)
+	if err != nil {
+		vlog.Errorf("Failed to start mounttable service: %v", err)
+		return nil, err
+	}
+	// TODO(caprita): We link in a proxy server into the device manager so
+	// that we can bootstrap with install-local before we can install an
+	// actual proxy app.  Once support is added to the IPC layer to allow
+	// install-local to serve on the same connection it established to the
+	// device manager (see TODO in
+	// veyron/tools/mgmt/device/impl/local_install.go), we can get rid of
+	// this local proxy altogether.
+	stopProxy, err := startProxyServer(ctx, args.Proxy, mtName)
+	if err != nil {
+		vlog.Errorf("Failed to start proxy service: %v", err)
+		stopMT()
+		return nil, err
+	}
 	stopDevice, err := startDeviceServer(ctx, args.Device, mtName)
 	if err != nil {
-		stopMT()
 		vlog.Errorf("Failed to start device service: %v", err)
+		stopProxy()
+		stopMT()
 		return nil, err
 	}
 	if args.MountGlobalNamespaceInLocalNamespace {
 		mountGlobalNamespaceInLocalNamespace(ctx, mtName)
 	}
+
 	impl.InvokeCallback(ctx, args.Device.ConfigState.Name)
 
 	return func() {
 		stopDevice()
+		stopProxy()
 		stopMT()
 	}, nil
 }
 
+func startProxyServer(ctx *context.T, p ProxyArgs, localMT string) (func(), error) {
+	switch port := p.Port; {
+	case port == 0:
+		return func() {}, nil
+	case port < 0:
+		return nil, fmt.Errorf("invalid port: %v", port)
+	}
+	port := strconv.Itoa(p.Port)
+	rid, err := naming.NewRoutingID()
+	if err != nil {
+		return nil, fmt.Errorf("Failed to get new routing id: %v", err)
+	}
+	protocol, addr := "tcp", net.JoinHostPort("", port)
+	// Attempt to get a publicly accessible address for the proxy to publish
+	// under.
+	var publishAddr string
+	ls := veyron2.GetListenSpec(ctx)
+	if addrs, err := netstate.GetAccessibleIPs(); err == nil {
+		if ac := ls.AddressChooser; ac != nil {
+			if a, err := ac(protocol, addrs); err == nil && len(a) > 0 {
+				addrs = a
+			}
+		}
+		publishAddr = net.JoinHostPort(addrs[0].Address().String(), port)
+	}
+	proxy, err := proxy.New(rid, veyron2.GetPrincipal(ctx), protocol, addr, publishAddr)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to create proxy: %v", err)
+	}
+	vlog.Infof("Local proxy (%v)", proxy.Endpoint().Name())
+	return proxy.Shutdown, nil
+}
+
 func startMounttable(ctx *context.T, n NamespaceArgs) (string, func(), error) {
 	mtName, stopMT, err := mounttable.StartServers(ctx, n.ListenSpec, n.Name, n.Neighborhood, n.ACLFile)
 	if err != nil {
diff --git a/tools/mgmt/device/impl/local_install.go b/tools/mgmt/device/impl/local_install.go
index 68ad9d2..799ec92 100644
--- a/tools/mgmt/device/impl/local_install.go
+++ b/tools/mgmt/device/impl/local_install.go
@@ -21,6 +21,7 @@
 	"v.io/core/veyron2/services/mgmt/repository"
 	"v.io/core/veyron2/services/security/access"
 	"v.io/core/veyron2/uniqueid"
+	"v.io/core/veyron2/vlog"
 
 	pkglib "v.io/core/veyron/services/mgmt/lib/packages"
 	"v.io/lib/cmdline"
@@ -97,6 +98,7 @@
 	if err := server.ServeDispatcher(name, dispatcher); err != nil {
 		return nil, nil, err
 	}
+	vlog.VI(1).Infof("Server listening on %v (%v)", endpoints, name)
 	cleanup := func() {
 		if err := server.Stop(); err != nil {
 			fmt.Fprintf(stderr, "server.Stop failed: %v", err)
@@ -292,6 +294,7 @@
 		if err != nil {
 			return err
 		}
+		vlog.VI(1).Infof("package %v serving as %v", pname, oname)
 		envelope.Packages[pname] = application.SignedFile{File: oname}
 	}
 	packagesRewritten := application.Packages{}
@@ -300,6 +303,7 @@
 		if err != nil {
 			return err
 		}
+		vlog.VI(1).Infof("package %v serving as %v", pname, oname)
 		pspec.File = oname
 		packagesRewritten[pname] = pspec
 	}
@@ -307,6 +311,7 @@
 	if err != nil {
 		return err
 	}
+	vlog.VI(1).Infof("application serving envelope as %v", appName)
 	appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride), packagesRewritten)
 	// Reset the value for any future invocations of "install" or
 	// "install-local" (we run more than one command per process in unit