-
Notifications
You must be signed in to change notification settings - Fork 0
/
enqueuer.go
161 lines (136 loc) · 4.24 KB
/
enqueuer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//Refer github.com/gocraft/work
package period
import (
"math/rand"
"time"
"github.com/gocraft/work"
"github.com/gomodule/redigo/redis"
"github.com/robfig/cron"
"github.com/vmware/harbor/src/jobservice/job"
"github.com/vmware/harbor/src/jobservice/logger"
"github.com/vmware/harbor/src/jobservice/utils"
)
const (
periodicEnqueuerSleep = 2 * time.Minute
periodicEnqueuerHorizon = 4 * time.Minute
)
type periodicEnqueuer struct {
namespace string
pool *redis.Pool
policyStore *periodicJobPolicyStore
scheduledPeriodicJobs []*scheduledPeriodicJob
stopChan chan struct{}
doneStoppingChan chan struct{}
}
type periodicJob struct {
jobName string
spec string
schedule cron.Schedule
}
type scheduledPeriodicJob struct {
scheduledAt time.Time
scheduledAtEpoch int64
*periodicJob
}
func newPeriodicEnqueuer(namespace string, pool *redis.Pool, policyStore *periodicJobPolicyStore) *periodicEnqueuer {
return &periodicEnqueuer{
namespace: namespace,
pool: pool,
policyStore: policyStore,
stopChan: make(chan struct{}),
doneStoppingChan: make(chan struct{}),
}
}
func (pe *periodicEnqueuer) start() {
go pe.loop()
logger.Info("Periodic enqueuer is started")
}
func (pe *periodicEnqueuer) stop() {
pe.stopChan <- struct{}{}
<-pe.doneStoppingChan
}
func (pe *periodicEnqueuer) loop() {
defer func() {
logger.Info("Periodic enqueuer is stopped")
}()
// Begin reaping periodically
timer := time.NewTimer(periodicEnqueuerSleep + time.Duration(rand.Intn(30))*time.Second)
defer timer.Stop()
if pe.shouldEnqueue() {
err := pe.enqueue()
if err != nil {
logger.Errorf("periodic_enqueuer.loop.enqueue:%s\n", err)
}
}
for {
select {
case <-pe.stopChan:
pe.doneStoppingChan <- struct{}{}
return
case <-timer.C:
timer.Reset(periodicEnqueuerSleep + time.Duration(rand.Intn(30))*time.Second)
if pe.shouldEnqueue() {
err := pe.enqueue()
if err != nil {
logger.Errorf("periodic_enqueuer.loop.enqueue:%s\n", err)
}
}
}
}
}
func (pe *periodicEnqueuer) enqueue() error {
now := utils.NowEpochSeconds()
nowTime := time.Unix(now, 0)
horizon := nowTime.Add(periodicEnqueuerHorizon)
conn := pe.pool.Get()
defer conn.Close()
for _, pl := range pe.policyStore.list() {
schedule, err := cron.Parse(pl.CronSpec)
if err != nil {
//The cron spec should be already checked at top components.
//Just in cases, if error occurred, ignore it
continue
}
pj := &periodicJob{
jobName: pl.JobName,
spec: pl.CronSpec,
schedule: schedule,
}
for t := pj.schedule.Next(nowTime); t.Before(horizon); t = pj.schedule.Next(t) {
epoch := t.Unix()
job := &work.Job{
Name: pj.jobName,
ID: pl.PolicyID, //Same with the id of the policy it's being scheduled for
// This is technically wrong, but this lets the bytes be identical for the same periodic job instance. If we don't do this, we'd need to use a different approach -- probably giving each periodic job its own history of the past 100 periodic jobs, and only scheduling a job if it's not in the history.
EnqueuedAt: epoch,
Args: pl.JobParameters, //Pass parameters to scheduled job here
}
rawJSON, err := utils.SerializeJob(job)
if err != nil {
return err
}
_, err = conn.Do("ZADD", utils.RedisKeyScheduled(pe.namespace), epoch, rawJSON)
if err != nil {
return err
}
logger.Infof("Schedule job %s for policy %s at %d\n", pj.jobName, pl.PolicyID, epoch)
}
//Directly use redis conn to update the periodic job (policy) status
//Do not care the result
conn.Do("HMSET", utils.KeyJobStats(pe.namespace, pl.PolicyID), "status", job.JobStatusScheduled, "update_time", time.Now().Unix())
}
_, err := conn.Do("SET", utils.RedisKeyLastPeriodicEnqueue(pe.namespace), now)
return err
}
func (pe *periodicEnqueuer) shouldEnqueue() bool {
conn := pe.pool.Get()
defer conn.Close()
lastEnqueue, err := redis.Int64(conn.Do("GET", utils.RedisKeyLastPeriodicEnqueue(pe.namespace)))
if err == redis.ErrNil {
return true
} else if err != nil {
logger.Errorf("periodic_enqueuer.should_enqueue:%s\n", err)
return true
}
return lastEnqueue < (utils.NowEpochSeconds() - int64(periodicEnqueuerSleep/time.Minute))
}