diff --git a/pkg/jobs/scheduled_job.go b/pkg/jobs/scheduled_job.go index cf6a520ffc3a..1bceea0c77fd 100644 --- a/pkg/jobs/scheduled_job.go +++ b/pkg/jobs/scheduled_job.go @@ -13,6 +13,7 @@ package jobs import ( "context" "fmt" + "math/rand" "reflect" "strings" "time" @@ -188,13 +189,28 @@ func (j *ScheduledJob) HasRecurringSchedule() bool { return len(j.rec.ScheduleExpr) > 0 } +// parseCronExpr parses cron expression, with added support for +// cockroach specific mnemonics. +func parseCronExpr(expr string) (cron.Schedule, error) { + switch strings.ToLower(expr) { + case "@every_hour": + // Schedule runs once an hour at a randomly chosen minute. + return cron.ParseStandard(fmt.Sprintf("%d * * * *", rand.Intn(60))) + case "@every_day": + // Schedule runs once a day at a randomly chosen time. + return cron.ParseStandard(fmt.Sprintf("%d %d * * *", rand.Intn(60), rand.Intn(24))) + default: + return cron.ParseStandard(expr) + } +} + // Frequency returns how often this schedule executes. func (j *ScheduledJob) Frequency() (time.Duration, error) { if !j.HasRecurringSchedule() { return 0, errors.Newf( "schedule %d is not periodic", j.rec.ScheduleID) } - expr, err := cron.ParseStandard(j.rec.ScheduleExpr) + expr, err := parseCronExpr(j.rec.ScheduleExpr) if err != nil { return 0, errors.Wrapf(err, "parsing schedule expression: %q; it must be a valid cron expression", @@ -212,7 +228,7 @@ func (j *ScheduledJob) ScheduleNextRun() error { return errors.Newf( "cannot set next run for schedule %d (empty schedule)", j.rec.ScheduleID) } - expr, err := cron.ParseStandard(j.rec.ScheduleExpr) + expr, err := parseCronExpr(j.rec.ScheduleExpr) if err != nil { return errors.Wrapf(err, "parsing schedule expression: %q", j.rec.ScheduleExpr) } diff --git a/pkg/jobs/scheduled_job_test.go b/pkg/jobs/scheduled_job_test.go index 4b9dab4181d4..17ef0e2f9bc0 100644 --- a/pkg/jobs/scheduled_job_test.go +++ b/pkg/jobs/scheduled_job_test.go @@ -18,6 +18,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/leaktest" "github.com/cockroachdb/cockroach/pkg/util/log" "github.com/cockroachdb/cockroach/pkg/util/timeutil" + "github.com/robfig/cron/v3" "github.com/stretchr/testify/require" ) @@ -117,3 +118,22 @@ func TestPauseUnpauseJob(t *testing.T) { require.False(t, loaded.IsPaused()) require.False(t, loaded.NextRun().Equal(time.Time{})) } + +func TestCrdbCronExtensions(t *testing.T) { + defer leaktest.AfterTest(t)() + defer log.Scope(t).Close(t) + + frequency := func(s cron.Schedule) time.Duration { + notALeapYear := time.Date(2022, 1, 2, 3, 4, 5, 6, time.UTC) + nextRun := s.Next(notALeapYear) + return s.Next(nextRun).Sub(nextRun) + } + + schedule, err := parseCronExpr("@every_hour") + require.NoError(t, err) + require.Equal(t, time.Hour, frequency(schedule)) + + schedule, err = parseCronExpr("@every_day") + require.NoError(t, err) + require.Equal(t, 24*time.Hour, frequency(schedule)) +}