veyron/lib/modules, veyron/tools/servicerunner: Start the test identityd
server in the servicerunner.

This CL adds a new module "test_identityd" for running the test identityd
server.

The servicerunner now starts the test identityd server.

Small cleanups to the servicerunner and wspr modules.

Change-Id: Ic066eefce3061541b98ddad914c1798248aa1f55
diff --git a/lib/modules/core/core.go b/lib/modules/core/core.go
index 0a49e3e..efd71cb 100644
--- a/lib/modules/core/core.go
+++ b/lib/modules/core/core.go
@@ -59,12 +59,13 @@
 	SleepCommand = "sleep"
 	TimeCommand  = "time"
 	// Subprocesses
-	EchoServerCommand  = "echoServer"
-	EchoClientCommand  = "echoClient"
-	RootMTCommand      = "root"
-	MTCommand          = "mt"
-	LSCommand          = "ls"
-	ProxyServerCommand = "proxyd"
-	WSPRCommand        = "wsprd"
-	ExecCommand        = "exec"
+	EchoServerCommand    = "echoServer"
+	EchoClientCommand    = "echoClient"
+	RootMTCommand        = "root"
+	MTCommand            = "mt"
+	LSCommand            = "ls"
+	ProxyServerCommand   = "proxyd"
+	WSPRCommand          = "wsprd"
+	TestIdentitydCommand = "test_identityd"
+	ExecCommand          = "exec"
 )
diff --git a/lib/modules/core/test_identityd.go b/lib/modules/core/test_identityd.go
new file mode 100644
index 0000000..9330214
--- /dev/null
+++ b/lib/modules/core/test_identityd.go
@@ -0,0 +1,108 @@
+package core
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"net"
+	"strconv"
+	"time"
+
+	"v.io/core/veyron2/rt"
+
+	"v.io/core/veyron/lib/flags"
+	"v.io/core/veyron/lib/modules"
+
+	"v.io/core/veyron/services/identity/auditor"
+	"v.io/core/veyron/services/identity/blesser"
+	"v.io/core/veyron/services/identity/caveats"
+	"v.io/core/veyron/services/identity/oauth"
+	"v.io/core/veyron/services/identity/revocation"
+	"v.io/core/veyron/services/identity/server"
+	"v.io/core/veyron/services/identity/util"
+)
+
+var (
+	ifs *flag.FlagSet = flag.NewFlagSet("test_identityd", flag.ContinueOnError)
+
+	googleDomain = ifs.String("google_domain", "", "An optional domain name. When set, only email addresses from this domain are allowed to authenticate via Google OAuth")
+	host         = ifs.String("host", "localhost", "Hostname the HTTP server listens on. This can be the name of the host running the webserver, but if running behind a NAT or load balancer, this should be the host name that clients will connect to. For example, if set to 'x.com', Veyron identities will have the IssuerName set to 'x.com' and clients can expect to find the root name and public key of the signer at 'x.com/blessing-root'.")
+	httpaddr     = ifs.String("httpaddr", "localhost:0", "Address on which the HTTP server listens on.")
+	tlsconfig    = ifs.String("tlsconfig", "", "Comma-separated list of TLS certificate and private key files. This must be provided.")
+
+	ifl *flags.Flags = flags.CreateAndRegister(ifs, flags.Listen)
+)
+
+func init() {
+	modules.RegisterChild(TestIdentitydCommand, usage(ifs), startTestIdentityd)
+}
+
+func startTestIdentityd(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	if err := parseFlags(ifl, args); err != nil {
+		return fmt.Errorf("failed to parse args: %s", err)
+	}
+
+	// Duration to use for tls cert and blessing duration.
+	duration := 365 * 24 * time.Hour
+
+	// If no tlsconfig has been provided, generate new cert and key and use them.
+	if ifs.Lookup("tlsconfig").Value.String() == "" {
+		certFile, keyFile, err := util.WriteCertAndKey(*host, duration)
+		if err != nil {
+			return fmt.Errorf("Could not write cert and key: %v", err)
+		}
+		if err := ifs.Set("tlsconfig", certFile+","+keyFile); err != nil {
+			return fmt.Errorf("Could not set tlsconfig: %v", err)
+		}
+	}
+
+	// Pick a free port if httpaddr flag is not set.
+	// We can't use :0 here, because the identity server calles
+	// http.ListenAndServeTLS, which block, leaving us with no way to tell
+	// what port the server is running on.  Hence, we must pass in an
+	// actual port so we know where the server is running.
+	if ifs.Lookup("httpaddr").Value.String() == ifs.Lookup("httpaddr").DefValue {
+		if err := ifs.Set("httpaddr", "localhost:"+freePort()); err != nil {
+			return fmt.Errorf("Could not set httpaddr: %v", err)
+		}
+	}
+
+	auditor, reader := auditor.NewMockBlessingAuditor()
+	revocationManager := revocation.NewMockRevocationManager()
+
+	googleParams := blesser.GoogleParams{
+		BlessingDuration:  duration,
+		DomainRestriction: *googleDomain,
+		RevocationManager: revocationManager,
+	}
+
+	s := server.NewIdentityServer(
+		oauth.NewMockOAuth(),
+		auditor,
+		reader,
+		revocationManager,
+		googleParams,
+		caveats.NewMockCaveatSelector())
+
+	l := initListenSpec(ifl)
+	r, err := rt.New()
+	if err != nil {
+		return fmt.Errorf("rt.New() failed: %v", err)
+	}
+	defer r.Cleanup()
+
+	ipcServer, veyronEPs, externalHttpaddress := s.Listen(r, &l, *host, *httpaddr, *tlsconfig)
+	defer ipcServer.Stop()
+
+	fmt.Fprintf(stdout, "TEST_IDENTITYD_ADDR=%s\n", veyronEPs[0])
+	fmt.Fprintf(stdout, "TEST_IDENTITYD_HTTP_ADDR=%s\n", externalHttpaddress)
+
+	modules.WaitForEOF(stdin)
+	return nil
+}
+
+func freePort() string {
+	l, _ := net.Listen("tcp", ":0")
+	defer l.Close()
+	return strconv.Itoa(l.Addr().(*net.TCPAddr).Port)
+}
diff --git a/lib/modules/core/util.go b/lib/modules/core/util.go
index c962a9c..dd0dcc7 100644
--- a/lib/modules/core/util.go
+++ b/lib/modules/core/util.go
@@ -3,6 +3,7 @@
 import (
 	"flag"
 	"fmt"
+	"strings"
 
 	"v.io/core/veyron2/ipc"
 
@@ -49,3 +50,19 @@
 	}
 	return nil
 }
