2020-03-11 22:46:45 +00:00
|
|
|
package dist
|
|
|
|
|
|
|
|
import (
|
2020-03-29 16:24:18 +00:00
|
|
|
"context"
|
2020-03-11 22:46:45 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"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
|
|
|
}
|
|
|
|
|
2020-03-29 16:24:18 +00:00
|
|
|
apiHandler := api.NewServiceHandler(log, c)
|
|
|
|
apiHandler.Register(c.mux)
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-03-29 16:24:18 +00:00
|
|
|
func (c *Coordinator) StartBuild(ctx context.Context, req *api.BuildRequest, w api.StatusWriter) error {
|
|
|
|
if err := w.Started(&api.BuildStarted{}); err != nil {
|
2020-03-11 22:46:45 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-03-29 16:24:18 +00:00
|
|
|
for _, job := range req.Graph.Jobs {
|
2020-03-14 10:24:44 +00:00
|
|
|
job := job
|
|
|
|
|
2020-03-28 21:34:09 +00:00
|
|
|
s := c.scheduler.ScheduleJob(&job)
|
|
|
|
|
|
|
|
select {
|
2020-03-29 16:24:18 +00:00
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
2020-03-28 21:34:09 +00:00
|
|
|
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:24:18 +00:00
|
|
|
jobFinished := api.StatusUpdate{JobFinished: s.Result}
|
|
|
|
if err := w.Updated(&jobFinished); err != nil {
|
2020-03-14 10:24:44 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-29 16:24:18 +00:00
|
|
|
finished := api.StatusUpdate{BuildFinished: &api.BuildFinished{}}
|
|
|
|
return w.Updated(&finished)
|
2020-03-11 22:46:45 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 16:24:18 +00:00
|
|
|
func (c *Coordinator) SignalBuild(ctx context.Context, buildID build.ID, signal *api.SignalRequest) (*api.SignalResponse, error) {
|
|
|
|
panic("implement me")
|
2020-03-11 22:46:45 +00:00
|
|
|
}
|
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")
|
|
|
|
}
|