Scheduler updates (#1311)

* Scheduler updates

* Add convenience methods to db/timestamp.Timestamp

* Remove recursive loop

Co-authored-by: Michael Gaffney <mike@gaffney.cc>
This commit is contained in:
Louis RuchandMichael Gaffney authored and GitHub committed 2021-06-11 11:36:14 -07:00
1 parent aea1da77b6
commit 649ed32504
14 files changed
+214 -109

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
package timestamp
import (
"time"
"google.golang.org/protobuf/types/known/timestamppb"
)
// New constructs a new Timestamp from the provided time.Time.
func New(t time.Time) *Timestamp {
return &Timestamp{
Timestamp: timestamppb.New(t),
}
}
// Now constructs a new Timestamp from the current time.
func Now() *Timestamp {
return &Timestamp{
Timestamp: timestamppb.Now(),
}
}
// AsTime converts x to a time.Time.
func (x *Timestamp) AsTime() time.Time {
return x.Timestamp.AsTime()
}
@@ -22,8 +22,7 @@ func TestSchedulerWorkflow(t *testing.T) {
wrapper := db.TestWrapper(t)
iam.TestRepo(t, conn, wrapper)
server := testController(t, conn, wrapper)
sched := testScheduler(t, conn, wrapper, server.PrivateId, WithRunJobsLimit(10), WithRunJobsInterval(time.Second))
sched := TestScheduler(t, conn, wrapper, WithRunJobsLimit(10), WithRunJobsInterval(time.Second))
job1Ch := make(chan error)
job1Ready := make(chan struct{})
@@ -67,7 +66,7 @@ func TestSchedulerWorkflow(t *testing.T) {
job1Ch <- nil
// Update job2 to run again
err = sched.UpdateJobNextRun(context.Background(), tj2.name, 0)
err = sched.UpdateJobNextRunInAtLeast(context.Background(), tj2.name, 0)
require.NoError(err)
<-job2Ready
@@ -84,8 +83,7 @@ func TestSchedulerCancelCtx(t *testing.T) {
wrapper := db.TestWrapper(t)
iam.TestRepo(t, conn, wrapper)
server := testController(t, conn, wrapper)
sched := testScheduler(t, conn, wrapper, server.PrivateId, WithRunJobsLimit(10), WithRunJobsInterval(time.Second))
sched := TestScheduler(t, conn, wrapper, WithRunJobsLimit(10), WithRunJobsInterval(time.Second))
fn, jobReady, jobDone := testJobFn()
tj := testJob{name: "name", description: "desc", fn: fn, nextRunIn: time.Hour}
@@ -125,8 +123,7 @@ func TestSchedulerInterruptedCancelCtx(t *testing.T) {
kmsCache := kms.TestKms(t, conn, wrapper)
iam.TestRepo(t, conn, wrapper)
server := testController(t, conn, wrapper)
sched := testScheduler(t, conn, wrapper, server.PrivateId, WithRunJobsLimit(10), WithRunJobsInterval(time.Second), WithMonitorInterval(time.Second))
sched := TestScheduler(t, conn, wrapper, WithRunJobsLimit(10), WithRunJobsInterval(time.Second), WithMonitorInterval(time.Second))
fn, job1Ready, job1Done := testJobFn()
tj1 := testJob{name: "name1", description: "desc", fn: fn, nextRunIn: time.Hour}
@@ -218,8 +215,7 @@ func TestSchedulerJobProgress(t *testing.T) {
kmsCache := kms.TestKms(t, conn, wrapper)
iam.TestRepo(t, conn, wrapper)
server := testController(t, conn, wrapper)
sched := testScheduler(t, conn, wrapper, server.PrivateId, WithRunJobsLimit(10), WithRunJobsInterval(time.Second), WithMonitorInterval(time.Second))
sched := TestScheduler(t, conn, wrapper, WithRunJobsLimit(10), WithRunJobsInterval(time.Second), WithMonitorInterval(time.Second))
jobReady := make(chan struct{})
fn := func(ctx context.Context) error {
@@ -306,8 +302,7 @@ func TestSchedulerMonitorLoop(t *testing.T) {
kmsCache := kms.TestKms(t, conn, wrapper)
iam.TestRepo(t, conn, wrapper)
server := testController(t, conn, wrapper)
sched := testScheduler(t, conn, wrapper, server.PrivateId, WithRunJobsLimit(10), WithInterruptThreshold(time.Second), WithRunJobsInterval(time.Second), WithMonitorInterval(time.Second))
sched := TestScheduler(t, conn, wrapper, WithRunJobsLimit(10), WithInterruptThreshold(time.Second), WithRunJobsInterval(time.Second), WithMonitorInterval(time.Second))
jobReady := make(chan struct{})
jobDone := make(chan struct{})
+5 -3
View File
@@ -19,10 +19,12 @@ type Job interface {
// NextRunIn returns the duration until the next job run should be scheduled. This
// method is invoked after a run has successfully completed and the next run time
// is being persisted by the scheduler.
NextRunIn() time.Duration
// is being persisted by the scheduler. If an error is returned, the error will be logged
// but the duration returned will still be used in scheduling. If a zero duration is returned
// the job will be scheduled to run again immediately.
NextRunIn() (time.Duration, error)
// Name is the human readable name of the job.
// Name is the unique name of the job.
Name() string
// Description is the human readable description of the job.
@@ -63,7 +63,7 @@ func TestJobWorkflow(t *testing.T) {
assert.Nil(newRuns)
// Update job next run to time in past
job, err = repo.UpdateJobNextRun(context.Background(), job.Name, 0)
job, err = repo.UpdateJobNextRunInAtLeast(context.Background(), job.Name, 0)
require.NoError(err)
// Now that next scheduled time is in past, a request for work should return a Run
+11
View File
@@ -27,6 +27,17 @@ const createJobQuery = `
returning *;
`
const setNextScheduledRunIfSoonerQuery = `
update
job
set
next_scheduled_run = least(wt_add_seconds_to_now(?), next_scheduled_run)
where
plugin_id = ?
and name = ?
returning *;
`
const setNextScheduledRunQuery = `
update
job
+6 -6
View File
@@ -70,13 +70,13 @@ func (r *Repository) CreateJob(ctx context.Context, name, description string, op
return j, nil
}
// UpdateJobNextRun updates the Job repository entry for the job name
// setting the job's NextScheduledRun to the current database time incremented by
// the nextRunIn parameter.
// UpdateJobNextRunInAtLeast updates the Job repository entry for the job name,
// setting the job's NextScheduledRun time to either the current database time incremented by
// the nextRunInAtLeast parameter or the current NextScheduledRun time value, which ever is sooner.
//
// All options are ignored.
func (r *Repository) UpdateJobNextRun(ctx context.Context, name string, nextRunIn time.Duration, _ ...Option) (*Job, error) {
const op = "job.(Repository).UpdateJobNextRun"
func (r *Repository) UpdateJobNextRunInAtLeast(ctx context.Context, name string, nextRunInAtLeast time.Duration, _ ...Option) (*Job, error) {
const op = "job.(Repository).UpdateJobNextRunInAtLeast"
if name == "" {
return nil, errors.New(errors.InvalidParameter, op, "missing name")
}
@@ -84,7 +84,7 @@ func (r *Repository) UpdateJobNextRun(ctx context.Context, name string, nextRunI
j := allocJob()
_, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
func(r db.Reader, w db.Writer) error {
rows, err := w.Query(ctx, setNextScheduledRunQuery, []interface{}{int(nextRunIn.Round(time.Second).Seconds()), defaultPluginId, name})
rows, err := w.Query(ctx, setNextScheduledRunIfSoonerQuery, []interface{}{int(nextRunInAtLeast.Round(time.Second).Seconds()), defaultPluginId, name})
if err != nil {
return errors.Wrap(err, op, errors.WithMsg(
fmt.Sprintf("failed to set next scheduled run time for job %v", name)))
+39 -12
View File
@@ -366,7 +366,7 @@ func TestRepository_ListJobs_Limits(t *testing.T) {
}
}
func TestRepository_UpdateJobNextRun(t *testing.T) {
func TestRepository_UpdateJobNextRunInAtLeast(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
@@ -380,34 +380,61 @@ func TestRepository_UpdateJobNextRun(t *testing.T) {
require.NoError(err)
require.NotNil(repo)
job, err := repo.CreateJob(context.Background(), "name", "description")
job, err := repo.CreateJob(context.Background(), "valid", "description", WithNextRunIn(2*time.Hour))
require.NoError(err)
got, err := repo.UpdateJobNextRun(context.Background(), job.Name, time.Hour)
got, err := repo.UpdateJobNextRunInAtLeast(context.Background(), job.Name, time.Hour)
require.NoError(err)
require.NotNil(got)
previousRunAt := job.NextScheduledRun.Timestamp.GetSeconds()
nextRunAt := got.NextScheduledRun.Timestamp.GetSeconds()
assert.True(nextRunAt >= previousRunAt+int64(time.Hour.Seconds()),
fmt.Sprintf("expected next run (%d) to be greater than or equal to the previous run (%d)",
nextRunAt, previousRunAt))
previousRunAt := job.NextScheduledRun.AsTime()
nextRunAt := got.NextScheduledRun.AsTime()
assert.Equal(nextRunAt.Round(time.Minute), previousRunAt.Add(-1*time.Hour).Round(time.Minute))
// update NextScheduledRun to pass equality check
job.NextScheduledRun = got.NextScheduledRun
assert.Equal(job, got)
})
t.Run("next-run-already-sooner", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kmsCache)
require.NoError(err)
require.NotNil(repo)
job, err := repo.CreateJob(context.Background(), "next-run-already-sooner", "description", WithNextRunIn(time.Hour))
require.NoError(err)
previousRunAt := job.NextScheduledRun.AsTime()
got, err := repo.UpdateJobNextRunInAtLeast(context.Background(), job.Name, 2*time.Hour)
require.NoError(err)
require.NotNil(got)
// Next run should not have been updated since its later than already scheduled
assert.Equal(got.NextScheduledRun.AsTime(), previousRunAt)
got, err = repo.UpdateJobNextRunInAtLeast(context.Background(), job.Name, time.Minute)
require.NoError(err)
require.NotNil(got)
// Next run should be less than previous run at
assert.True(got.NextScheduledRun.AsTime().Before(previousRunAt))
previousRunAt = got.NextScheduledRun.AsTime()
got, err = repo.UpdateJobNextRunInAtLeast(context.Background(), job.Name, time.Hour)
require.NoError(err)
require.NotNil(got)
// Next run should not have been updated since its later than already scheduled
assert.Equal(got.NextScheduledRun.AsTime(), previousRunAt)
})
t.Run("no-name", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(rw, rw, kmsCache)
require.NoError(err)
require.NotNil(repo)
got, err := repo.UpdateJobNextRun(context.Background(), "", time.Hour)
got, err := repo.UpdateJobNextRunInAtLeast(context.Background(), "", time.Hour)
require.Error(err)
require.Nil(got)
assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "Unexpected error %s", err)
assert.Equal("job.(Repository).UpdateJobNextRun: missing name: parameter violation: error #100", err.Error())
assert.Equal("job.(Repository).UpdateJobNextRunInAtLeast: missing name: parameter violation: error #100", err.Error())
})
t.Run("job-not-found", func(t *testing.T) {
@@ -416,10 +443,10 @@ func TestRepository_UpdateJobNextRun(t *testing.T) {
require.NoError(err)
require.NotNil(repo)
got, err := repo.UpdateJobNextRun(context.Background(), "fake-name", time.Hour)
got, err := repo.UpdateJobNextRunInAtLeast(context.Background(), "fake-name", time.Hour)
require.Error(err)
require.Nil(got)
assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "Unexpected error %s", err)
assert.Equal("job.(Repository).UpdateJobNextRun: db.DoTx: job.(Repository).UpdateJobNextRun: job \"fake-name\" does not exist: search issue: error #1100", err.Error())
assert.Equal("job.(Repository).UpdateJobNextRunInAtLeast: db.DoTx: job.(Repository).UpdateJobNextRunInAtLeast: job \"fake-name\" does not exist: search issue: error #1100", err.Error())
})
}
@@ -557,12 +557,9 @@ func TestRepository_CompleteRun(t *testing.T) {
// The previous run is ended before the next run is scheduled, therefore the previous
// run end time incremented by the nextRunIn duration, should be less than or equal to the
// NextScheduledRun time that is persisted in the repo.
nextRunAt := updatedJob.NextScheduledRun.Timestamp.GetSeconds()
previousRunEnd := got.EndTime.Timestamp.GetSeconds()
nextRunIn := int64(tt.nextRunIn.Round(time.Second).Seconds())
assert.True(nextRunAt >= previousRunEnd+nextRunIn,
fmt.Sprintf("expected next run (%d) to be greater than or equal to the previous run end time (%d) incremented by %d",
nextRunAt, previousRunEnd, nextRunIn))
nextRunAt := updatedJob.NextScheduledRun.AsTime()
previousRunEnd := got.EndTime.AsTime()
assert.Equal(nextRunAt.Round(time.Minute), previousRunEnd.Add(tt.nextRunIn).Round(time.Minute))
// Delete job run so it does not clash with future runs
_, err = repo.deleteRun(context.Background(), privateId)
+3 -7
View File
@@ -2,7 +2,6 @@ package job
import (
"context"
"fmt"
"testing"
"time"
@@ -44,12 +43,9 @@ func Test_TestJob(t *testing.T) {
// The previous job next scheduled run should have been created with the current database time,
// while the current job should be an hour later
previousJobNextRun := got.NextScheduledRun.Timestamp.GetSeconds()
currentJobNextRun := got1.NextScheduledRun.Timestamp.GetSeconds()
nextRunIn := int64(time.Hour.Seconds())
assert.True(currentJobNextRun >= previousJobNextRun+nextRunIn,
fmt.Sprintf("expected next run (%d) to be greater than or equal to the previous run end time (%d) incremented by %d",
currentJobNextRun, previousJobNextRun, nextRunIn))
previousJobNextRun := got.NextScheduledRun.AsTime()
currentJobNextRun := got1.NextScheduledRun.AsTime()
assert.Equal(currentJobNextRun.Round(time.Minute), previousJobNextRun.Add(time.Hour).Round(time.Minute))
}
func Test_TestRun(t *testing.T) {
+10
View File
@@ -23,6 +23,7 @@ type Option func(*options)
// options = how options are represented
type options struct {
withNextRunIn time.Duration
withRunJobsLimit uint
withRunJobInterval time.Duration
withMonitorInterval time.Duration
@@ -85,3 +86,12 @@ func WithInterruptThreshold(l time.Duration) Option {
}
}
}
// WithNextRunIn provides an option to provide the duration until the next run is scheduled.
// If this option is not provided the NextScheduledRun of the job will default to the
// current database time, and be available to run immediately.
func WithNextRunIn(d time.Duration) Option {
return func(o *options) {
o.withNextRunIn = d
}
}
+23 -17
View File
@@ -76,8 +76,8 @@ func New(serverId string, jobRepoFn jobRepoFactory, logger hclog.Logger, opt ...
//
// • job must be provided and is an implementer of the Job interface.
//
// All options are ignored.
func (s *Scheduler) RegisterJob(ctx context.Context, j Job, _ ...Option) error {
// WithNextRunIn is the only valid options.
func (s *Scheduler) RegisterJob(ctx context.Context, j Job, opt ...Option) error {
const op = "scheduler.(Scheduler).RegisterJob"
if err := validateJob(j); err != nil {
return errors.Wrap(err, op)
@@ -93,7 +93,8 @@ func (s *Scheduler) RegisterJob(ctx context.Context, j Job, _ ...Option) error {
return errors.Wrap(err, op)
}
_, err = repo.CreateJob(ctx, j.Name(), j.Description())
opts := getOpts(opt...)
_, err = repo.CreateJob(ctx, j.Name(), j.Description(), job.WithNextRunIn(opts.withNextRunIn))
if err != nil && !errors.IsUniqueError(err) {
return errors.Wrap(err, op)
}
@@ -102,13 +103,14 @@ func (s *Scheduler) RegisterJob(ctx context.Context, j Job, _ ...Option) error {
return nil
}
// UpdateJobNextRun sets the next scheduled run time for the provided name
// to the current database time incremented by the nextRunIn parameter. If
// nextRunIn == 0 the job will be available to run immediately.
// UpdateJobNextRunInAtLeast updates the next scheduled run time for the provided name,
// setting the job's NextScheduledRun time to either the current database time incremented by
// the nextRunInAtLeast parameter or the current NextScheduledRun time value, which ever is sooner.
// If nextRunInAtLeast == 0 the job will be available to run immediately.
//
// All options are ignored.
func (s *Scheduler) UpdateJobNextRun(ctx context.Context, name string, nextRunIn time.Duration, _ ...Option) error {
const op = "scheduler.(Scheduler).UpdateJobNextRun"
func (s *Scheduler) UpdateJobNextRunInAtLeast(ctx context.Context, name string, nextRunInAtLeast time.Duration, _ ...Option) error {
const op = "scheduler.(Scheduler).UpdateJobNextRunInAtLeast"
if name == "" {
return errors.New(errors.InvalidParameter, op, "missing name")
}
@@ -117,7 +119,7 @@ func (s *Scheduler) UpdateJobNextRun(ctx context.Context, name string, nextRunIn
return errors.Wrap(err, op)
}
_, err = repo.UpdateJobNextRun(ctx, name, nextRunIn)
_, err = repo.UpdateJobNextRunInAtLeast(ctx, name, nextRunInAtLeast)
if err != nil {
return errors.Wrap(err, op)
}
@@ -137,7 +139,7 @@ func (s *Scheduler) Start(ctx context.Context) error {
}
if err := ctx.Err(); err != nil {
return err
return errors.Wrap(err, op)
}
repo, err := s.jobRepoFn()
@@ -159,7 +161,7 @@ func (s *Scheduler) Start(ctx context.Context) error {
func (s *Scheduler) start(ctx context.Context) {
s.logger.Debug("starting scheduling loop")
timer := time.NewTimer(0)
timer := time.NewTimer(s.runJobsInterval)
for {
select {
case <-ctx.Done():
@@ -220,19 +222,23 @@ func (s *Scheduler) runJob(ctx context.Context, r *job.Run) error {
go func() {
defer rj.cancelCtx()
runErr := j.Run(jobContext)
var updateErr error
switch runErr {
case nil:
s.logger.Debug("job run complete", "run id", r.PrivateId, "name", j.Name())
if _, inner := repo.CompleteRun(jobContext, r.PrivateId, j.NextRunIn()); inner != nil {
s.logger.Error("error updating completed job run", "error", inner)
nextRun, inner := j.NextRunIn()
if inner != nil {
s.logger.Error("error getting next run time", "name", j.Name(), "error", inner)
}
_, updateErr = repo.CompleteRun(jobContext, r.PrivateId, nextRun)
default:
s.logger.Debug("job run failed", "run id", r.PrivateId, "name", j.Name())
if _, inner := repo.FailRun(jobContext, r.PrivateId); inner != nil {
s.logger.Error("error updating failed job run", "error", inner)
}
s.logger.Debug("job run failed", "run id", r.PrivateId, "name", j.Name(), "error", runErr)
_, updateErr = repo.FailRun(jobContext, r.PrivateId)
}
if updateErr != nil {
s.logger.Error("error updating job run", "name", j.Name(), "error", updateErr)
}
s.runningJobs.Delete(j.Name())
}()
+47 -17
View File
@@ -163,8 +163,7 @@ func TestScheduler_RegisterJob(t *testing.T) {
wrapper := db.TestWrapper(t)
iam.TestRepo(t, conn, wrapper)
server := testController(t, conn, wrapper)
sched := testScheduler(t, conn, wrapper, server.PrivateId)
sched := TestScheduler(t, conn, wrapper)
tests := []struct {
name string
@@ -268,8 +267,7 @@ func TestScheduler_RegisterJob(t *testing.T) {
_, ok := sched.registeredJobs.Load(tj.name)
assert.True(ok)
server1 := testController(t, conn, wrapper)
sched1 := testScheduler(t, conn, wrapper, server1.PrivateId)
sched1 := TestScheduler(t, conn, wrapper)
// Verify job is not registered on second scheduler
_, ok = sched1.registeredJobs.Load(tj.name)
assert.False(ok)
@@ -282,23 +280,22 @@ func TestScheduler_RegisterJob(t *testing.T) {
})
}
func TestScheduler_UpdateJobNextRun(t *testing.T) {
func TestScheduler_UpdateJobNextRunInAtLeast(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
iam.TestRepo(t, conn, wrapper)
server := testController(t, conn, wrapper)
sched := testScheduler(t, conn, wrapper, server.PrivateId)
sched := TestScheduler(t, conn, wrapper)
t.Run("missing-job-name", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := sched.UpdateJobNextRun(context.Background(), "", time.Hour)
err := sched.UpdateJobNextRunInAtLeast(context.Background(), "", time.Hour)
require.Error(err)
assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "Unexpected error %s", err)
assert.Equal("scheduler.(Scheduler).UpdateJobNextRun: missing name: parameter violation: error #100", err.Error())
assert.Equal("scheduler.(Scheduler).UpdateJobNextRunInAtLeast: missing name: parameter violation: error #100", err.Error())
})
t.Run("job-not-found", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@@ -306,33 +303,66 @@ func TestScheduler_UpdateJobNextRun(t *testing.T) {
// Insert fake job to bypass registration check
sched.registeredJobs.Store("fake-job-name", nil)
err := sched.UpdateJobNextRun(context.Background(), "fake-job-name", time.Hour)
err := sched.UpdateJobNextRunInAtLeast(context.Background(), "fake-job-name", time.Hour)
require.Error(err)
assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "Unexpected error %s", err)
assert.Equal("scheduler.(Scheduler).UpdateJobNextRun: job.(Repository).UpdateJobNextRun: db.DoTx: job.(Repository).UpdateJobNextRun: job \"fake-job-name\" does not exist: search issue: error #1100", err.Error())
assert.Equal("scheduler.(Scheduler).UpdateJobNextRunInAtLeast: job.(Repository).UpdateJobNextRunInAtLeast: db.DoTx: job.(Repository).UpdateJobNextRunInAtLeast: job \"fake-job-name\" does not exist: search issue: error #1100", err.Error())
})
t.Run("valid", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tj := testJob{
name: "name",
name: "valid",
description: "description",
}
err := sched.RegisterJob(context.Background(), tj)
err := sched.RegisterJob(context.Background(), tj, WithNextRunIn(2*time.Hour))
require.NoError(err)
// get job from repo
var dbJob job.Job
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []interface{}{tj.name})
require.NoError(err)
previousNextRun := dbJob.NextScheduledRun.Timestamp.GetSeconds()
previousNextRun := dbJob.NextScheduledRun.AsTime()
err = sched.UpdateJobNextRun(context.Background(), tj.name, time.Hour)
err = sched.UpdateJobNextRunInAtLeast(context.Background(), tj.name, time.Hour)
require.NoError(err)
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []interface{}{tj.name})
require.NoError(err)
// Verify job run time in repo is at least 1 hour later than the previous run time
assert.True(previousNextRun+int64(time.Hour.Seconds()) <= dbJob.NextScheduledRun.Timestamp.GetSeconds())
// Verify job run time in repo is at least 1 hour before than the previous run time
assert.Equal(previousNextRun.Add(-1*time.Hour).Round(time.Minute), dbJob.NextScheduledRun.AsTime().Round(time.Minute))
})
t.Run("next-run-already-sooner", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tj := testJob{
name: "next-run-already-sooner",
description: "description",
}
err := sched.RegisterJob(context.Background(), tj, WithNextRunIn(2*time.Hour))
require.NoError(err)
// get job from repo
var dbJob job.Job
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []interface{}{tj.name})
require.NoError(err)
previousNextRun := dbJob.NextScheduledRun.AsTime()
err = sched.UpdateJobNextRunInAtLeast(context.Background(), tj.name, 4*time.Hour)
require.NoError(err)
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []interface{}{tj.name})
require.NoError(err)
// Job should not have updated next run time, since its later than the already scheduled time
assert.Equal(previousNextRun, dbJob.NextScheduledRun.AsTime())
err = sched.UpdateJobNextRunInAtLeast(context.Background(), tj.name, time.Hour)
require.NoError(err)
err = rw.LookupWhere(context.Background(), &dbJob, "name = ?", []interface{}{tj.name})
require.NoError(err)
// Verify job run time in repo is at least 1 hour before than the previous run time
assert.Equal(previousNextRun.Add(-1*time.Hour).Round(time.Minute), dbJob.NextScheduledRun.AsTime().Round(time.Minute))
})
}
+19 -20
View File
@@ -18,29 +18,20 @@ import (
"github.com/stretchr/testify/require"
)
func testScheduler(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, serverId string, opts ...Option) *Scheduler {
// TestScheduler creates a mock controller and a new Scheduler attached to that controller id.
// The Scheduler returned should only be used for tests. The mock controller is not run.
//
// WithRunJobsLimit, WithRunJobsInterval, WithMonitorInterval and WithInterruptThreshold are
// the only valid options.
func TestScheduler(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, opt ...Option) *Scheduler {
t.Helper()
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrapper)
serversRepo, err := servers.NewRepository(rw, rw, kmsCache)
require.NoError(t, err)
iam.TestRepo(t, conn, wrapper)
jobRepoFn := func() (*job.Repository, error) {
return job.NewRepository(rw, rw, kmsCache)
}
s, err := New(serverId, jobRepoFn, hclog.L(), opts...)
require.NoError(t, err)
return s
}
func testController(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper) *servers.Server {
t.Helper()
rw := db.New(conn)
kms := kms.TestKms(t, conn, wrapper)
serversRepo, err := servers.NewRepository(rw, rw, kms)
require.NoError(t, err)
id, err := uuid.GenerateUUID()
require.NoError(t, err)
controller := &servers.Server{
@@ -51,7 +42,15 @@ func testController(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper) *serv
}
_, _, err = serversRepo.UpsertServer(context.Background(), controller)
require.NoError(t, err)
return controller
jobRepoFn := func() (*job.Repository, error) {
return job.NewRepository(rw, rw, kmsCache)
}
s, err := New(controller.PrivateId, jobRepoFn, hclog.L(), opt...)
require.NoError(t, err)
return s
}
func testJobFn() (func(ctx context.Context) error, chan struct{}, chan struct{}) {
@@ -87,8 +86,8 @@ func (j testJob) Run(ctx context.Context) error {
return j.fn(ctx)
}
func (j testJob) NextRunIn() time.Duration {
return j.nextRunIn
func (j testJob) NextRunIn() (time.Duration, error) {
return j.nextRunIn, nil
}
func (j testJob) Name() string {
+15 -9
View File
@@ -112,6 +112,14 @@ func New(conf *Config) (*Controller, error) {
); err != nil {
return nil, fmt.Errorf("error adding config keys to kms: %w", err)
}
jobRepoFn := func() (*job.Repository, error) {
return job.NewRepository(dbase, dbase, c.kms)
}
c.scheduler, err = scheduler.New(c.conf.RawConfig.Controller.Name, jobRepoFn, c.logger)
if err != nil {
return nil, fmt.Errorf("error creating new scheduler: %w", err)
}
c.IamRepoFn = func() (*iam.Repository, error) {
return iam.NewRepository(dbase, dbase, c.kms, iam.WithRandomReader(c.conf.SecureRandomReader))
}
@@ -138,17 +146,8 @@ func New(conf *Config) (*Controller, error) {
c.SessionRepoFn = func() (*session.Repository, error) {
return session.NewRepository(dbase, dbase, c.kms)
}
c.workerAuthCache = cache.New(0, 0)
jobRepoFn := func() (*job.Repository, error) {
return job.NewRepository(dbase, dbase, c.kms)
}
c.scheduler, err = scheduler.New(c.conf.RawConfig.Controller.Name, jobRepoFn, c.logger)
if err != nil {
return nil, fmt.Errorf("error creating new scheduler: %w", err)
}
return c, nil
}
@@ -158,6 +157,9 @@ func (c *Controller) Start() error {
return nil
}
c.baseContext, c.baseCancel = context.WithCancel(context.Background())
if err := c.registerJobs(); err != nil {
return fmt.Errorf("error registering jobs: %w", err)
}
if err := c.scheduler.Start(c.baseContext); err != nil {
return fmt.Errorf("error starting scheduler: %w", err)
}
@@ -175,6 +177,10 @@ func (c *Controller) Start() error {
return nil
}
func (c *Controller) registerJobs() error {
return nil
}
func (c *Controller) Shutdown(serversOnly bool) error {
if !c.started.Load() {
c.logger.Info("already shut down, skipping")