+
+// usage generates a usage string based on the flags in a flagset.
+func usage(fs *flag.FlagSet) string {
+	res := []string{}
+	fs.VisitAll(func(f *flag.Flag) {
+		format := "  -%s=%s: %s"
+		if getter, ok := f.Value.(flag.Getter); ok {
+			if _, ok := getter.Get().(string); ok {
+				// put quotes on the value
+				format = "  -%s=%q: %s"
+			}
+		}
+		res = append(res, fmt.Sprintf(format, f.Name, f.DefValue, f.Usage))
+	})
+	return strings.Join(res, "\n") + "\n"
+}
diff --git a/lib/modules/core/wspr.go b/lib/modules/core/wspr.go
index 0569d11..c93ae58 100644
--- a/lib/modules/core/wspr.go
+++ b/lib/modules/core/wspr.go
@@ -4,7 +4,6 @@
 	"flag"
 	"fmt"
 	"io"
-	"strings"
 
 	"v.io/core/veyron/lib/flags"
 	"v.io/core/veyron/lib/modules"
@@ -24,30 +23,14 @@
 	fl *flags.Flags = flags.CreateAndRegister(fs, flags.Listen)
 )
 
-func usageWSPR() string {
-	res := []string{}
-	fs.VisitAll(func(f *flag.Flag) {
-		format := "  -%s=%s: %s"
-		if getter, ok := f.Value.(flag.Getter); ok {
-			if _, ok := getter.Get().(string); ok {
-				// put quotes on the value
-				format = "  -%s=%q: %s"
-			}
-		}
-		res = append(res, fmt.Sprintf(format, f.Name, f.DefValue, f.Usage))
-	})
-	return strings.Join(res, "\n") + "\n"
-}
-
 func init() {
-	modules.RegisterChild(WSPRCommand, usageWSPR(), startWSPR)
+	modules.RegisterChild(WSPRCommand, usage(fs), startWSPR)
 }
 
 func startWSPR(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	if err := parseFlags(fl, args); err != nil {
 		return fmt.Errorf("failed to parse args: %s", err)
 	}
-	args = fl.Args()
 
 	r, err := rt.New()
 	if err != nil {
diff --git a/tools/servicerunner/main.go b/tools/servicerunner/main.go
index edbc8ff..e615c2d 100644
--- a/tools/servicerunner/main.go
+++ b/tools/servicerunner/main.go
@@ -14,7 +14,7 @@
 	"v.io/core/veyron/lib/expect"
 	"v.io/core/veyron/lib/flags/consts"
 	"v.io/core/veyron/lib/modules"
-	_ "v.io/core/veyron/lib/modules/core"
+	"v.io/core/veyron/lib/modules/core"
 	_ "v.io/core/veyron/profiles"
 )
 
@@ -79,9 +79,9 @@
 	}
 	vars[consts.VeyronCredentials] = v
 
