Manager: consult the sleep schedule on worker sign-on

If there is no status change queued for the Worker, the sleep schedule
should determine its initial status.
This commit is contained in:
Sybren A. Stüvel 2022-07-18 18:25:24 +02:00
parent bc725ea7dc
commit bfd6746f78
7 changed files with 57 additions and 9 deletions

View File

@ -208,6 +208,7 @@ var _ TimeService = (clock.Clock)(nil)
type WorkerSleepScheduler interface {
FetchSchedule(ctx context.Context, workerUUID string) (*persistence.SleepSchedule, error)
SetSchedule(ctx context.Context, workerUUID string, schedule *persistence.SleepSchedule) error
WorkerStatus(ctx context.Context, workerUUID string) (api.WorkerStatus, error)
}
var _ WorkerSleepScheduler = (*sleep_scheduler.SleepScheduler)(nil)

View File

@ -1139,3 +1139,18 @@ func (mr *MockWorkerSleepSchedulerMockRecorder) SetSchedule(arg0, arg1, arg2 int
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSchedule", reflect.TypeOf((*MockWorkerSleepScheduler)(nil).SetSchedule), arg0, arg1, arg2)
}
// WorkerStatus mocks base method.
func (m *MockWorkerSleepScheduler) WorkerStatus(arg0 context.Context, arg1 string) (api.WorkerStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "WorkerStatus", arg0, arg1)
ret0, _ := ret[0].(api.WorkerStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// WorkerStatus indicates an expected call of WorkerStatus.
func (mr *MockWorkerSleepSchedulerMockRecorder) WorkerStatus(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkerStatus", reflect.TypeOf((*MockWorkerSleepScheduler)(nil).WorkerStatus), arg0, arg1)
}

View File

@ -79,27 +79,38 @@ func (f *Flamenco) SignOn(e echo.Context) error {
return sendAPIError(e, http.StatusBadRequest, "invalid format")
}
logger.Info().Msg("worker signing on")
w, prevStatus, err := f.workerUpdateAfterSignOn(e, req)
if err != nil {
return sendAPIError(e, http.StatusInternalServerError, "error storing worker in database")
}
// Broadcast the status change.
// Broadcast the status change to 'starting'.
update := webupdates.NewWorkerUpdate(w)
if prevStatus != "" {
update.PreviousStatus = &prevStatus
}
f.broadcaster.BroadcastWorkerUpdate(update)
resp := api.WorkerStateChange{}
if w.StatusRequested != "" {
resp.StatusRequested = w.StatusRequested
} else {
resp.StatusRequested = api.WorkerStatusAwake
// Get the status the Worker should go to after starting up.
ctx := e.Request().Context()
initialStatus, err := f.workerInitialStatus(ctx, w)
if err != nil {
return sendAPIError(e, http.StatusInternalServerError, "error figuring out your initial status: %v", err)
}
return e.JSON(http.StatusOK, resp)
logger.Info().Str("initialStatus", string(initialStatus)).Msg("worker signing on")
return e.JSON(http.StatusOK, api.WorkerStateChange{
StatusRequested: initialStatus,
})
}
// workerInitialStatus returns the status the worker should go to after starting up.
func (f *Flamenco) workerInitialStatus(ctx context.Context, w *persistence.Worker) (api.WorkerStatus, error) {
if w.StatusRequested != "" {
return w.StatusRequested, nil
}
return f.sleepScheduler.WorkerStatus(ctx, w.UUID)
}
func (f *Flamenco) workerUpdateAfterSignOn(e echo.Context, update api.SignOnJSONBody) (*persistence.Worker, api.WorkerStatus, error) {

View File

@ -168,6 +168,9 @@ func TestWorkerSignOn(t *testing.T) {
worker.Status = api.WorkerStatusOffline
prevStatus := worker.Status
mf.sleepScheduler.EXPECT().WorkerStatus(gomock.Any(), worker.UUID).
Return(api.WorkerStatusAsleep, nil)
mf.broadcaster.EXPECT().BroadcastWorkerUpdate(api.SocketIOWorkerUpdate{
Id: worker.UUID,
Name: "Lazy Boi",
@ -190,7 +193,7 @@ func TestWorkerSignOn(t *testing.T) {
assert.NoError(t, err)
assertResponseJSON(t, echo, http.StatusOK, api.WorkerStateChange{
StatusRequested: api.WorkerStatusAwake,
StatusRequested: api.WorkerStatusAsleep,
})
}

View File

@ -12,6 +12,11 @@ import (
// scheduledWorkerStatus returns the expected worker status at the given date/time.
func scheduledWorkerStatus(now time.Time, sched *persistence.SleepSchedule) api.WorkerStatus {
if sched == nil {
// If there is no schedule at all, the worker should be awake.
return api.WorkerStatusAwake
}
tod := persistence.MakeTimeOfDay(now)
if !sched.IsActive {

View File

@ -52,6 +52,9 @@ func TestScheduledWorkerStatus(t *testing.T) {
var sched persistence.SleepSchedule
empty := persistence.EmptyTimeOfDay()
// No schedule means 'awake'.
assert.Equal(t, api.WorkerStatusAwake, scheduledWorkerStatus(mocks.todayAt(11, 16), nil))
// Below, S, N, and E respectively mean Start, Now, and End times.
// Their order shows their relation to "Now". Lower-case letters mean "no value".
// Note that N can never be before 's' or after 'e'.

View File

@ -75,6 +75,16 @@ func (ss *SleepScheduler) SetSchedule(ctx context.Context, workerUUID string, sc
return ss.ApplySleepSchedule(ctx, schedule)
}
// WorkerStatus returns the status the worker should be in right now, according to its schedule.
// If the worker has no schedule active, returns 'awake'.
func (ss *SleepScheduler) WorkerStatus(ctx context.Context, workerUUID string) (api.WorkerStatus, error) {
schedule, err := ss.persist.FetchWorkerSleepSchedule(ctx, workerUUID)
if err != nil {
return "", err
}
return ss.scheduledWorkerStatus(schedule), nil
}
// scheduledWorkerStatus returns the expected worker status for the current date/time.
func (ss *SleepScheduler) scheduledWorkerStatus(sched *persistence.SleepSchedule) api.WorkerStatus {
now := ss.clock.Now()