diff --git a/internal/manager/api_impl/interfaces.go b/internal/manager/api_impl/interfaces.go index a8573297..5bb6754c 100644 --- a/internal/manager/api_impl/interfaces.go +++ b/internal/manager/api_impl/interfaces.go @@ -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) diff --git a/internal/manager/api_impl/mocks/api_impl_mock.gen.go b/internal/manager/api_impl/mocks/api_impl_mock.gen.go index 23b905ec..ad493e0b 100644 --- a/internal/manager/api_impl/mocks/api_impl_mock.gen.go +++ b/internal/manager/api_impl/mocks/api_impl_mock.gen.go @@ -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) +} diff --git a/internal/manager/api_impl/workers.go b/internal/manager/api_impl/workers.go index 2a002e40..650e9b29 100644 --- a/internal/manager/api_impl/workers.go +++ b/internal/manager/api_impl/workers.go @@ -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) { diff --git a/internal/manager/api_impl/workers_test.go b/internal/manager/api_impl/workers_test.go index a077edee..de84b56e 100644 --- a/internal/manager/api_impl/workers_test.go +++ b/internal/manager/api_impl/workers_test.go @@ -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, }) } diff --git a/internal/manager/sleep_scheduler/calculations.go b/internal/manager/sleep_scheduler/calculations.go index 7b112f0a..896970ef 100644 --- a/internal/manager/sleep_scheduler/calculations.go +++ b/internal/manager/sleep_scheduler/calculations.go @@ -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 { diff --git a/internal/manager/sleep_scheduler/calculations_test.go b/internal/manager/sleep_scheduler/calculations_test.go index 4b36a2c7..a4cfb496 100644 --- a/internal/manager/sleep_scheduler/calculations_test.go +++ b/internal/manager/sleep_scheduler/calculations_test.go @@ -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'. diff --git a/internal/manager/sleep_scheduler/sleep_scheduler.go b/internal/manager/sleep_scheduler/sleep_scheduler.go index 57c2a941..48803397 100644 --- a/internal/manager/sleep_scheduler/sleep_scheduler.go +++ b/internal/manager/sleep_scheduler/sleep_scheduler.go @@ -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()