-	h, err := sh.Start("root", nil, "--", "--veyron.tcp.protocol=ws", "--veyron.tcp.address=127.0.0.1:0")
+	h, err := sh.Start(core.RootMTCommand, nil, "--", "--veyron.tcp.protocol=ws", "--veyron.tcp.address=127.0.0.1:0")
 	panicOnError(err)
-	updateVars(h, vars, "MT_NAME")
+	panicOnError(updateVars(h, vars, "MT_NAME"))
 
 	// Set consts.NamespaceRootPrefix env var, consumed downstream by proxyd
 	// among others.
@@ -91,13 +91,17 @@
 
 	// NOTE(sadovsky): The proxyd binary requires --protocol and --address flags
 	// while the proxyd command instead uses ListenSpec flags.
-	h, err = sh.Start("proxyd", nil, "--", "--veyron.tcp.protocol=ws", "--veyron.tcp.address=127.0.0.1:0", "test/proxy")
+	h, err = sh.Start(core.ProxyServerCommand, nil, "--", "--veyron.tcp.protocol=ws", "--veyron.tcp.address=127.0.0.1:0", "test/proxy")
 	panicOnError(err)
-	updateVars(h, vars, "PROXY_ADDR")
+	panicOnError(updateVars(h, vars, "PROXY_ADDR"))
 
-	h, err = sh.Start("wsprd", nil, "--", "--veyron.tcp.protocol=ws", "--veyron.tcp.address=127.0.0.1:0", "--veyron.proxy=test/proxy", "--identd=test/identd")
+	h, err = sh.Start(core.WSPRCommand, nil, "--", "--veyron.tcp.protocol=ws", "--veyron.tcp.address=127.0.0.1:0", "--veyron.proxy=test/proxy", "--identd=test/identd")
 	panicOnError(err)
-	updateVars(h, vars, "WSPR_ADDR")
+	panicOnError(updateVars(h, vars, "WSPR_ADDR"))
+
+	h, err = sh.Start(core.TestIdentitydCommand, nil, "--", "--veyron.tcp.protocol=ws", "--veyron.tcp.address=127.0.0.1:0", "--veyron.proxy=test/proxy", "--host=localhost", "--httpaddr=localhost:0")
+	panicOnError(err)
+	panicOnError(updateVars(h, vars, "TEST_IDENTITYD_ADDR", "TEST_IDENTITYD_HTTP_ADDR"))
 
 	bytes, err := json.Marshal(vars)
 	panicOnError(err)
diff --git a/tools/servicerunner/servicerunner_test.go b/tools/servicerunner/servicerunner_test.go
index 5a7b24f..57cd8f7 100644
--- a/tools/servicerunner/servicerunner_test.go
+++ b/tools/servicerunner/servicerunner_test.go
@@ -39,7 +39,15 @@
 	vars := map[string]string{}
 	check(t, json.Unmarshal(line, &vars))
 	fmt.Println(vars)
-	for _, name := range []string{"VEYRON_CREDENTIALS", "MT_NAME", "PROXY_ADDR", "WSPR_ADDR"} {
+	expectedVars := []string{
+		"VEYRON_CREDENTIALS",
+		"MT_NAME",
+		"PROXY_ADDR",
+		"WSPR_ADDR",
+		"TEST_IDENTITYD_ADDR",
+		"TEST_IDENTITYD_HTTP_ADDR",
+	}
+	for _, name := range expectedVars {
 		if _, ok := vars[name]; !ok {
 			t.Error("Missing", name)
 		}