-
Notifications
You must be signed in to change notification settings - Fork 1
/
schedule.go
224 lines (204 loc) · 6.12 KB
/
schedule.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package main
import (
"fmt"
"time"
log "github.com/sirupsen/logrus"
)
func priorityRule(typeStr string, critical bool) int {
if critical {
return 0
}
if typeStr == "manual" {
return 1
}
return 2
}
type Task struct {
ID string
StartDatetime time.Time
Duration time.Duration
Deadline time.Time
Zones []string
Type string // auto or manual
Critical bool // only for manual type
Priority int // 0 for critical, 1, for manual noncritical, 2 for auto
Status string // wait, progress, complete or cancel
}
var tasks = make(map[string]*Task)
type ScheduleZone []string
var schedule = make(map[string]ScheduleZone)
func cancelTask(taskId string) { // tasks are cancelled in all zones specified for the task
log.Debug("Cancelling task ", taskId)
if tasks[taskId].Status != "cancel" {
for _, zone := range tasks[taskId].Zones {
idx := 0
for i := range schedule[zone] {
if schedule[zone][i] == taskId {
idx = i
}
}
schedule[zone] = append(schedule[zone][:idx], schedule[zone][idx+1:]...)
}
tasks[taskId].Status = "cancel"
}
}
func insertTask(taskId string, idx int, zone string) {
log.Debug("Inserting task ", taskId, " into schedule...")
if len(schedule[zone]) == idx { // nil or empty slice or after last element
schedule[zone] = append(schedule[zone], taskId)
return
}
schedule[zone] = append(schedule[zone][:idx+1], schedule[zone][idx:]...) // index < len(schedue)
schedule[zone][idx] = taskId
}
type Order struct {
zone string
cancelTaskIds []string
addIdx int
taskID string
}
func executeOrder(order Order) {
for _, taskId := range order.cancelTaskIds {
cancelTask(taskId)
}
insertTask(order.taskID, order.addIdx, order.zone)
}
func overlap(start1 time.Time, end1 time.Time, start2 time.Time, end2 time.Time) bool {
if start1.After(start2) {
return overlap(start2, end2, start1, end1)
}
if !end1.Before(start2) {
return true
}
return false
}
func availableTimeZone(task *Task) error {
// check that task is scheduled in available zone in available time
startTime := task.StartDatetime
endTime := task.StartDatetime.Add(task.Duration)
// hack: cut off the date part and reconvert to time.Time to compare with whiteListed timespans
startTimeConverted, _ := time.Parse("15:04", startTime.Format("15:04"))
endTimeConverted, _ := time.Parse("15:04", endTime.Format("15:04"))
for _, zone := range task.Zones {
zoneExists := false
for _, blackListZone := range config.BlackList {
if zone == blackListZone {
zoneExists = true
if !task.Critical {
return fmt.Errorf("one of zones is in blackList and task is not critical: %s", zone)
}
}
}
availableZones := len(config.WhiteList)
for whiteListZone, zoneTimeSpans := range config.WhiteList {
if zone == whiteListZone {
availableZones -= 1
zoneExists = true
valid := false
for _, timeSpan := range zoneTimeSpans {
log.Debug(fmt.Sprintf("Comparing timespans... startTime: %v, endTime: %v, timeSpan: %v", startTimeConverted, endTimeConverted, timeSpan))
if startTimeConverted.After(timeSpan.Start) && endTimeConverted.Before(timeSpan.End) {
valid = true
}
}
if !valid {
return fmt.Errorf("does not match any timespan in zone: %s", zone)
}
} else {
// ensure that there are at least config.availableZones available
zoneSchedule, ok := schedule[whiteListZone]
if ok {
for i := range zoneSchedule {
schedTask := tasks[zoneSchedule[i]]
if overlap(schedTask.StartDatetime, schedTask.StartDatetime.Add(schedTask.Duration), startTime, endTime) {
availableZones -= 1
break
}
}
}
}
}
if availableZones < config.AvailableZones {
return fmt.Errorf("can't schedule task; %d zones should be available at all times", config.AvailableZones)
}
if !zoneExists {
return fmt.Errorf("no such zone exists in config")
}
}
return nil
}
func availablePrioritizedTimespan(task *Task, zone string) (Order, error) {
order := Order{
zone: zone,
taskID: task.ID,
}
startIdx := 0
zoneSchedule, ok := schedule[zone]
if ok {
overlaps := []int{}
for i := range zoneSchedule {
schedTask := tasks[zoneSchedule[i]]
schedTaskStart := schedTask.StartDatetime
schedTaskEnd := schedTaskStart.Add(tasks[zoneSchedule[i]].Duration).Add(config.Pauses[zone]) // added zone-specific pauses
if overlap(schedTaskStart, schedTaskEnd, task.StartDatetime, task.StartDatetime.Add(task.Duration)) {
overlaps = append(overlaps, i)
}
if task.StartDatetime.After(schedTaskEnd) {
startIdx = i + 1
}
}
// no overlaps
if len(overlaps) == 0 {
order.cancelTaskIds = []string{}
order.addIdx = startIdx
return order, nil
}
// there are overlaps; check priorities and status (if "cancel", then the task is set for cancellation/extension/move)
for i := range overlaps {
schedTask := tasks[zoneSchedule[i]]
if schedTask.Priority <= task.Priority && schedTask.Status != "cancel" {
return order, fmt.Errorf("can't schedule task; overlap in zone %s with task with priority %d %s (%s, critical: %v), %v-%v", zone, schedTask.Priority, schedTask.ID, schedTask.Type, schedTask.Critical, schedTask.StartDatetime, schedTask.StartDatetime.Add(schedTask.Duration))
}
}
// no priority overlaps; cancel less prioritized overlapping tasks
for i := range overlaps {
order.cancelTaskIds = append(order.cancelTaskIds, tasks[zoneSchedule[i]].ID)
}
}
order.addIdx = startIdx
return order, nil
}
func availableTimespan(task *Task) error {
orders := []Order{}
for _, zone := range task.Zones {
order, err := availablePrioritizedTimespan(task, zone)
if err != nil {
return err
}
orders = append(orders, order)
}
fmt.Println(orders)
for _, order := range orders {
log.Debug("Executing order in zone ", order.zone)
executeOrder(order)
}
return nil
}
func scheduleTask(task *Task) error {
status := task.Status
task.Status = "cancel"
err := availableTimeZone(task)
if err != nil {
return err
}
err = availableTimespan(task)
if err != nil {
return err
}
task.Status = status
return nil
}
func reschedule() error {
// TODO: rescheduler "cancels" all tasks and recreates schedule
return nil
}