From 5538d22de5aa8ba5774fd596fa107b74868a9f8e Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Fri, 13 Sep 2024 10:32:16 +0200 Subject: [PATCH] fix: refactored layer incompatiblility testing --- README.md | 29 +++++---- cmd/kubedownscaler/main.go | 8 +-- internal/api/kubernetes/resourceLogger.go | 8 +-- internal/pkg/values/layer.go | 21 ++----- internal/pkg/values/layer_test.go | 38 ++++++------ internal/pkg/values/timespan.go | 72 +++++++++++++---------- internal/pkg/values/timespan_test.go | 60 +++++++++---------- internal/pkg/values/util.go | 46 ++++++++------- 8 files changed, 147 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index 2316c6e..232a3b0 100644 --- a/README.md +++ b/README.md @@ -222,16 +222,25 @@ Mon-Tue 20:00-00:00 Europe/Amsterdam # On Monday and Tuesday: from 20:00 to midn Valid Values: -- Weekdays: (case-insensitive) - - Mon - - Tue - - Wed - - Thu - - Fri - - Sat - - Sun -- Timezones: all from the [IANA Time Zone database](https://www.iana.org/time-zones) -- Time of day: 00:00 - 24:00 +Weekdays: (case-insensitive) + +- Mon +- Tue +- Wed +- Thu +- Fri +- Sat +- Sun + +Timezones: + +- all from the [IANA Time Zone database](https://www.iana.org/time-zones) + +> [!Note] +> The IANA Time Zone database mainly supports regional/city timezones (example: `Europe/Berlin`, `America/Los_Angeles`) instead of abbreviations (example: `CEST`, `PST`, `PDT`). +> It supports some abbreviations like `CET`, `MET` and `PST8PDT` but these (not including `UTC`) shouldn't be used, and only exist for backwards compatibility. + +Time of day: 00:00 - 24:00 #### Multiple/Complex Timespans diff --git a/cmd/kubedownscaler/main.go b/cmd/kubedownscaler/main.go index 76968ee..fe791ac 100644 --- a/cmd/kubedownscaler/main.go +++ b/cmd/kubedownscaler/main.go @@ -71,6 +71,10 @@ func main() { if debug { slog.SetLogLoggerLevel(slog.LevelDebug) } + if err := layerCli.CheckForIncompatibleFields(); err != nil { + slog.Error("found incompatible fields", "error", err) + os.Exit(1) + } ctx := context.Background() client, err := kubernetes.NewClient(kubeconfig) @@ -147,10 +151,6 @@ func scanWorkload(workload scalable.Workload, client kubernetes.Client, ctx cont slog.Error("failed to get current scaling for workload", "error", err, "workload", workload.GetName(), "namespace", workload.GetNamespace()) return false } - if scaling == values.ScalingIncompatible { - slog.Error("scaling is incompatible, skipping", "workload", workload.GetName(), "namespace", workload.GetNamespace()) - return false - } if scaling == values.ScalingIgnore { slog.Debug("scaling is ignored, skipping", "workload", workload.GetName(), "namespace", workload.GetNamespace()) return true diff --git a/internal/api/kubernetes/resourceLogger.go b/internal/api/kubernetes/resourceLogger.go index 963c6c0..13a26e4 100644 --- a/internal/api/kubernetes/resourceLogger.go +++ b/internal/api/kubernetes/resourceLogger.go @@ -7,7 +7,7 @@ import ( "github.com/caas-team/gokubedownscaler/internal/pkg/scalable" ) -const reasonInvalidAnnotation = "InvalidAnnotation" +const reasonInvalidConfiguration = "InvalidConfiguration" func NewResourceLogger(client Client, workload scalable.Workload) resourceLogger { logger := resourceLogger{ @@ -22,9 +22,9 @@ type resourceLogger struct { client Client } -// ErrorInvalidAnnotation adds an error on the resource -func (r resourceLogger) ErrorInvalidAnnotation(id, message string, ctx context.Context) { - err := r.client.AddErrorEvent(reasonInvalidAnnotation, id, message, r.workload, ctx) +// ErrorInvalidConfiguration adds an error on the resource +func (r resourceLogger) ErrorInvalidConfiguration(id, message string, ctx context.Context) { + err := r.client.AddErrorEvent(reasonInvalidConfiguration, id, message, r.workload, ctx) if err != nil { slog.Error("failed to add error event to workload", "workload", r.workload.GetName(), "error", err) return diff --git a/internal/pkg/values/layer.go b/internal/pkg/values/layer.go index 519e1c9..cc24148 100644 --- a/internal/pkg/values/layer.go +++ b/internal/pkg/values/layer.go @@ -2,7 +2,6 @@ package values import ( "errors" - "fmt" "time" ) @@ -21,11 +20,10 @@ const Undefined = -1 // Undefined represents an undefined integer value type scaling int const ( - scalingNone scaling = iota // no scaling set in this layer, go to next layer - ScalingIncompatible // incompatible scaling fields set, error - ScalingIgnore // not scaling - ScalingDown // scaling down - ScalingUp // scaling up + scalingNone scaling = iota // no scaling set in this layer, go to next layer + ScalingIgnore // not scaling + ScalingDown // scaling down + ScalingUp // scaling up ) // NewLayer gets a new layer with the default values @@ -61,8 +59,8 @@ func (l Layer) isScalingExcluded() *bool { return nil } -// checkForIncompatibleFields checks if there are incompatible fields -func (l Layer) checkForIncompatibleFields() error { +// CheckForIncompatibleFields checks if there are incompatible fields +func (l Layer) CheckForIncompatibleFields() error { // force down and uptime if l.ForceDowntime.isSet && l.ForceDowntime.value && @@ -140,13 +138,6 @@ type Layers []Layer // GetCurrentScaling gets the current scaling of the first layer that implements scaling func (l Layers) GetCurrentScaling() (scaling, error) { - // check for incompatibilities - for _, layer := range l { - err := layer.checkForIncompatibleFields() - if err != nil { - return ScalingIncompatible, fmt.Errorf("error found incompatible fields: %w", err) - } - } // check for forced scaling for _, layer := range l { forcedScaling := layer.getForcedScaling() diff --git a/internal/pkg/values/layer_test.go b/internal/pkg/values/layer_test.go index d00d9ac..6d8edcc 100644 --- a/internal/pkg/values/layer_test.go +++ b/internal/pkg/values/layer_test.go @@ -31,51 +31,51 @@ func TestLayer_checkForIncompatibleFields(t *testing.T) { { name: "up- and downtime", layer: Layer{ - UpTime: timeSpans{relativeTimeSpan{}}, - DownTime: timeSpans{relativeTimeSpan{}}, + UpTime: timeSpans{&relativeTimeSpan{}}, + DownTime: timeSpans{&relativeTimeSpan{}}, }, wantErr: true, }, { name: "uptime an upscaleperiod", layer: Layer{ - UpTime: timeSpans{relativeTimeSpan{}}, - UpscalePeriod: timeSpans{relativeTimeSpan{}}, + UpTime: timeSpans{&relativeTimeSpan{}}, + UpscalePeriod: timeSpans{&relativeTimeSpan{}}, }, wantErr: true, }, { name: "uptime an downscaleperiod", layer: Layer{ - UpTime: timeSpans{relativeTimeSpan{}}, - DownscalePeriod: timeSpans{relativeTimeSpan{}}, + UpTime: timeSpans{&relativeTimeSpan{}}, + DownscalePeriod: timeSpans{&relativeTimeSpan{}}, }, wantErr: true, }, { name: "downtime an upscaleperiod", layer: Layer{ - DownTime: timeSpans{relativeTimeSpan{}}, - UpscalePeriod: timeSpans{relativeTimeSpan{}}, + DownTime: timeSpans{&relativeTimeSpan{}}, + UpscalePeriod: timeSpans{&relativeTimeSpan{}}, }, wantErr: true, }, { name: "downtime an downscaleperiod", layer: Layer{ - DownTime: timeSpans{relativeTimeSpan{}}, - DownscalePeriod: timeSpans{relativeTimeSpan{}}, + DownTime: timeSpans{&relativeTimeSpan{}}, + DownscalePeriod: timeSpans{&relativeTimeSpan{}}, }, wantErr: true, }, { name: "down- and upscale periods overlapping", layer: Layer{ - DownscalePeriod: timeSpans{absoluteTimeSpan{ + DownscalePeriod: timeSpans{&absoluteTimeSpan{ from: time.Now(), to: time.Now().Add(1 * time.Hour), }}, - UpscalePeriod: timeSpans{absoluteTimeSpan{ // overlapping + UpscalePeriod: timeSpans{&absoluteTimeSpan{ // overlapping from: time.Now(), to: time.Now().Add(1 * time.Hour), }}, @@ -85,11 +85,11 @@ func TestLayer_checkForIncompatibleFields(t *testing.T) { { name: "down- and upscale do not overlap", layer: Layer{ - DownscalePeriod: timeSpans{absoluteTimeSpan{ + DownscalePeriod: timeSpans{&absoluteTimeSpan{ from: time.Now(), to: time.Now().Add(time.Hour), }}, - UpscalePeriod: timeSpans{absoluteTimeSpan{ + UpscalePeriod: timeSpans{&absoluteTimeSpan{ from: time.Now().Add(2 * time.Hour), to: time.Now().Add(3 * time.Hour), }}, @@ -99,8 +99,8 @@ func TestLayer_checkForIncompatibleFields(t *testing.T) { { name: "valid", layer: Layer{ - DownTime: timeSpans{relativeTimeSpan{}}, - DownscalePeriod: timeSpans{relativeTimeSpan{}}, + DownTime: timeSpans{&relativeTimeSpan{}}, + DownscalePeriod: timeSpans{&relativeTimeSpan{}}, }, wantErr: true, }, @@ -108,7 +108,7 @@ func TestLayer_checkForIncompatibleFields(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - err := test.layer.checkForIncompatibleFields() + err := test.layer.CheckForIncompatibleFields() if test.wantErr { assert.Error(t, err) } else { @@ -120,11 +120,11 @@ func TestLayer_checkForIncompatibleFields(t *testing.T) { func TestLayer_getCurrentScaling(t *testing.T) { var ( - inTimeSpan = timeSpans{absoluteTimeSpan{ + inTimeSpan = timeSpans{&absoluteTimeSpan{ from: time.Now().Add(-time.Hour), to: time.Now().Add(time.Hour), }} - outOfTimeSpan = timeSpans{absoluteTimeSpan{ + outOfTimeSpan = timeSpans{&absoluteTimeSpan{ from: time.Now().Add(-2 * time.Hour), to: time.Now().Add(-time.Hour), }} diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index 666bd4a..a4f31fb 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -51,7 +51,7 @@ func (t *timeSpans) Set(value string) error { if err != nil { return fmt.Errorf("failed to parse absolute timespan: %w", err) } - timespans = append(timespans, timespan) + timespans = append(timespans, ×pan) continue } @@ -143,7 +143,7 @@ type relativeTimeSpan struct { } // isWeekdayInRange checks if the weekday falls into the weekday range -func (t relativeTimeSpan) isWeekdayInRange(weekday time.Weekday) bool { +func (t *relativeTimeSpan) isWeekdayInRange(weekday time.Weekday) bool { if t.weekdayFrom <= t.weekdayTo { // check if range wraps across weeks return weekday >= t.weekdayFrom && weekday <= t.weekdayTo } @@ -151,7 +151,7 @@ func (t relativeTimeSpan) isWeekdayInRange(weekday time.Weekday) bool { } // isTimeOfDayInRange checks if the time falls into the time of day range -func (t relativeTimeSpan) isTimeOfDayInRange(timeOfDay time.Time) bool { +func (t *relativeTimeSpan) isTimeOfDayInRange(timeOfDay time.Time) bool { if t.timeFrom.After(t.timeTo) { // check if range wraps across days return timeOfDay.After(t.timeFrom) || timeOfDay.Equal(t.timeFrom) || timeOfDay.Before(t.timeTo) } @@ -159,7 +159,7 @@ func (t relativeTimeSpan) isTimeOfDayInRange(timeOfDay time.Time) bool { } // isTimeInSpan checks if the time is in the span -func (t relativeTimeSpan) isTimeInSpan(targetTime time.Time) bool { +func (t *relativeTimeSpan) isTimeInSpan(targetTime time.Time) bool { targetTime = targetTime.In(t.timezone) timeOfDay := getTimeOfDay(targetTime) weekday := targetTime.Weekday() @@ -167,46 +167,56 @@ func (t relativeTimeSpan) isTimeInSpan(targetTime time.Time) bool { } // inLocation returns an array of relative timespans matching the timespan converted to the given location -func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan { +func (t *relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan { var result []relativeTimeSpan - sameDays := relativeTimeSpan{ + sameWeedays := relativeTimeSpan{ timezone: timezone, weekdayFrom: t.weekdayFrom, weekdayTo: t.weekdayTo, timeFrom: t.timeFrom.In(timezone), timeTo: t.timeTo.In(timezone), } - result = append(result, sameDays) - if isTimeFromSkippedToPreviousDay(sameDays.timeFrom) { - daysBefore := relativeTimeSpan{ + result = append(result, sameWeedays) + if sameWeedays.overlapsIntoPreviousDay() { + weekdaysBefore := relativeTimeSpan{ timezone: timezone, - timeFrom: sameDays.timeFrom.Add(24 * time.Hour), - timeTo: sameDays.timeTo.Add(24 * time.Hour), - weekdayFrom: getWeekdayBefore(sameDays.weekdayFrom), - weekdayTo: getWeekdayBefore(sameDays.weekdayTo), + timeFrom: sameWeedays.timeFrom.Add(24 * time.Hour), + timeTo: sameWeedays.timeTo.Add(24 * time.Hour), + weekdayFrom: getWeekdayBefore(sameWeedays.weekdayFrom), + weekdayTo: getWeekdayBefore(sameWeedays.weekdayTo), } - result = append(result, daysBefore) + result = append(result, weekdaysBefore) } - if isTimeToSkippedToNextDay(sameDays.timeTo) { - daysAfter := relativeTimeSpan{ + if sameWeedays.overlapsIntoNextDay() { + weekdaysAfter := relativeTimeSpan{ timezone: timezone, - timeFrom: sameDays.timeFrom.Add(-24 * time.Hour), - timeTo: sameDays.timeTo.Add(-24 * time.Hour), - weekdayFrom: getWeekdayAfter(sameDays.weekdayFrom), - weekdayTo: getWeekdayAfter(sameDays.weekdayTo), + timeFrom: sameWeedays.timeFrom.Add(-24 * time.Hour), + timeTo: sameWeedays.timeTo.Add(-24 * time.Hour), + weekdayFrom: getWeekdayAfter(sameWeedays.weekdayFrom), + weekdayTo: getWeekdayAfter(sameWeedays.weekdayTo), } - result = append(result, daysAfter) + result = append(result, weekdaysAfter) } return result } +// overlapsIntoPreviousDay checks if timeFrom overlaps into the previous day +func (r *relativeTimeSpan) overlapsIntoPreviousDay() bool { + return r.timeFrom.Year() == -1 +} + +// overlapsIntoNextDay checks if timeTo overlaps into the following day +func (r *relativeTimeSpan) overlapsIntoNextDay() bool { + return asExclusiveTimestamp(r.timeTo).Day() == 2 +} + type absoluteTimeSpan struct { from time.Time to time.Time } // isTimeInSpan check if the time is in the span -func (t absoluteTimeSpan) isTimeInSpan(targetTime time.Time) bool { +func (t *absoluteTimeSpan) isTimeInSpan(targetTime time.Time) bool { return (t.from.Before(targetTime) || t.from.Equal(targetTime)) && t.to.After(targetTime) } @@ -268,18 +278,18 @@ func getTimeOfDay(targetTime time.Time) time.Time { // doTimespansOverlap checks if the given timespans overlap with each other func doTimespansOverlap(span1, span2 TimeSpan) bool { switch s1 := span1.(type) { - case absoluteTimeSpan: - if s2, ok := span2.(absoluteTimeSpan); ok { - return absAndAbsOverlap(s1, s2) + case *absoluteTimeSpan: + if s2, ok := span2.(*absoluteTimeSpan); ok { + return absAndAbsOverlap(*s1, *s2) } - return relAndAbsOverlap(span2.(relativeTimeSpan), s1) - case relativeTimeSpan: - if s2, ok := span2.(absoluteTimeSpan); ok { - return relAndAbsOverlap(s1, s2) + return relAndAbsOverlap(*span2.(*relativeTimeSpan), *s1) + case *relativeTimeSpan: + if s2, ok := span2.(*absoluteTimeSpan); ok { + return relAndAbsOverlap(*s1, *s2) } - return relAndRelOverlap(s1, span2.(relativeTimeSpan)) + return relAndRelOverlap(*s1, *span2.(*relativeTimeSpan)) } - return false // this shouldn't ever be reached + panic(fmt.Sprintf("Fatal error, the timespan does not match any of the known types. This should never happen! Type: %T", span1)) } // relAndRelOverlap checks if two relative timespans overlap diff --git a/internal/pkg/values/timespan_test.go b/internal/pkg/values/timespan_test.go index 777edbb..8357728 100644 --- a/internal/pkg/values/timespan_test.go +++ b/internal/pkg/values/timespan_test.go @@ -332,14 +332,14 @@ var doTimespansOverlapTests = []struct { }{ { name: "rel rel overlap", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Friday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: relativeTimeSpan{ + span2: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Monday, @@ -350,14 +350,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel rel dont overlap", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Friday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: relativeTimeSpan{ + span2: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Tuesday, weekdayTo: time.Tuesday, @@ -368,11 +368,11 @@ var doTimespansOverlapTests = []struct { }, { name: "abs abs overlap", - span1: absoluteTimeSpan{ // all of January 2024 + span1: &absoluteTimeSpan{ // all of January 2024 from: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), // from 1st of January to: time.Date(2024, time.February, 1, 0, 0, 0, 0, time.UTC), // to 1st of February }, - span2: absoluteTimeSpan{ // from the 10th of Janurary 2024 until the end of the 19th of Janurary 2024 + span2: &absoluteTimeSpan{ // from the 10th of Janurary 2024 until the end of the 19th of Janurary 2024 from: time.Date(2024, time.January, 10, 0, 0, 0, 0, time.UTC), // from 10th of January to: time.Date(2024, time.January, 20, 0, 0, 0, 0, time.UTC), // to 20th of January }, @@ -380,11 +380,11 @@ var doTimespansOverlapTests = []struct { }, { name: "abs abs dont overlap", - span1: absoluteTimeSpan{ // all of January 2024 + span1: &absoluteTimeSpan{ // all of January 2024 from: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), // from 1st of January to: time.Date(2024, time.February, 1, 0, 0, 0, 0, time.UTC), // to 1st of February }, - span2: absoluteTimeSpan{ // all of February 2024 + span2: &absoluteTimeSpan{ // all of February 2024 from: time.Date(2024, time.February, 10, 0, 0, 0, 0, time.UTC), // from 1st of February to: time.Date(2024, time.March, 20, 0, 0, 0, 0, time.UTC), // to 1st of March }, @@ -392,14 +392,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel abs overlap", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Friday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: absoluteTimeSpan{ // all of January 2024 + span2: &absoluteTimeSpan{ // all of January 2024 from: time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC), // from Monday 1st of January to: time.Date(2024, time.February, 1, 0, 0, 0, 0, time.UTC), // to Thursday 1st of Feburary }, @@ -407,14 +407,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel abs dont overlap", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Friday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: absoluteTimeSpan{ // the entire day on 6th of January 2024 (Saturday) + span2: &absoluteTimeSpan{ // the entire day on 6th of January 2024 (Saturday) from: time.Date(2024, time.January, 6, 0, 0, 0, 0, time.UTC), // from Saturday 6th of January to: time.Date(2024, time.January, 7, 0, 0, 0, 0, time.UTC), // to Sunday 7th of January }, @@ -422,14 +422,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel rel overlap different timezones", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Friday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: relativeTimeSpan{ + span2: &relativeTimeSpan{ timezone: UTC1, weekdayFrom: time.Monday, weekdayTo: time.Friday, @@ -440,14 +440,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel rel dont overlap different timezones", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Friday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: relativeTimeSpan{ + span2: &relativeTimeSpan{ timezone: UTC1, weekdayFrom: time.Monday, weekdayTo: time.Friday, @@ -458,14 +458,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel abs overlap different timezones", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Friday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: absoluteTimeSpan{ // from 20:00 UTC+1 Friday and on the entire Saturday + span2: &absoluteTimeSpan{ // from 20:00 UTC+1 Friday and on the entire Saturday from: time.Date(2024, time.January, 5, 20, 0, 0, 0, UTC1), // from 20:00 UTC+1 Friday 5th of January to: time.Date(2024, time.January, 7, 0, 0, 0, 0, UTC1), // to Sunday 7th of January }, @@ -473,14 +473,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel abs dont overlap different timezones", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Friday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: absoluteTimeSpan{ // the entire day on the 7th of Janurary 2024 and on Monday the 8th of Janurary at 9:00 UTC+1 + span2: &absoluteTimeSpan{ // the entire day on the 7th of Janurary 2024 and on Monday the 8th of Janurary at 9:00 UTC+1 from: time.Date(2024, time.January, 7, 0, 0, 0, 0, UTC1), // from Sunday 7th of January to: time.Date(2024, time.January, 8, 9, 0, 0, 0, UTC1), // to 9:00 UTC+1 Monday 8th of January }, @@ -488,14 +488,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel abs overlap reverse weekdays", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Sunday, timeFrom: zeroTime.Add(8 * time.Hour), timeTo: zeroTime.Add(20 * time.Hour), }, - span2: absoluteTimeSpan{ // from 20:00 Friday until the next day 8:00 + span2: &absoluteTimeSpan{ // from 20:00 Friday until the next day 8:00 from: time.Date(2024, time.January, 5, 20, 0, 0, 0, time.UTC), // from 20:00 Friday 5th of January to: time.Date(2024, time.January, 6, 8, 0, 0, 0, time.UTC), // to 8:00 Saturday 6th of January }, @@ -503,14 +503,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rev-rel rev-rel overlap", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Monday, timeFrom: zeroTime.Add(20 * time.Hour), timeTo: zeroTime.Add(1 * time.Hour), }, - span2: relativeTimeSpan{ + span2: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Monday, weekdayTo: time.Monday, @@ -521,14 +521,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel abs overlap different timezones rel split days", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Tuesday, weekdayTo: time.Tuesday, timeFrom: zeroTime.Add(0 * time.Hour), timeTo: zeroTime.Add(24 * time.Hour), }, - span2: absoluteTimeSpan{ // the entire day on Wednesday the 3rd of January 2024 UTC+1 + span2: &absoluteTimeSpan{ // the entire day on Wednesday the 3rd of January 2024 UTC+1 from: time.Date(2024, time.January, 3, 0, 0, 0, 0, UTC1), // from Wednesday 0:00 January 3rd of 2024 to: time.Date(2024, time.January, 4, 0, 0, 0, 0, UTC1), // to Thursday 0:00 January 4th of 2024 }, @@ -536,14 +536,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel rel overlap different timezones rel split days", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Tuesday, weekdayTo: time.Tuesday, timeFrom: zeroTime.Add(0 * time.Hour), timeTo: zeroTime.Add(24 * time.Hour), }, - span2: relativeTimeSpan{ + span2: &relativeTimeSpan{ timezone: UTC1, weekdayFrom: time.Wednesday, weekdayTo: time.Wednesday, @@ -554,14 +554,14 @@ var doTimespansOverlapTests = []struct { }, { name: "rel rel dont overlap different timezones rel split days", - span1: relativeTimeSpan{ + span1: &relativeTimeSpan{ timezone: UTC1, weekdayFrom: time.Wednesday, weekdayTo: time.Wednesday, timeFrom: zeroTimeUTC1.Add(0 * time.Hour), timeTo: zeroTimeUTC1.Add(8 * time.Hour), }, - span2: relativeTimeSpan{ + span2: &relativeTimeSpan{ timezone: time.UTC, weekdayFrom: time.Tuesday, weekdayTo: time.Tuesday, diff --git a/internal/pkg/values/util.go b/internal/pkg/values/util.go index 291171f..7bcec40 100644 --- a/internal/pkg/values/util.go +++ b/internal/pkg/values/util.go @@ -25,11 +25,13 @@ const ( envUptime = "DEFAULT_UPTIME" envDownscalePeriod = "DOWNSCALE_PERIOD" envDowntime = "DEFAULT_DOWNTIME" + + reasonIncompatible = "Incompatible Configuration" ) type resourceLogger interface { - // ErrorInvalidAnnotation adds an invalid annotation error on a resource - ErrorInvalidAnnotation(id string, message string, ctx context.Context) + // ErrorInvalidConfiguration adds an invalid configuration error on a resource + ErrorInvalidConfiguration(id string, message string, ctx context.Context) } // GetLayerFromAnnotations makes a layer and fills it with all values from the annotations @@ -40,74 +42,79 @@ func GetLayerFromAnnotations(annotations map[string]string, logEvent resourceLog if downscalePeriod, ok := annotations[annotationDownscalePeriod]; ok { err = result.DownscalePeriod.Set(downscalePeriod) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationDownscalePeriod, fmt.Sprintf("failed to parse %q annotaion: %s", annotationDownscalePeriod, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationDownscalePeriod, fmt.Sprintf("failed to parse %q annotaion: %s", annotationDownscalePeriod, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationDownscalePeriod, err) } } if downtime, ok := annotations[annotationDowntime]; ok { err = result.DownTime.Set(downtime) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationDowntime, fmt.Sprintf("failed to parse %q annotaion: %s", annotationDowntime, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationDowntime, fmt.Sprintf("failed to parse %q annotaion: %s", annotationDowntime, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationDowntime, err) } } if upscalePeriod, ok := annotations[annotationUpscalePeriod]; ok { err = result.UpscalePeriod.Set(upscalePeriod) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationUpscalePeriod, fmt.Sprintf("failed to parse %q annotaion: %s", annotationUpscalePeriod, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationUpscalePeriod, fmt.Sprintf("failed to parse %q annotaion: %s", annotationUpscalePeriod, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationUpscalePeriod, err) } } if uptime, ok := annotations[annotationUptime]; ok { err = result.UpTime.Set(uptime) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationUptime, fmt.Sprintf("failed to parse %q annotaion: %s", annotationUptime, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationUptime, fmt.Sprintf("failed to parse %q annotaion: %s", annotationUptime, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationUptime, err) } } if exclude, ok := annotations[annotationExclude]; ok { err = result.Exclude.Set(exclude) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationExclude, fmt.Sprintf("failed to parse %q annotaion: %s", annotationExclude, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationExclude, fmt.Sprintf("failed to parse %q annotaion: %s", annotationExclude, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationExclude, err) } } if excludeUntil, ok := annotations[annotationExcludeUntil]; ok { result.ExcludeUntil, err = time.Parse(time.RFC3339, excludeUntil) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationExcludeUntil, fmt.Sprintf("failed to parse %q annotaion: %s", annotationExcludeUntil, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationExcludeUntil, fmt.Sprintf("failed to parse %q annotaion: %s", annotationExcludeUntil, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationExcludeUntil, err) } } if forceUptime, ok := annotations[annotationForceUptime]; ok { err = result.ForceUptime.Set(forceUptime) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationForceUptime, fmt.Sprintf("failed to parse %q annotaion: %s", annotationForceUptime, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationForceUptime, fmt.Sprintf("failed to parse %q annotaion: %s", annotationForceUptime, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationForceUptime, err) } } if forceDowntime, ok := annotations[annotationForceDowntime]; ok { err = result.ForceDowntime.Set(forceDowntime) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationForceDowntime, fmt.Sprintf("failed to parse %q annotaion: %s", annotationForceDowntime, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationForceDowntime, fmt.Sprintf("failed to parse %q annotaion: %s", annotationForceDowntime, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationForceDowntime, err) } } if downscaleReplicas, ok := annotations[annotationDownscaleReplicas]; ok { result.DownscaleReplicas, err = strconv.Atoi(downscaleReplicas) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationDownscaleReplicas, fmt.Sprintf("failed to parse %q annotaion: %s", annotationDownscaleReplicas, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationDownscaleReplicas, fmt.Sprintf("failed to parse %q annotaion: %s", annotationDownscaleReplicas, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationDownscaleReplicas, err) } } if gracePeriod, ok := annotations[annotationGracePeriod]; ok { err = result.GracePeriod.Set(gracePeriod) if err != nil { - logEvent.ErrorInvalidAnnotation(annotationGracePeriod, fmt.Sprintf("failed to parse %q annotaion: %s", annotationGracePeriod, err.Error()), ctx) + logEvent.ErrorInvalidConfiguration(annotationGracePeriod, fmt.Sprintf("failed to parse %q annotaion: %s", annotationGracePeriod, err.Error()), ctx) return result, fmt.Errorf("failed to parse %q annotation: %w", annotationGracePeriod, err) } } + if err = result.CheckForIncompatibleFields(); err != nil { + logEvent.ErrorInvalidConfiguration(reasonIncompatible, fmt.Sprintf("found incompatible fields: %s", err.Error()), ctx) + return result, fmt.Errorf("error: found incompatible fields: %w", err) + } + return result, nil } @@ -141,6 +148,11 @@ func GetLayerFromEnv() (Layer, error) { if err != nil { return result, fmt.Errorf("error while getting %q environment variable: %w", envDowntime, err) } + + if err = result.CheckForIncompatibleFields(); err != nil { + return result, fmt.Errorf("error: found incompatible fields: %w", err) + } + return result, nil } @@ -149,16 +161,6 @@ func asExclusiveTimestamp(inc time.Time) time.Time { return inc.Add(-time.Nanosecond) } -// isTimeFromSkippedToPreviousDay checks if timeFrom skipped to the previous day -func isTimeFromSkippedToPreviousDay(timeFrom time.Time) bool { - return timeFrom.Year() == -1 -} - -// isTimeToSkippedToNextDay checks if timeTo skipped to the following day -func isTimeToSkippedToNextDay(timeTo time.Time) bool { - return asExclusiveTimestamp(timeTo).Day() == 2 -} - // getWeekdayBefore returns the day before (it automatically checks if value is below 0 or above 6) func getWeekdayBefore(weekday time.Weekday) time.Weekday { return (weekday + 6) % 7