// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // The coordinator runs on GCE and coordinates builds in Docker containers. package main import ( "bytes" "crypto/hmac" "crypto/md5" "encoding/json" "flag" "fmt" "io" "io/ioutil" "log" "net/http" "os" "os/exec" "sort" "strings" "sync" "time" ) var ( masterKeyFile = flag.String("masterkey", "", "Path to builder master key. Else fetched using GCE project attribute 'builder-master-key'.") maxBuilds = flag.Int("maxbuilds", 6, "Max concurrent builds") // Debug flags: addTemp = flag.Bool("temp", false, "Append -temp to all builders.") just = flag.String("just", "", "If non-empty, run single build in the foreground. Requires rev.") rev = flag.String("rev", "", "Revision to build.") ) var ( startTime = time.Now() builders = map[string]buildConfig{} // populated once at startup donec = make(chan builderRev) // reports of finished builders statusMu sync.Mutex status = map[builderRev]*buildStatus{} ) type imageInfo struct { url string // of tar file mu sync.Mutex lastMod string } var images = map[string]*imageInfo{ "gobuilders/linux-x86-base": {url: "https://storage.googleapis.com/go-builder-data/docker-linux.base.tar.gz"}, "gobuilders/linux-x86-clang": {url: "https://storage.googleapis.com/go-builder-data/docker-linux.clang.tar.gz"}, "gobuilders/linux-x86-gccgo": {url: "https://storage.googleapis.com/go-builder-data/docker-linux.gccgo.tar.gz"}, "gobuilders/linux-x86-nacl": {url: "https://storage.googleapis.com/go-builder-data/docker-linux.nacl.tar.gz"}, "gobuilders/linux-x86-sid": {url: "https://storage.googleapis.com/go-builder-data/docker-linux.sid.tar.gz"}, } type buildConfig struct { name string // "linux-amd64-race" image string // Docker image to use to build cmd string // optional -cmd flag (relative to go/src/) env []string // extra environment ("key=value") pairs dashURL string // url of the build dashboard tool string // the tool this configuration is for } func main() { flag.Parse() addBuilder(buildConfig{name: "linux-386"}) addBuilder(buildConfig{name: "linux-386-387", env: []string{"GO386=387"}}) addBuilder(buildConfig{name: "linux-amd64"}) addBuilder(buildConfig{name: "linux-amd64-nocgo", env: []string{"CGO_ENABLED=0", "USER=root"}}) addBuilder(buildConfig{name: "linux-amd64-noopt", env: []string{"GO_GCFLAGS=-N -l"}}) addBuilder(buildConfig{name: "linux-amd64-race"}) addBuilder(buildConfig{name: "nacl-386"}) addBuilder(buildConfig{name: "nacl-amd64p32"}) addBuilder(buildConfig{ name: "linux-amd64-gccgo", image: "gobuilders/linux-x86-gccgo", cmd: "make RUNTESTFLAGS=\"--target_board=unix/-m64\" check-go -j16", dashURL: "https://build.golang.org/gccgo", tool: "gccgo", }) addBuilder(buildConfig{ name: "linux-386-gccgo", image: "gobuilders/linux-x86-gccgo", cmd: "make RUNTESTFLAGS=\"--target_board=unix/-m32\" check-go -j16", dashURL: "https://build.golang.org/gccgo", tool: "gccgo", }) addBuilder(buildConfig{name: "linux-386-sid", image: "gobuilders/linux-x86-sid"}) addBuilder(buildConfig{name: "linux-amd64-sid", image: "gobuilders/linux-x86-sid"}) addBuilder(buildConfig{name: "linux-386-clang", image: "gobuilders/linux-x86-clang"}) addBuilder(buildConfig{name: "linux-amd64-clang", image: "gobuilders/linux-x86-clang"}) if (*just != "") != (*rev != "") { log.Fatalf("--just and --rev must be used together") } if *just != "" { conf, ok := builders[*just] if !ok { log.Fatalf("unknown builder %q", *just) } cmd := exec.Command("docker", append([]string{"run"}, conf.dockerRunArgs(*rev)...)...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { log.Fatalf("Build failed: %v", err) } return } http.HandleFunc("/", handleStatus) http.HandleFunc("/logs", handleLogs) go http.ListenAndServe(":80", nil) workc := make(chan builderRev) for name, builder := range builders { go findWorkLoop(name, builder.dashURL, workc) } ticker := time.NewTicker(1 * time.Minute) for { select { case work := <-workc: log.Printf("workc received %+v; len(status) = %v, maxBuilds = %v; cur = %p", work, len(status), *maxBuilds, status[work]) mayBuild := mayBuildRev(work) if mayBuild { out, _ := exec.Command("docker", "ps").Output() numBuilds := bytes.Count(out, []byte("\n")) - 1 log.Printf("num current docker builds: %d", numBuilds) if numBuilds > *maxBuilds { mayBuild = false } } if mayBuild { if st, err := startBuilding(builders[work.name], work.rev); err == nil { setStatus(work, st) log.Printf("%v now building in %v", work, st.container) } else { log.Printf("Error starting to build %v: %v", work, err) } } case done := <-donec: log.Printf("%v done", done) setStatus(done, nil) case <-ticker.C: if numCurrentBuilds() == 0 && time.Now().After(startTime.Add(10*time.Minute)) { // TODO: halt the whole machine to kill the VM or something } } } } func numCurrentBuilds() int { statusMu.Lock() defer statusMu.Unlock() return len(status) } func mayBuildRev(work builderRev) bool { statusMu.Lock() defer statusMu.Unlock() return len(status) < *maxBuilds && status[work] == nil } func setStatus(work builderRev, st *buildStatus) { statusMu.Lock() defer statusMu.Unlock() if st == nil { delete(status, work) } else { status[work] = st } } func getStatus(work builderRev) *buildStatus { statusMu.Lock() defer statusMu.Unlock() return status[work] } type byAge []*buildStatus func (s byAge) Len() int { return len(s) } func (s byAge) Less(i, j int) bool { return s[i].start.Before(s[j].start) } func (s byAge) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func handleStatus(w http.ResponseWriter, r *http.Request) { var active []*buildStatus statusMu.Lock() for _, st := range status { active = append(active, st) } statusMu.Unlock() fmt.Fprintf(w, "
", len(status), *maxBuilds) sort.Sort(byAge(active)) for _, st := range active { fmt.Fprintf(w, "%-22s hg %s in container %s, %v ago\n", st.name, st.rev, st.name, st.rev, st.container, time.Now().Sub(st.start)) } fmt.Fprintf(w, "") } func handleLogs(w http.ResponseWriter, r *http.Request) { st := getStatus(builderRev{r.FormValue("name"), r.FormValue("rev")}) if st == nil { fmt.Fprintf(w, "