blob: 0b8f174add5656354946eb8c9a236b0ec856e63d [file] [log] [blame]
// Compiles and runs code for the Veyron playground.
// Code is passed via os.Stdin as a JSON encoded
// Request struct.
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"go/parser"
"go/token"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"regexp"
"strings"
"syscall"
"time"
)
const RUN_TIMEOUT = time.Second
var debug = flag.Bool("v", false, "Verbose mode")
type CodeFile struct {
Name string
Body string
identity string
pkg string
executable bool
proc *exec.Cmd
}
type Identity struct {
Name string
Blesser string
Duration string
Files []string
}
// The input on STDIN should only contain Files. We look for a file
// whose Name ends with .id, and parse that into Identities.
//
// TODO(ribrdb): Consider moving identity parsing into the http server.
type Request struct {
Files []*CodeFile
Identities []Identity
}
type Exit struct {
name string
err error
}
func Log(args ...interface{}) {
if *debug {
log.Println(args...)
}
}
func MakeCmd(prog string, args ...string) *exec.Cmd {
Log("Running", prog, strings.Join(args, " "))
cmd := exec.Command(prog, args...)
// TODO(ribrdb): prefix output with the name of the binary
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = os.Environ()
return cmd
}
func ParseRequest(in io.Reader) (r Request, err error) {
Log("Parsing input")
data, err := ioutil.ReadAll(in)
if err == nil {
err = json.Unmarshal(data, &r)
}
m := make(map[string]*CodeFile)
for i := 0; i < len(r.Files); {
f := r.Files[i]
if path.Ext(f.Name) == ".id" {
err = json.Unmarshal([]byte(f.Body), &r.Identities)
if err != nil {
return
}
r.Files = append(r.Files[:i], r.Files[i+1:]...)
} else {
m[f.Name] = f
i++
}
}
if len(r.Identities) == 0 {
// Run everything with the same identity if none are specified.
r.Identities = append(r.Identities, Identity{Name: "default"})
for _, f := range r.Files {
f.identity = "default"
}
} else {
for _, identity := range r.Identities {
for _, name := range identity.Files {
m[name].identity = identity.Name
}
}
}
return
}
func main() {
flag.Parse()
r, err := ParseRequest(os.Stdin)
if err != nil {
log.Fatal(err)
}
CreateIdentities(r)
if err = StartMount(); err != nil {
log.Fatal(err)
}
CompileAndRun(r)
}
func CreateIdentities(r Request) error {
Log("Generating identities")
if err := os.MkdirAll("ids", 0777); err != nil {
return err
}
for _, id := range r.Identities {
if err := id.Create(); err != nil {
return err
}
}
return nil
}
func CompileAndRun(r Request) {
Log("Processing files")
exit := make(chan Exit)
running := 0
// TODO(ribrdb): Compile first, don't run anything if compilation fails.
for _, f := range r.Files {
var err error
if err = f.Write(); err != nil {
goto Error
}
if err = f.Compile(); err != nil {
goto Error
}
if f.executable {
go f.Run(exit)
running++
}
Error:
if err != nil {
log.Printf("%s: %v\n", f.Name, err)
}
}
timeout := time.After(RUN_TIMEOUT)
killed := false
for running > 0 {
select {
case <-timeout:
log.Fatal("Process executed too long.")
case status := <-exit:
if status.err == nil {
log.Printf("%s exited.", status.name)
} else {
log.Printf("%s exited with error %v", status.name, status.err)
}
running--
if !killed {
killed = true
for _, f := range r.Files {
if f.Name != status.name && f.proc != nil {
f.proc.Process.Signal(syscall.SIGTERM)
}
}
}
}
}
}
func (file *CodeFile) readPackage() error {
Log("Parsing package from ", file.Name)
f, err := parser.ParseFile(token.NewFileSet(), file.Name,
strings.NewReader(file.Body), parser.PackageClauseOnly)
if err != nil {
return err
}
file.pkg = f.Name.String()
if "main" == file.pkg {
file.executable = true
basename := path.Base(file.Name)
file.pkg = basename[:len(basename)-len(path.Ext(basename))]
}
return nil
}
func (f *CodeFile) Write() error {
if err := f.readPackage(); err != nil {
return err
}
if err := os.MkdirAll(path.Join("src", f.pkg), 0777); err != nil {
return err
}
Log("Writing file ", f.Name)
return ioutil.WriteFile(path.Join("src", f.pkg, f.Name), []byte(f.Body), 0666)
}
func (f *CodeFile) Compile() error {
var cmd *exec.Cmd
if path.Ext(f.Name) == ".vdl" {
cmd = MakeCmd("vdl", "generate", f.pkg)
} else {
cmd = MakeCmd("go", "install", f.pkg)
cmd.Dir = "/usr/local/veyron/veyron"
}
cmd.Stdout = cmd.Stderr
err := cmd.Run()
if err != nil {
f.executable = false
}
return err
}
func (f *CodeFile) Run(ch chan Exit) {
cmd := MakeCmd(path.Join("bin", f.pkg))
if f.identity != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("VEYRON_IDENTITY=%s", path.Join("ids", f.identity)))
}
f.proc = cmd
err := cmd.Run()
ch <- Exit{f.pkg, err}
}
func (id Identity) Create() error {
args := make([]string, 0, 10)
args = append(args, "--name", id.Name)
if id.Blesser != "" {
args = append(args, "--blesser", path.Join("ids", id.Blesser))
}
if id.Duration != "" {
args = append(args, "--duration", id.Duration)
}
cmd := MakeCmd("identity", args...)
out, err := os.Create(path.Join("ids", id.Name))
if err != nil {
return err
}
defer out.Close()
cmd.Stdout = out
return cmd.Run()
}
func StartMount() (err error) {
reader, writer := io.Pipe()
cmd := MakeCmd("mounttabled")
cmd.Stdout = writer
cmd.Stderr = cmd.Stdout
err = cmd.Start()
if err != nil {
return err
}
buf := bufio.NewReader(reader)
pat := regexp.MustCompile("Mount table .+ endpoint: (.+)\n")
timeout := time.After(RUN_TIMEOUT)
ch := make(chan string)
go (func() {
for line, err := buf.ReadString('\n'); err == nil; line, err = buf.ReadString('\n') {
if groups := pat.FindStringSubmatch(line); groups != nil {
ch <- groups[1]
} else {
Log("mounttabld: %s", line)
}
}
close(ch)
})()
select {
case <-timeout:
log.Fatal("Timeout starting mounttabled")
case endpoint := <-ch:
if endpoint == "" {
log.Fatal("mounttable died")
}
Log("mount at ", endpoint)
return os.Setenv("NAMESPACE_ROOT", endpoint)
}
return err
}