blob: e33217841ad0cfc6011a54ea126798d3ade3ae40 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Functions to start services needed by the Vanadium playground.
// These should never trigger program exit.
// TODO(ivanpi): Use the modules library to start the services instead.
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strconv"
"time"
"v.io/x/playground/lib"
"v.io/x/ref/envvar"
)
var (
proxyName = "proxy"
)
// TODO(ivanpi): this is all implemented in veyron/lib/modules/core, you
// may be able to use that directly.
func makeServiceCmd(progName string, args ...string) *exec.Cmd {
return makeCmd(fmt.Sprintf("<%v>", progName), true, progName, progName, args...)
}
// startMount starts a mounttabled process, and sets the V23_NAMESPACE env
// variable to the mounttable's location. We run one mounttabled process for
// the entire environment.
func startMount(timeLimit time.Duration) (proc *os.Process, err error) {
cmd := makeServiceCmd("mounttabled", "-v23.tcp.address=127.0.0.1:0")
matches, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("NAME=(.*)"))
if err != nil {
return nil, fmt.Errorf("Error starting mounttabled: %v", err)
}
endpoint := matches[1]
if endpoint == "" {
return nil, fmt.Errorf("Failed to get mounttable endpoint")
}
return cmd.Process, os.Setenv(envvar.NamespacePrefix, endpoint)
}
// startProxy starts a proxyd process. We run one proxyd process for the
// entire environment.
func startProxy(timeLimit time.Duration) (proc *os.Process, err error) {
cmd := makeServiceCmd(
"proxyd",
"-log_dir=/tmp/logs",
"-name="+proxyName,
"-v23.tcp.address=127.0.0.1:0")
if _, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("NAME=(.*)")); err != nil {
return nil, fmt.Errorf("Error starting proxy: %v", err)
}
return cmd.Process, nil
}
// startWspr starts a wsprd process. We run one wsprd process for each
// javascript file being run.
func startWspr(fileName, credentials string, timeLimit time.Duration) (proc *os.Process, port int, err error) {
cmd := makeCmd("<wsprd>:"+fileName, true, credentials,
"wsprd",
"-v23.proxy="+proxyName,
"-v23.tcp.address=127.0.0.1:0",
"-port=0",
// The identd server won't be used, so pass a fake name.
"-identd=/unused")
parts, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile(".*port: (.*)"))
if err != nil {
return nil, 0, fmt.Errorf("Error starting wspr: %v", err)
}
portstr := parts[1]
port, err = strconv.Atoi(portstr)
if err != nil {
return nil, 0, fmt.Errorf("Malformed port: %q: %v", portstr, err)
}
return cmd.Process, port, nil
}
// Helper function to start a command and wait for output. Arguments are a cmd
// to run, a timeout, and a regexp. The slice of strings matched by the regexp
// is returned.
// TODO(nlacasse): Consider standardizing how services log when they start
// listening, and their endpoints (if any). Then this could become a common
// util function.
func startAndWaitFor(cmd *exec.Cmd, timeout time.Duration, outputRegexp *regexp.Regexp) ([]string, error) {
reader, writer := io.Pipe()
cmd.Stdout.(*lib.MultiWriter).Add(writer)
if err := cmd.Start(); err != nil {
return nil, err
}
ch := make(chan []string)
go (func() {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
if matches := outputRegexp.FindStringSubmatch(line); matches != nil {
ch <- matches
}
}
close(ch)
})()
select {
case <-time.After(timeout):
return nil, fmt.Errorf("Timeout starting service: %v", cmd.Path)
case matches := <-ch:
return matches, nil
}
}