influxdb/task/backend/scheduler/scheduler.go

129 lines
3.8 KiB
Go

package scheduler
import (
"context"
"strings"
"time"
"github.com/influxdata/cron"
"github.com/influxdata/influxdb/v2/task/options"
)
// ID duplicates the influxdb ID so users of the scheduler don't have to
// import influxdb for the ID.
type ID uint64
// Executor is a system used by the scheduler to actually execute the scheduleable item.
type Executor interface {
// Execute is used to execute run's for any schedulable object.
// the executor can go through manual runs, clean currently running, and then create a new run based on `now`.
// if Now is zero we can just do the first 2 steps (This is how we would trigger manual runs).
// Errors returned from the execute request imply that this attempt has failed and
// should be put back in scheduler and re executed at a alter time. We will add scheduler specific errors
// so the time can be configurable.
Execute(ctx context.Context, id ID, scheduledFor time.Time, runAt time.Time) error
}
// Schedulable is the interface that encapsulates work that
// is to be executed on a specified schedule.
type Schedulable interface {
// ID is the unique identifier for this Schedulable
ID() ID
// Schedule defines the frequency for which this Schedulable should be
// queued for execution.
Schedule() Schedule
// Offset defines a negative or positive duration that should be added
// to the scheduled time, resulting in the instance running earlier or later
// than the scheduled time.
Offset() time.Duration
// LastScheduled specifies last time this Schedulable was queued
// for execution.
LastScheduled() time.Time
}
// SchedulableService encapsulates the work necessary to schedule a job
type SchedulableService interface {
// UpdateLastScheduled notifies the instance that it was scheduled for
// execution at the specified time
UpdateLastScheduled(ctx context.Context, id ID, t time.Time) error
}
func NewSchedule(unparsed string, lastScheduledAt time.Time) (Schedule, time.Time, error) {
lastScheduledAt = lastScheduledAt.UTC().Truncate(time.Second)
c, err := cron.ParseUTC(unparsed)
if err != nil {
return Schedule{}, lastScheduledAt, err
}
unparsed = strings.TrimSpace(unparsed)
// Align create to the hour/minute
if strings.HasPrefix(unparsed, "@every ") {
everyString := strings.TrimSpace(strings.TrimPrefix(unparsed, "@every "))
every := options.Duration{}
err := every.Parse(everyString)
if err != nil {
// We cannot align a invalid time
return Schedule{c}, lastScheduledAt, nil
}
// drop nanoseconds
lastScheduledAt = time.Unix(lastScheduledAt.UTC().Unix(), 0).UTC()
everyDur, err := every.DurationFrom(lastScheduledAt)
if err != nil {
return Schedule{c}, lastScheduledAt, nil
}
// and align
lastScheduledAt = lastScheduledAt.Truncate(everyDur).Truncate(time.Second)
}
return Schedule{c}, lastScheduledAt, err
}
// Schedule is an object a valid schedule of runs
type Schedule struct {
cron cron.Parsed
}
// Next returns the next time after from that a schedule should trigger on.
func (s Schedule) Next(from time.Time) (time.Time, error) {
return cron.Parsed(s.cron).Next(from)
}
// ValidSchedule returns an error if the cron string is invalid.
func ValidateSchedule(c string) error {
_, err := cron.ParseUTC(c)
return err
}
// Scheduler is a example interface of a Scheduler.
// // todo(lh): remove this once we start building the actual scheduler
type Scheduler interface {
// Schedule adds the specified task to the scheduler.
Schedule(task Schedulable) error
// Release removes the specified task from the scheduler.
Release(taskID ID) error
}
type ErrUnrecoverable struct {
error
}
func (e *ErrUnrecoverable) Error() string {
if e.error != nil {
return "error unrecoverable error on task run " + e.error.Error()
}
return "error unrecoverable error on task run"
}
func (e *ErrUnrecoverable) Unwrap() error {
return e.error
}