Commit 73fc64e3 authored by Birger Schmidt's avatar Birger Schmidt

refactor timezone support for OffPeakTimePreriods

parent c62162f9
......@@ -81,7 +81,7 @@ type DockerMachine struct {
MachineOptions []string `long:"machine-options" env:"MACHINE_OPTIONS" description:"Additional machine creation options"`
OffPeakPeriods []string `long:"off-peak-periods" env:"MACHINE_OFF_PEAK_PERIODS" description:"Time periods when the scheduler is in the OffPeak mode"`
OffPeakTimezone string `long:"off-peak-timezone" env:"MACHINE_OFF_PEAK_TIMEZONE" description:"Timezone for the OffPeak periods (defaults to local)"`
OffPeakTimezone string `long:"off-peak-timezone" env:"MACHINE_OFF_PEAK_TIMEZONE" description:"Timezone for the OffPeak periods (defaults to Local)"`
OffPeakIdleCount int `long:"off-peak-idle-count" env:"MACHINE_OFF_PEAK_IDLE_COUNT" description:"Maximum idle machines when the scheduler is in the OffPeak mode"`
OffPeakIdleTime int `long:"off-peak-idle-time" env:"MACHINE_OFF_PEAK_IDLE_TIME" description:"Minimum time after machine can be destroyed when the scheduler is in the OffPeak mode"`
......@@ -262,18 +262,13 @@ func (c *DockerMachine) isOffPeak() bool {
c.CompileOffPeakPeriods()
}
return c.offPeakTimePeriods != nil && c.offPeakTimePeriods.InPeriod(c.OffPeakTimezone)
return c.offPeakTimePeriods != nil && c.offPeakTimePeriods.InPeriod()
}
func (c *DockerMachine) CompileOffPeakPeriods() (err error) {
c.offPeakTimePeriods, err = timeperiod.TimePeriods(c.OffPeakPeriods)
c.offPeakTimePeriods, err = timeperiod.TimePeriods(c.OffPeakPeriods, c.OffPeakTimezone)
if err != nil {
err = errors.New(fmt.Sprint("Invalid OffPeakPeriods value: ", err))
return
}
_, err = time.LoadLocation(c.OffPeakTimezone)
if err != nil {
err = errors.New(fmt.Sprint("Invalid OffPeakTimeZone value: ", err))
}
return
......
......@@ -294,8 +294,9 @@ from 12am to 9am and from 6pm to 11pm and whole weekend days. Machines
scheduler is checking all patterns from the array and if at least one of
them describes current time, then the _Off Peak_ time mode is enabled.
You can specify the `OffPeakTimezone`. If you don't, the system setting
of the host machine of every runner will be used.
You can specify the `OffPeakTimezone` e.g. `"Australia/Sydney"`. If you don't,
the system setting of the host machine of every runner will be used. This
default can be stated as `OffPeakTimezone = "Local"` explicitly if you wish.
When the _Off Peak_ time mode is enabled machines scheduler use
`OffPeakIdleCount` instead of `IdleCount` setting and `OffPeakIdleTime`
......
package timeperiod
import (
"fmt"
"time"
"github.com/gorhill/cronexpr"
......@@ -9,22 +8,12 @@ import (
type TimePeriod struct {
expressions []*cronexpr.Expression
location *time.Location
GetCurrentTime func() time.Time
}
func (t *TimePeriod) InPeriod(timezone string) bool {
// if not set, default to system setting (the empty string would mean UTC)
if timezone == "" {
timezone = "Local"
}
location, err := time.LoadLocation(timezone)
if err != nil {
// I don't want this function to return an error code.
// The validity of the input should already be checked on config load.
// But to be sure and able to test, we have this here.
panic(fmt.Sprint("Invalid OffPeakTimeZone value: ", err))
}
now := t.GetCurrentTime().In(location)
func (t *TimePeriod) InPeriod() bool {
now := t.GetCurrentTime().In(t.location)
for _, expression := range t.expressions {
nextIn := expression.Next(now)
timeSince := now.Sub(nextIn)
......@@ -36,7 +25,7 @@ func (t *TimePeriod) InPeriod(timezone string) bool {
return false
}
func TimePeriods(periods []string) (*TimePeriod, error) {
func TimePeriods(periods []string, timezone string) (*TimePeriod, error) {
var expressions []*cronexpr.Expression
for _, period := range periods {
......@@ -48,8 +37,18 @@ func TimePeriods(periods []string) (*TimePeriod, error) {
expressions = append(expressions, expression)
}
// if not set, default to system setting (the empty string would mean UTC)
if timezone == "" {
timezone = "Local"
}
location, err := time.LoadLocation(timezone)
if err != nil {
return nil, err
}
timePeriod := &TimePeriod{
expressions: expressions,
location: location,
GetCurrentTime: func() time.Time { return time.Now() },
}
......
......@@ -26,29 +26,27 @@ func newTimePeriods(t *testing.T) (time.Time, *TimePeriod) {
dayName := daysOfWeek[day]
periodPattern := fmt.Sprintf("* %d %d * * %s *", minute, hour, dayName)
timePeriods, err := TimePeriods([]string{periodPattern})
timePeriods, err := TimePeriods([]string{periodPattern}, "Europe/Berlin")
assert.NoError(t, err)
return now, timePeriods
}
func TestInvalidTimezone(t *testing.T) {
_, err := TimePeriods([]string{}, "InvalidTimezone/String")
assert.Error(t, err)
}
func TestOffPeakPeriod(t *testing.T) {
timePeriods, _ := TimePeriods([]string{"* * 10-17 * * * *"})
timePeriods, _ := TimePeriods([]string{"* * 10-17 * * * *"}, "Europe/Berlin")
timePeriods.GetCurrentTime = func() time.Time {
return time.Date(2017, time.January, 1, 16, 30, 0, 0, time.UTC)
}
assert.True(t, timePeriods.InPeriod("Europe/Berlin"), "2017-01-01 16:30:00 UTC (no DST time in Europe/Berlin, so the time is +1h to UTC = 17:30 - which is inside of '* * 10-17 * * * *')")
assert.True(t, timePeriods.InPeriod(), "2017-01-01 16:30:00 UTC (no DST time in Europe/Berlin, so the time is +1h to UTC = 17:30 - which is inside of '* * 10-17 * * * *')")
timePeriods.GetCurrentTime = func() time.Time {
return time.Date(2017, time.July, 1, 16, 30, 0, 0, time.UTC)
}
assert.False(t, timePeriods.InPeriod("Europe/Berlin"), "2017-07-01 16:30:00 UTC (DST time in Europe/Berlin, so the time is +2h to UTC = 18:30 - which is outside of '* * 10-17 * * * *')")
}
func TestWrongTimezone(t *testing.T) {
timePeriods, _ := TimePeriods([]string{})
assert.Panics(t, func() {
_ = timePeriods.InPeriod("NoValidTimezone/String")
}, "Calling InPeriod(\"NoValidTimezone/String\") should panic")
assert.False(t, timePeriods.InPeriod(), "2017-07-01 16:30:00 UTC (DST time in Europe/Berlin, so the time is +2h to UTC = 18:30 - which is outside of '* * 10-17 * * * *')")
}
func TestInPeriod(t *testing.T) {
......@@ -56,7 +54,7 @@ func TestInPeriod(t *testing.T) {
timePeriods.GetCurrentTime = func() time.Time {
return now
}
assert.True(t, timePeriods.InPeriod("Local"))
assert.True(t, timePeriods.InPeriod())
}
func TestPeriodOut(t *testing.T) {
......@@ -64,17 +62,17 @@ func TestPeriodOut(t *testing.T) {
timePeriods.GetCurrentTime = func() time.Time {
return now.Add(time.Hour * 48)
}
assert.False(t, timePeriods.InPeriod("Local"))
assert.False(t, timePeriods.InPeriod())
now, timePeriods = newTimePeriods(t)
timePeriods.GetCurrentTime = func() time.Time {
return now.Add(time.Hour * 4)
}
assert.False(t, timePeriods.InPeriod("Local"))
assert.False(t, timePeriods.InPeriod())
now, timePeriods = newTimePeriods(t)
timePeriods.GetCurrentTime = func() time.Time {
return now.Add(time.Minute * 4)
}
assert.False(t, timePeriods.InPeriod("Local"))
assert.False(t, timePeriods.InPeriod())
}
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