mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-10 19:50:30 +00:00
This adds the initial version of ScheduledOverrides to HorizontalRunnerAutoscaler. `MinReplicas` overriding should just work. When there are two or more ScheduledOverrides, the earliest one that matched is activated. Each ScheduledOverride can be recurring or one-time. If you have two or more ScheduledOverrides, only one of them should be one-time. And the one-time override should be the earliest item in the list to make sense. Tests will be added in another commit. Logging improvements and additional observability in HRA.Status will also be added in yet another commits. Ref #484
123 lines
3.0 KiB
Go
123 lines
3.0 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/teambition/rrule-go"
|
|
)
|
|
|
|
type RecurrenceRule struct {
|
|
Frequency string
|
|
UntilTime time.Time
|
|
}
|
|
|
|
type Period struct {
|
|
StartTime time.Time
|
|
EndTime time.Time
|
|
}
|
|
|
|
func (r *Period) String() string {
|
|
if r == nil {
|
|
return ""
|
|
}
|
|
|
|
return r.StartTime.Format(time.RFC3339) + "-" + r.EndTime.Format(time.RFC3339)
|
|
}
|
|
|
|
func MatchSchedule(now time.Time, startTime, endTime time.Time, recurrenceRule RecurrenceRule) (*Period, *Period, error) {
|
|
return calculateActiveAndUpcomingRecurringPeriods(
|
|
now,
|
|
startTime,
|
|
endTime,
|
|
recurrenceRule.Frequency,
|
|
recurrenceRule.UntilTime,
|
|
)
|
|
}
|
|
|
|
func calculateActiveAndUpcomingRecurringPeriods(now, startTime, endTime time.Time, frequency string, untilTime time.Time) (*Period, *Period, error) {
|
|
var freqValue rrule.Frequency
|
|
|
|
var freqDurationDay int
|
|
var freqDurationMonth int
|
|
var freqDurationYear int
|
|
|
|
switch frequency {
|
|
case "Daily":
|
|
freqValue = rrule.DAILY
|
|
freqDurationDay = 1
|
|
case "Weekly":
|
|
freqValue = rrule.WEEKLY
|
|
freqDurationDay = 7
|
|
case "Monthly":
|
|
freqValue = rrule.MONTHLY
|
|
freqDurationMonth = 1
|
|
case "Yearly":
|
|
freqValue = rrule.YEARLY
|
|
freqDurationYear = 1
|
|
case "":
|
|
if now.Before(startTime) {
|
|
return nil, &Period{StartTime: startTime, EndTime: endTime}, nil
|
|
}
|
|
|
|
if now.Before(endTime) {
|
|
return &Period{StartTime: startTime, EndTime: endTime}, nil, nil
|
|
}
|
|
|
|
return nil, nil, nil
|
|
default:
|
|
return nil, nil, fmt.Errorf(`invalid freq %q: It must be one of "Daily", "Weekly", "Monthly", and "Yearly"`, frequency)
|
|
}
|
|
|
|
freqDurationLater := time.Date(
|
|
now.Year()+freqDurationYear,
|
|
time.Month(int(now.Month())+freqDurationMonth),
|
|
now.Day()+freqDurationDay,
|
|
now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), now.Location(),
|
|
)
|
|
|
|
freqDuration := freqDurationLater.Sub(now)
|
|
|
|
overrideDuration := endTime.Sub(startTime)
|
|
if overrideDuration > freqDuration {
|
|
return nil, nil, fmt.Errorf("override's duration %s must be equal to sor shorter than the duration implied by freq %q (%s)", overrideDuration, frequency, freqDuration)
|
|
}
|
|
|
|
rrule, err := rrule.NewRRule(rrule.ROption{
|
|
Freq: freqValue,
|
|
Dtstart: startTime,
|
|
Until: untilTime,
|
|
})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
overrideDurationBefore := now.Add(-overrideDuration + 1)
|
|
activeOverrideStarts := rrule.Between(overrideDurationBefore, now, true)
|
|
|
|
var active *Period
|
|
|
|
if len(activeOverrideStarts) > 1 {
|
|
return nil, nil, fmt.Errorf("[bug] unexpted number of active overrides found: %v", activeOverrideStarts)
|
|
} else if len(activeOverrideStarts) == 1 {
|
|
active = &Period{
|
|
StartTime: activeOverrideStarts[0],
|
|
EndTime: activeOverrideStarts[0].Add(overrideDuration),
|
|
}
|
|
}
|
|
|
|
oneSecondLater := now.Add(1)
|
|
upcomingOverrideStarts := rrule.Between(oneSecondLater, freqDurationLater, true)
|
|
|
|
var next *Period
|
|
|
|
if len(upcomingOverrideStarts) > 0 {
|
|
next = &Period{
|
|
StartTime: upcomingOverrideStarts[0],
|
|
EndTime: upcomingOverrideStarts[0].Add(overrideDuration),
|
|
}
|
|
}
|
|
|
|
return active, next, nil
|
|
}
|