func nextRun(schedule string) (int64, error) { sched, err := cron.Parse(schedule) if err != nil { return 0, err } return sched.Next(time.Now().UTC()).UnixNano(), nil }
//create a schdule with the cmd ID (overrides old ones) and func (sched *Scheduler) Add(cmd *core.Command) (interface{}, error) { defer sched.restart() db := sched.pool.Get() defer db.Close() job := &SchedulerJob{} err := json.Unmarshal([]byte(cmd.Data), job) if err != nil { log.Println("Failed to load command spec", cmd.Data, err) return nil, err } _, err = cron.Parse(job.Cron) if err != nil { return nil, err } job.ID = cmd.ID //we can safely push the command to the hashset now. db.Do("HSET", hashScheduleKey, cmd.ID, cmd.Data) return true, nil }
// ValidCron checks if the syntax of value is valid for a cron job func ValidCron(value string) error { _, err := cron.Parse(value) if err != nil { return errors.New(notValidCron) } return nil }
func (c *CronValue) Set(value string) error { _, err := cron.Parse(value) if err != nil { return fmt.Errorf("expected cron expression or '@every DURATION' got '%s'", value) } *c = (CronValue)(value) return nil }
func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} _, err := cron.Parse(schedule) if err != nil { allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error())) } return allErrs }
// Validate returns error if cron entry dosn't match crontab format func (c *CronSchedule) Validate() error { if c.entry == "" { return ErrMissingCronEntry } _, err := cron.Parse(c.entry) if err != nil { return err } return nil }
func Schedule(spec string, job cron.Job) { // Look to see if given spec is a key from the Config. if strings.HasPrefix(spec, "cron.") { confSpec, found := revel.Config.String(spec) if !found { panic("Cron spec not found: " + spec) } spec = confSpec } MainCron.Schedule(cron.Parse(spec), New(job)) }
func newJob(id string, pri int) *Job { sch, _ := cron.Parse("* * * * *") // subtract priority from fixed last run, so higher priority jobs were last run further back in time t_last_run := time.Unix(1437856044-int64(pri), 0) return &Job{ Id: id, t_schedule: sch, t_last_run: &t_last_run, scheduling_priority: NormalPriority, } }
func (k *Keeper) PutTask(name string, t Task) error { k.tkmu.Lock() defer k.tkmu.Unlock() if name != t.Name { return errors.New("Task name not correct") } if _, err := cron.Parse(t.Schedule); err != nil { return err } k.tasks[name] = t k.reloadCron() return k.save() }
func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // TODO soltysh: this should be removed when https://github.com/robfig/cron/issues/58 is fixed tmpSchedule := schedule if len(schedule) > 0 && schedule[0] != '@' { tmpSchedule = "0 " + schedule } if _, err := cron.Parse(tmpSchedule); err != nil { allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error())) } return allErrs }
func UpdateSchedule(s *spec.Schedule) error { _, err := cron.Parse(s.String()) if err != nil { return err } if err = gDb.UpdateSchedule(s); err != nil { return err } schedules.Stop() return initCron() }
// getRecentUnmetScheduleTimes gets a slice of times (from oldest to latest) that have passed when a Job should have started but did not. // // If there are too many (>100) unstarted times, just give up and return an empty slice. // If there were missed times prior to the last known start time, then those are not returned. func getRecentUnmetScheduleTimes(sj batch.ScheduledJob, now time.Time) ([]time.Time, error) { starts := []time.Time{} tmpSched := addSeconds(sj.Spec.Schedule) sched, err := cron.Parse(tmpSched) if err != nil { return starts, fmt.Errorf("Unparseable schedule: %s : %s", sj.Spec.Schedule, err) } var earliestTime time.Time if sj.Status.LastScheduleTime != nil { earliestTime = sj.Status.LastScheduleTime.Time } else { // If none found, then this is either a recently created scheduledJob, // or the active/completed info was somehow lost (contract for status // in kubernetes says it may need to be recreated), or that we have // started a job, but have not noticed it yet (distributed systems can // have arbitrary delays). In any case, use the creation time of the // ScheduledJob as last known start time. earliestTime = sj.ObjectMeta.CreationTimestamp.Time } if earliestTime.After(now) { return []time.Time{}, nil } for t := sched.Next(earliestTime); !t.After(now); t = sched.Next(t) { starts = append(starts, t) // An object might miss several starts. For example, if // controller gets wedged on friday at 5:01pm when everyone has // gone home, and someone comes in on tuesday AM and discovers // the problem and restarts the controller, then all the hourly // jobs, more than 80 of them for one hourly scheduledJob, should // all start running with no further intervention (if the scheduledJob // allows concurrency and late starts). // // However, if there is a bug somewhere, or incorrect clock // on controller's server or apiservers (for setting creationTimestamp) // then there could be so many missed start times (it could be off // by decades or more), that it would eat up all the CPU and memory // of this controller. In that case, we want to not try to list // all the misseded start times. // // I've somewhat arbitrarily picked 100, as more than 80, but // but less than "lots". if len(starts) > 100 { // We can't get the most recent times so just return an empty slice return []time.Time{}, fmt.Errorf("Too many missed start times to list") } } return starts, nil }
func (k *Keeper) AddTask(t Task) error { k.tkmu.Lock() defer k.tkmu.Unlock() if _, exists := k.tasks[t.Name]; exists { return errors.New("Task name duplicate: " + t.Name) } if _, err := cron.Parse(t.Schedule); err != nil { return err } t.Enabled = true k.tasks[t.Name] = t k.reloadCron() return k.save() }
// getNextStartTimeAfter gets the latest scheduled start time that is less than "now", or an error. func getNextStartTimeAfter(schedule string, now time.Time) (time.Time, error) { // Using robfig/cron for cron scheduled parsing and next runtime // computation. Not using the entire library because: // - I want to detect when we missed a runtime due to being down. // - How do I set the time such that I can detect the last known runtime? // - I guess the functions could launch a go-routine to start the job and // then return. // How to handle concurrency control. // How to detect changes to schedules or deleted schedules and then // update the jobs? sched, err := cron.Parse(schedule) if err != nil { return time.Unix(0, 0), fmt.Errorf("Unparseable schedule: %s : %s", schedule, err) } return sched.Next(now), nil }
func Schedule(spec string, job cron.Job) error { // Look to see if given spec is a key from the Config. if strings.HasPrefix(spec, "cron.") { confSpec, found := revel.Config.String(spec) if !found { panic("Cron spec not found: " + spec) } spec = confSpec } sched, err := cron.Parse(spec) if err != nil { return err } MainCron.Schedule(sched, New(job)) return nil }
func NewJob( id string, exec string, disabled bool, timesToRepeat int64, retries uint, cronExp string) *Job { trigger, _ := cron.Parse(cronExp) // TODO(javier): error handling here return &Job{ Id: id, Exec: exec, Disabled: disabled, TimesToRepeat: timesToRepeat, TimeToRepeatLeft: timesToRepeat, TotalRetries: retries - 1, CurrentRetries: 0, Trigger: trigger, NextRunAt: trigger.Next(time.Now().Add(-1 * time.Second)), } }
func createTaskUsingWFManifest(ctx *cli.Context) { // Get the workflow path := ctx.String("workflow-manifest") ext := filepath.Ext(path) file, e := ioutil.ReadFile(path) if !ctx.IsSet("interval") && !ctx.IsSet("i") { fmt.Println("Workflow manifest requires interval to be set via flag.") os.Exit(1) } if e != nil { fmt.Printf("File error [%s]- %v\n", ext, e) os.Exit(1) } var wf *wmap.WorkflowMap switch ext { case ".yaml", ".yml": // e = yaml.Unmarshal(file, &t) wf, e = wmap.FromYaml(file) if e != nil { fmt.Printf("Error parsing YAML file input - %v\n", e) os.Exit(1) } case ".json": wf, e = wmap.FromJson(file) // e = json.Unmarshal(file, &t) if e != nil { fmt.Printf("Error parsing JSON file input - %v\n", e) os.Exit(1) } } // Get the task name name := ctx.String("name") // Get the interval isCron := false i := ctx.String("interval") _, err := time.ParseDuration(i) if err != nil { // try interpreting interval as cron entry _, e := cron.Parse(i) if e != nil { fmt.Printf("Bad interval format:\nfor simple schedule: %v\nfor cron schedule: %v\n", err, e) os.Exit(1) } isCron = true } // Deadline for a task dl := ctx.String("deadline") var sch *client.Schedule // None of these mean it is a simple schedule if !ctx.IsSet("start-date") && !ctx.IsSet("start-time") && !ctx.IsSet("stop-date") && !ctx.IsSet("stop-time") { // Check if duration was set if ctx.IsSet("duration") && !isCron { d, err := time.ParseDuration(ctx.String("duration")) if err != nil { fmt.Printf("Bad duration format:\n%v\n", err) os.Exit(1) } start := time.Now().Add(createTaskNowPad) stop := start.Add(d) sch = &client.Schedule{ Type: "windowed", Interval: i, StartTime: &start, StopTime: &stop, } } else { // No start or stop and no duration == simple schedule t := "simple" if isCron { // It's a cron schedule, ignore "duration" if set t = "cron" } sch = &client.Schedule{ Type: t, Interval: i, } } } else { // We have some form of windowed schedule start := mergeDateTime( strings.ToUpper(ctx.String("start-time")), strings.ToUpper(ctx.String("start-date")), ) stop := mergeDateTime( strings.ToUpper(ctx.String("stop-time")), strings.ToUpper(ctx.String("stop-date")), ) // Use duration to create missing start or stop if ctx.IsSet("duration") { d, err := time.ParseDuration(ctx.String("duration")) if err != nil { fmt.Printf("Bad duration format:\n%v\n", err) os.Exit(1) } // if start is set and stop is not then use duration to create stop if start != nil && stop == nil { t := start.Add(d) stop = &t } // if stop is set and start is not then use duration to create start if stop != nil && start == nil { t := stop.Add(d * -1) start = &t } } sch = &client.Schedule{ Type: "windowed", Interval: i, StartTime: start, StopTime: stop, } } // Create task r := pClient.CreateTask(sch, wf, name, dl, !ctx.IsSet("no-start")) if r.Err != nil { errors := strings.Split(r.Err.Error(), " -- ") fmt.Println("Error creating task:") for _, err := range errors { fmt.Printf("%v\n", err) } os.Exit(1) } fmt.Println("Task created") fmt.Printf("ID: %s\n", r.ID) fmt.Printf("Name: %s\n", r.Name) fmt.Printf("State: %s\n", r.State) }
// parse the command-line options and use them to setup a new schedule for this task func (t *task) setScheduleFromCliOptions(ctx *cli.Context) error { // check the start, stop, and duration values to see if we're looking at a windowed schedule (or not) // first, get the parameters that define the windowed schedule start := mergeDateTime( strings.ToUpper(ctx.String("start-time")), strings.ToUpper(ctx.String("start-date")), ) stop := mergeDateTime( strings.ToUpper(ctx.String("stop-time")), strings.ToUpper(ctx.String("stop-date")), ) // Grab the duration string (if one was passed in) and parse it durationStr := ctx.String("duration") var duration *time.Duration if ctx.IsSet("duration") || durationStr != "" { d, err := time.ParseDuration(durationStr) if err != nil { return fmt.Errorf("Usage error (bad duration format); %v", err) } duration = &d } // Grab the interval for the schedule (if one was provided). Note that if an // interval value was not passed in and there is no interval defined for the // schedule associated with this task, it's an error interval := ctx.String("interval") if !ctx.IsSet("interval") && interval == "" && t.Schedule.Interval == "" { return fmt.Errorf("Usage error (missing interval value); when constructing a new task schedule an interval must be provided") } // if a start, stop, or duration value was provided, or if the existing schedule for this task // is 'windowed', then it's a 'windowed' schedule isWindowed := (start != nil || stop != nil || duration != nil || t.Schedule.Type == "windowed") // if an interval was passed in, then attempt to parse it (first as a duration, // then as the definition of a cron job) isCron := false if interval != "" { // first try to parse it as a duration _, err := time.ParseDuration(interval) if err != nil { // if that didn't work, then try parsing the interval as cron job entry _, e := cron.Parse(interval) if e != nil { return fmt.Errorf("Usage error (bad interval value): cannot parse interval value '%v' either as a duration or a cron entry", interval) } // if it's a 'windowed' schedule, then return an error (we can't use a // cron entry interval with a 'windowed' schedule) if isWindowed { return fmt.Errorf("Usage error; cannot use a cron entry ('%v') as the interval for a 'windowed' schedule", interval) } isCron = true } t.Schedule.Interval = interval } // if it's a 'windowed' schedule, then create a new 'windowed' schedule and add it to // the current task; the existing schedule (if on exists) will be replaced by the new // schedule in this method (note that it is an error to try to replace an existing // schedule with a new schedule of a different type, so an error will be returned from // this method call if that is the case) if isWindowed { return t.setWindowedSchedule(start, stop, duration) } // if it's not a 'windowed' schedule, then set the schedule type based on the 'isCron' flag, // which was set above. if isCron { // make sure the current schedule type (if there is one) matches; if not it is an error if t.Schedule.Type != "" && t.Schedule.Type != "cron" { return fmt.Errorf("Usage error; cannot replace existing schedule of type '%v' with a new, 'cron' schedule", t.Schedule.Type) } t.Schedule.Type = "cron" return nil } // if it wasn't a 'windowed' schedule and it's not a 'cron' schedule, then it must be a 'simple' // schedule, so first make sure the current schedule type (if there is one) matches; if not // then it's an error if t.Schedule.Type != "" && t.Schedule.Type != "simple" { return fmt.Errorf("Usage error; cannot replace existing schedule of type '%v' with a new, 'simple' schedule", t.Schedule.Type) } // if we get this far set the schedule type and return a nil error (indicating success) t.Schedule.Type = "simple" return nil }
func ParseExpression(expression string) error { _, err := cron.Parse(expression) return err }
func main() { flag.Usage = usage flag.Parse() args := flag.Args() if ver { fmt.Println("Version:", version) os.Exit(0) } if hlp { usage() } runMode := run_mode_default if cronT != default_cron { runMode = run_mode_cron } else if timeD != default_time { runMode = run_mode_time } else if loop != default_loop { runMode = run_mode_loop } else { fmt.Println("do not set -loop, -time or -cron. ant wiil run with default mode(loop once).") } sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGKILL) defer close(sigCh) c := cron.New() w, err := filter(args) if err != nil { fmt.Println(err) os.Exit(0) } if runMode == run_mode_cron { // crontab schedule, err := cron.Parse(cronT) if err != nil { fmt.Println(err) os.Exit(0) } c.Schedule(schedule, &moveJob{w}) c.Start() defer c.Stop() } else if runMode == run_mode_time { // time schedule := cron.Every(timeD) c := cron.New() c.Schedule(schedule, &moveJob{w}) c.Start() defer c.Stop() } else if runMode == run_mode_loop { // loop job := &moveJob{w} for i := 0; i < loop; i++ { job.Run() time.Sleep(5 * time.Second) } os.Exit(0) } else if runMode == run_mode_default { // default job := &moveJob{w} job.Run() os.Exit(0) } else { fmt.Printf("run mode [%d] unknown.\n", runMode) os.Exit(0) } sig := <-sigCh fmt.Println("Got signal:", sig) }