Commit fc453256 authored by Kyle Clarke's avatar Kyle Clarke 💬

Additional findByJobID method - generate JobID is now on the global Kevin...

Additional findByJobID method - generate JobID is now on the global Kevin namespace, not a schedule. GenerateID now takes an optional string value to return a JobID type.
parent 8b433132
Pipeline #4212398 passed with stage
in 27 seconds
......@@ -29,13 +29,13 @@ func Simple() {
// Do some repeated work.
}
ID, _ := kevin.DefaultSchedule().Run(&Job{
JobID, _ := kevin.DefaultSchedule().Run(&Job{
Fn: Simple,
Every: time.Duration(time.Second),
})
// Some time later we check the status.
runner, _ := kevin.DefaultSchedule().Find(ID)
runner, _ := kevin.DefaultSchedule().FindByJobID(JobID)
i := runner.Info()
if i.IsRunning() {
// All is well
......@@ -188,7 +188,7 @@ ID, err := kevin.DefaultSchedule().Run(j)
By default a `JobID` is returned on `Add()` and `Run()` to enable you to store and
track your job runners progress. A `JobID` is **unique ONLY to each schedule**. If you
are creating numerous schedules, it may be beneficial to provide your own universal ID,
such as a `user_id`.
such as a `user_id`.
For example, imagine you have x3 schedules, one for http requests, one for sms requests
and another for emails. In such a scenario is would make sense to use a `user_id` as your
......@@ -202,13 +202,21 @@ s.Reset()
// Remove all user specific runners from all schedules.
for _, s := range kevin.All() {
err := s.Remove("your user ID here")
err := s.Remove(JobID)
if err != nil {
// Alert
}
}
```
Generate your JobID with an optional string value. A unique JobID will be returned if ommitted.
```
UniqueJobID := kevin.GenerateID()
UserJobID := kevin.GenerateID("your optional user_id here")
```
## Caveats / TODO's
**Job Every time.Duration**
......
......@@ -11,6 +11,8 @@
package kevin
import (
"crypto/rand"
"fmt"
"sync"
"time"
)
......@@ -27,11 +29,12 @@ const (
)
var pool *schedulePool
func init() {
pool = &schedulePool{
schedules: make(schedules),
schedules: make(schedules),
defaultPurgeAfter: DefaultPurgeAfter,
defaultPurgePoll: DefaultPurgePoll,
defaultPurgePoll: DefaultPurgePoll,
}
// Setup the default schedule on load
......@@ -56,7 +59,7 @@ type schedules map[string]*schedule
// SchedulesInfo represents information about all Schedulers assigned to the schedule pool.
type SchedulesInfo struct {
JobCount int64
PurgeAfter time.Duration
PurgeAfter time.Duration
PurgePoll time.Duration
ScheduleCount int64
Schedules map[string]ScheduleInfo
......@@ -89,12 +92,31 @@ func DefaultSchedule() *schedule {
return s
}
// GenerateID will return a "unique enough" job ID. You can always assign
// your own string ID to jobs. This is especially useful when using
// multiple schedules that pertain to a universal ID, such as a user_id.
func GenerateID(ID ...string) JobID {
if len(ID) > 0 {
return JobID(ID[0])
} else {
unix32bits := uint32(time.Now().UTC().Unix())
buff := make([]byte, 12)
numRead, err := rand.Read(buff)
if numRead != len(buff) || err != nil {
// On return an empty string ID
return JobID("")
}
return JobID(fmt.Sprintf("%x%x", unix32bits, buff))
}
}
// Info will return a populated SchedulesInfo struct.
func Info() SchedulesInfo {
c := SchedulesInfo{
Schedules: make(map[string]ScheduleInfo),
Schedules: make(map[string]ScheduleInfo),
PurgeAfter: pool.defaultPurgeAfter,
PurgePoll: pool.defaultPurgePoll,
PurgePoll: pool.defaultPurgePoll,
}
for _, s := range All() {
......
......@@ -43,6 +43,32 @@ func TestDefaultSchedule(t *testing.T) {
}
}
func TestGenerateID(t *testing.T) {
ID := GenerateID()
if len(ID) != 32 {
t.Error("ID of unknown length returned")
}
// I have tested 1 billion entries all ok.
ps := make(map[JobID]bool)
for i := 0; i < 200; i++ {
ID := GenerateID()
if _, ok := ps[ID]; ok {
t.Error("We have an identical ID of:", ID)
}
}
userDefined := "foobar"
ID = GenerateID(userDefined, "no", "matter", "ignored", "=]")
if len(ID) == 32 {
t.Error("User defined ID returned with incorrect length.")
}
if ID != JobID(userDefined) {
t.Error("User defined ID not assigned correctly.")
}
}
func TestInfo(t *testing.T) {
Reset()
i := Info()
......
......@@ -162,7 +162,7 @@ func do(r *runner) {
// Check if this job is orphaned. Though this "should not" happen
// we still check that this job is still assigned to this schedule.
_, err := r.job.schedule.Find(r.job.ID)
_, err := r.job.schedule.FindByJobID(r.job.ID)
if err != nil {
r.status = StatusError
r.err = ErrJobOrphaned
......
......@@ -35,7 +35,7 @@ func TestRunner_CanBegin(t *testing.T) {
j.Begins = time.Now().Add(time.Minute)
s := DefaultSchedule()
s.Add(j)
r, _ := s.Find(j.ID)
r, _ := s.FindByJobID(j.ID)
if r.CanBegin() {
t.Error("Can begin should be false with date 10 mins in future.")
......@@ -58,7 +58,7 @@ func TestRunner_CapBreached(t *testing.T) {
j.Cap = 0
s := DefaultSchedule()
s.Add(j)
r, _ := s.Find(j.ID)
r, _ := s.FindByJobID(j.ID)
if r.CapBreached() {
t.Error("Cap should not be breached with Cap of zero.")
}
......@@ -89,7 +89,7 @@ func TestRunner_Expired(t *testing.T) {
s := DefaultSchedule()
s.Add(j)
r, _ := s.Find(j.ID)
r, _ := s.FindByJobID(j.ID)
if r.Expired() {
t.Error("Expired should be false as zero expire time assigned to job.")
......@@ -111,7 +111,7 @@ func TestRunner_Stop(t *testing.T) {
j := newSimpleJob()
s := DefaultSchedule()
s.Add(j)
r, _ := s.Find(j.ID)
r, _ := s.FindByJobID(j.ID)
if r.Stopped() {
t.Error("A job should not be stopped by default.")
}
......
package kevin
import (
"crypto/rand"
"fmt"
"sync"
"time"
)
......@@ -17,13 +15,13 @@ type schedule struct {
// ScheduleConfig are used to create new Schedulers.
type ScheduleConfig struct {
Name string
Name string
// PurgeAfter vlue used to purge job runners that have a completed time older than Now plus this duration.
PurgeAfter time.Duration
// PurgePoll will poll the schedule pool to purge completed jobs. A zero value will not poll. Refer DefaultPurgePoll.
PurgePoll time.Duration
PurgePoll time.Duration
}
// ScheduleInfo represents information about all Schedulers assigned to the schedule pool.
......@@ -100,16 +98,15 @@ func NewSchedule(sc ScheduleConfig) (*schedule, error) {
func NewScheduleConfig() ScheduleConfig {
return ScheduleConfig{
PurgeAfter: pool.defaultPurgeAfter,
PurgePoll: pool.defaultPurgePoll,
PurgePoll: pool.defaultPurgePoll,
}
}
// Add will attempt to Add a new Job to the current schedule. It will return an error on
// a duplicate JobID found, or an Every duration value of zero.
func (s *schedule) Add(j *Job) (JobID, error) {
if "" == j.ID {
j.ID = s.GenerateID()
j.ID = GenerateID()
}
// Check for a duplicate job ID on this schedule.
......@@ -179,8 +176,15 @@ func (s *schedule) doPurge(before time.Duration) {
}
}
// Find will attempt to find and return a Runner instance on this schedule. Returns error on not found.
func (s *schedule) Find(ID JobID) (*runner, error) {
// Find will attempt to find and return a Runner instance on this schedule by a common String.
// Internally Kevin uses a JobID type so this Find method is used as a convenience for your
// common string references. Returns error on not found.
func (s *schedule) Find(ID string) (*runner, error) {
return s.FindByJobID(JobID(ID))
}
// Find will attempt to find and return a Runner instance on this schedule by JobID. Returns error on not found.
func (s *schedule) FindByJobID(ID JobID) (*runner, error) {
j, ok := s.runners[ID]
if ok {
return j, nil
......@@ -189,22 +193,6 @@ func (s *schedule) Find(ID JobID) (*runner, error) {
return nil, ErrScheduleJobNotFound
}
// GenerateID will return a "unique enough" job ID. You can always
// assign your own ID to jobs if you want explicit unique values.
func (s *schedule) GenerateID() JobID {
unix32bits := uint32(time.Now().UTC().Unix())
buff := make([]byte, 12)
numRead, err := rand.Read(buff)
if numRead != len(buff) || err != nil {
// On return an empty string ID
ID := JobID("")
return ID
}
ID := JobID(fmt.Sprintf("%x%x", unix32bits, buff))
return ID
}
// Info returns generic schedule info about this schedule instance.
func (s *schedule) Info() ScheduleInfo {
return ScheduleInfo{
......@@ -254,7 +242,7 @@ func (s *schedule) Reset() error {
// Stop will attempt to stop the execution on the next job runner from this schedule.
func (s *schedule) Stop(ID JobID) error {
r, err := s.Find(ID)
r, err := s.FindByJobID(ID)
if err != nil {
return err
}
......
......@@ -149,7 +149,7 @@ func TestSchedule_Add(t *testing.T) {
func TestSchedule_AutoPurge(t *testing.T) {
Reset()
s, _ := NewSchedule(ScheduleConfig{
Name: "TestSchedule_AutoPurge",
Name: "TestSchedule_AutoPurge",
PurgeAfter: time.Second,
})
j := newSimpleJob()
......@@ -185,18 +185,51 @@ func TestSchedule_AutoPurge(t *testing.T) {
func TestSchedule_Find(t *testing.T) {
Reset()
userID := "foobar"
userJobID := GenerateID(userID)
j := newSimpleJob()
j.ID = userJobID
ID, err := DefaultSchedule().Add(j)
if err != nil {
t.Error(err)
}
if ID != userJobID {
t.Error("User defined JobID does not match ID assiged to schedule.")
}
// Now find the job runners
foundByID, err := DefaultSchedule().Find(userID)
if err != nil {
t.Error(err)
}
foundByJobID, err := DefaultSchedule().FindByJobID(userJobID)
if err != nil {
t.Error(err)
}
if foundByID != foundByJobID {
t.Error("Mismatch of job runners found by each ID type.")
}
}
func TestSchedule_FindByJobID(t *testing.T) {
Reset()
ID, err := DefaultSchedule().Add(newSimpleJob())
if err != nil {
t.Error(err)
}
_, err = DefaultSchedule().Find(ID)
_, err = DefaultSchedule().FindByJobID(ID)
if err != nil {
t.Error(err)
}
Reset()
_, err = DefaultSchedule().Find(ID)
_, err = DefaultSchedule().FindByJobID(ID)
if !IsKevinError(err) {
t.Error("Error of unexpected type on find job.")
}
......@@ -206,23 +239,6 @@ func TestSchedule_Find(t *testing.T) {
}
}
func TestSchedule_GenerateID(t *testing.T) {
s := DefaultSchedule()
ID := s.GenerateID()
if len(ID) != 32 {
t.Error("Id of unknown length returned")
}
// I have tested 1 billion entries all ok.
ps := make(map[JobID]bool)
for i := 0; i < 200; i++ {
ID := s.GenerateID()
if _, ok := ps[ID]; ok {
t.Error("We have an identical ID of:", ID)
}
}
}
func TestSchedule_Info(t *testing.T) {
Reset()
s := DefaultSchedule()
......@@ -426,7 +442,7 @@ func TestSchedule_Stop(t *testing.T) {
j := newSimpleJob()
j.Every = 200 * time.Millisecond
ID, _ := s.Run(j)
r, _ := s.Find(ID)
r, _ := s.FindByJobID(ID)
if r.Info().IsRunning() {
t.Error("Execution path should beat the job and therefore not be running.")
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment