shad-go/distbuild/pkg/dist/coordinator.go

160 lines
3.5 KiB
Go
Raw Normal View History

2020-03-11 22:46:45 +00:00
package dist
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
2020-03-14 10:24:44 +00:00
"sync"
"time"
2020-03-11 22:46:45 +00:00
"go.uber.org/zap"
2020-03-29 16:03:07 +00:00
"gitlab.com/slon/shad-go/distbuild/pkg/api"
2020-03-11 22:46:45 +00:00
"gitlab.com/slon/shad-go/distbuild/pkg/build"
"gitlab.com/slon/shad-go/distbuild/pkg/filecache"
2020-03-28 21:34:09 +00:00
"gitlab.com/slon/shad-go/distbuild/pkg/scheduler"
2020-03-11 22:46:45 +00:00
)
type Coordinator struct {
log *zap.Logger
mux *http.ServeMux
fileCache *filecache.Cache
2020-03-14 10:24:44 +00:00
2020-03-28 21:34:09 +00:00
mu sync.Mutex
builds map[build.ID]*Build
scheduler *scheduler.Scheduler
}
var defaultConfig = scheduler.Config{
CacheTimeout: time.Millisecond * 10,
DepsTimeout: time.Millisecond * 100,
2020-03-11 22:46:45 +00:00
}
func NewCoordinator(
log *zap.Logger,
fileCache *filecache.Cache,
) *Coordinator {
c := &Coordinator{
log: log,
mux: http.NewServeMux(),
fileCache: fileCache,
2020-03-14 10:24:44 +00:00
2020-03-28 21:34:09 +00:00
builds: make(map[build.ID]*Build),
scheduler: scheduler.NewScheduler(log, defaultConfig),
2020-03-11 22:46:45 +00:00
}
c.mux.HandleFunc("/build", c.Build)
2020-03-28 21:34:09 +00:00
c.mux.HandleFunc("/signal", c.Signal)
2020-03-14 10:24:44 +00:00
c.mux.HandleFunc("/heartbeat", c.Heartbeat)
2020-03-11 22:46:45 +00:00
return c
}
func (c *Coordinator) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.mux.ServeHTTP(w, r)
}
func (c *Coordinator) doBuild(w http.ResponseWriter, r *http.Request) error {
graphJS, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
var g build.Graph
if err := json.Unmarshal(graphJS, &g); err != nil {
return err
}
w.WriteHeader(http.StatusOK)
enc := json.NewEncoder(w)
2020-03-29 16:03:07 +00:00
if err := enc.Encode(api.BuildStarted{}); err != nil {
2020-03-11 22:46:45 +00:00
return err
}
2020-03-14 10:24:44 +00:00
for _, job := range g.Jobs {
job := job
2020-03-28 21:34:09 +00:00
s := c.scheduler.ScheduleJob(&job)
select {
case <-r.Context().Done():
return r.Context().Err()
case <-s.Finished:
}
2020-03-14 10:24:44 +00:00
c.log.Debug("job finished", zap.String("job_id", job.ID.String()))
2020-03-29 16:03:07 +00:00
update := api.StatusUpdate{JobFinished: s.Result}
2020-03-14 10:24:44 +00:00
if err := enc.Encode(update); err != nil {
return err
}
}
2020-03-29 16:03:07 +00:00
update := api.StatusUpdate{BuildFinished: &api.BuildFinished{}}
2020-03-14 10:24:44 +00:00
return enc.Encode(update)
2020-03-11 22:46:45 +00:00
}
2020-03-28 21:34:09 +00:00
func (c *Coordinator) Signal(w http.ResponseWriter, r *http.Request) {
c.log.Debug("build signal started")
if err := c.doHeartbeat(w, r); err != nil {
c.log.Error("build signal failed", zap.Error(err))
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(err.Error()))
return
}
c.log.Debug("build signal finished")
}
2020-03-11 22:46:45 +00:00
func (c *Coordinator) Build(w http.ResponseWriter, r *http.Request) {
if err := c.doBuild(w, r); err != nil {
c.log.Error("build failed", zap.Error(err))
2020-03-29 16:03:07 +00:00
errorUpdate := api.StatusUpdate{BuildFailed: &api.BuildFailed{Error: err.Error()}}
2020-03-11 22:46:45 +00:00
errorJS, _ := json.Marshal(errorUpdate)
_, _ = w.Write(errorJS)
}
}
2020-03-14 10:24:44 +00:00
func (c *Coordinator) doHeartbeat(w http.ResponseWriter, r *http.Request) error {
2020-03-29 16:03:07 +00:00
var req api.HeartbeatRequest
2020-03-14 10:24:44 +00:00
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return fmt.Errorf("invalid request: %w", err)
}
2020-03-28 21:34:09 +00:00
c.scheduler.RegisterWorker(req.WorkerID)
2020-03-14 10:24:44 +00:00
for _, job := range req.FinishedJob {
job := job
2020-03-28 21:34:09 +00:00
c.scheduler.OnJobComplete(req.WorkerID, job.ID, &job)
2020-03-14 10:24:44 +00:00
}
2020-03-29 16:03:07 +00:00
rsp := api.HeartbeatResponse{
JobsToRun: map[build.ID]api.JobSpec{},
2020-03-28 21:34:09 +00:00
}
2020-03-14 10:24:44 +00:00
2020-03-28 21:34:09 +00:00
job := c.scheduler.PickJob(req.WorkerID, r.Context().Done())
if job != nil {
2020-03-29 16:03:07 +00:00
rsp.JobsToRun[job.Job.ID] = api.JobSpec{Job: *job.Job}
2020-03-14 10:24:44 +00:00
}
if err := json.NewEncoder(w).Encode(rsp); err != nil {
return err
}
return nil
}
func (c *Coordinator) Heartbeat(w http.ResponseWriter, r *http.Request) {
c.log.Debug("heartbeat started")
if err := c.doHeartbeat(w, r); err != nil {
c.log.Error("heartbeat failed", zap.Error(err))
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(err.Error()))
return
}
c.log.Debug("heartbeat finished")
}