From 2078f63c30c8bb61a4f22213355ed67bad04b0db Mon Sep 17 00:00:00 2001 From: Jonas Klug Date: Tue, 27 Aug 2024 22:36:52 +0200 Subject: [PATCH 01/16] added basic implementation of timespan check --- internal/pkg/values/timespan.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index f3da6fb..fc29052 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -195,3 +195,23 @@ func getTimeOfDay(targetTime time.Time) time.Time { targetTime.Location(), ) } + +func areTimespanOverlapped(relTime relativeTimeSpan, absTime absoluteTimeSpan) bool { + relTimeStart, relTimeEnd := convertRelativeToAbsolute(relTime, time.Now()) + + if absTime.from.Before(relTimeEnd) && relTimeStart.Before(absTime.to) { + return true + } else { + return false + } +} + +func convertRelativeToAbsolute(relTime relativeTimeSpan, reference time.Time) (time.Time, time.Time) { + // Calulating start and end date based on current date and relative timespan + year, month, day := reference.Date() + + start := time.Date(year, month, day+int((relTime.weekdayFrom-reference.Weekday()+7)%7), relTime.timeFrom.Hour(), relTime.timeFrom.Minute(), relTime.timeFrom.Second(), 0, relTime.timezone) + end := time.Date(year, month, day+int((relTime.weekdayTo-reference.Weekday()+7)%7), relTime.timeTo.Hour(), relTime.timeTo.Minute(), relTime.timeTo.Second(), 0, relTime.timezone) + + return start, end +} From 2cf38320c071b38ec8785ec487f9a6e3d4299523 Mon Sep 17 00:00:00 2001 From: Jonas Klug Date: Wed, 28 Aug 2024 09:38:31 +0200 Subject: [PATCH 02/16] added test for overlapping timespan check --- internal/pkg/values/timespan_test.go | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/internal/pkg/values/timespan_test.go b/internal/pkg/values/timespan_test.go index 7a7e9fd..a65742d 100644 --- a/internal/pkg/values/timespan_test.go +++ b/internal/pkg/values/timespan_test.go @@ -251,3 +251,33 @@ func TestAbsoluteTimeSpan_isTimeInSpan(t *testing.T) { }) } } + +func TestOverlappingTimespans(t *testing.T) { + tests := []struct { + name string + relTime relativeTimeSpan + absTime absoluteTimeSpan + wantedResult bool + }{ + { + name: "timespans are not overlapping", + relTime: relativeTimeSpan{time.UTC, time.Wednesday, time.Wednesday, time.Now(), time.Now().Add(5)}, + absTime: absoluteTimeSpan{time.Date(2024, time.August, 28, 12, 0, 0, 0, time.UTC), time.Date(2024, time.August, 28, 12, 5, 0, 0, time.UTC)}, + wantedResult: false, + }, + { + name: "timespans are overlapping", + relTime: relativeTimeSpan{time.UTC, time.Wednesday, time.Wednesday, + time.Date(2024, time.August, 28, 12, 0, 0, 0, time.UTC), time.Date(2024, time.August, 28, 12, 5, 0, 0, time.UTC)}, + absTime: absoluteTimeSpan{time.Date(2024, time.August, 28, 12, 0, 0, 0, time.UTC), + time.Date(2024, time.August, 28, 12, 3, 0, 0, time.UTC)}, + wantedResult: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotResult := areTimespanOverlapped(test.relTime, test.absTime) + assert.Equal(t, test.wantedResult, gotResult) + }) + } +} From 1a228c01038bba904a6498116a4dd338994f946f Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Wed, 28 Aug 2024 10:06:22 +0200 Subject: [PATCH 03/16] test: changed test to reflect wanted outcome --- internal/pkg/values/timespan_test.go | 98 ++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/internal/pkg/values/timespan_test.go b/internal/pkg/values/timespan_test.go index a65742d..44e1826 100644 --- a/internal/pkg/values/timespan_test.go +++ b/internal/pkg/values/timespan_test.go @@ -255,28 +255,104 @@ func TestAbsoluteTimeSpan_isTimeInSpan(t *testing.T) { func TestOverlappingTimespans(t *testing.T) { tests := []struct { name string - relTime relativeTimeSpan - absTime absoluteTimeSpan + span1 TimeSpan + span2 TimeSpan wantedResult bool }{ { - name: "timespans are not overlapping", - relTime: relativeTimeSpan{time.UTC, time.Wednesday, time.Wednesday, time.Now(), time.Now().Add(5)}, - absTime: absoluteTimeSpan{time.Date(2024, time.August, 28, 12, 0, 0, 0, time.UTC), time.Date(2024, time.August, 28, 12, 5, 0, 0, time.UTC)}, + name: "rel rel overlap", + 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{ + timezone: time.UTC, + weekdayFrom: time.Monday, + weekdayTo: time.Monday, + timeFrom: zeroTime.Add(12 * time.Hour), + timeTo: zeroTime.Add(18 * time.Hour), + }, + wantedResult: true, + }, + { + name: "rel rel dont overlap", + 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{ + timezone: time.UTC, + weekdayFrom: time.Tuesday, + weekdayTo: time.Tuesday, + timeFrom: zeroTime.Add(22 * time.Hour), + timeTo: zeroTime.Add(24 * time.Hour), + }, wantedResult: false, }, { - name: "timespans are overlapping", - relTime: relativeTimeSpan{time.UTC, time.Wednesday, time.Wednesday, - time.Date(2024, time.August, 28, 12, 0, 0, 0, time.UTC), time.Date(2024, time.August, 28, 12, 5, 0, 0, time.UTC)}, - absTime: absoluteTimeSpan{time.Date(2024, time.August, 28, 12, 0, 0, 0, time.UTC), - time.Date(2024, time.August, 28, 12, 3, 0, 0, time.UTC)}, + name: "abs abs overlap", + 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 + 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 + }, wantedResult: true, }, + { + name: "abs abs dont overlap", + 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 + 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 + }, + wantedResult: false, + }, + { + name: "rel abs overlap", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "rel abs dont overlap", + 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) + from: time.Date(2024, time.January, 6, 0, 0, 0, 0, time.UTC), // from Saturday 6st of January + to: time.Date(2024, time.January, 7, 0, 0, 0, 0, time.UTC), // to Sunday 7st of January + }, + wantedResult: false, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - gotResult := areTimespanOverlapped(test.relTime, test.absTime) + gotResult := areTimespanOverlapped(test.span1, test.span2) assert.Equal(t, test.wantedResult, gotResult) }) } From e977eeb9b0884b32b13cfd7b3d7750d191e7f68f Mon Sep 17 00:00:00 2001 From: Jonas Klug Date: Thu, 29 Aug 2024 09:55:18 +0200 Subject: [PATCH 04/16] feat: improved check for overlapping timespans --- internal/pkg/values/timespan.go | 60 +++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index fc29052..7a79b56 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -196,22 +196,60 @@ func getTimeOfDay(targetTime time.Time) time.Time { ) } -func areTimespanOverlapped(relTime relativeTimeSpan, absTime absoluteTimeSpan) bool { - relTimeStart, relTimeEnd := convertRelativeToAbsolute(relTime, time.Now()) +// overlapsWith function for relativeTimeSpan +func (r relativeTimeSpan) overlapsWith(span TimeSpan) bool { + switch o := span.(type) { + case relativeTimeSpan: + // Check if two timespan are overlapping + if r.weekdayFrom <= o.weekdayTo && o.weekdayFrom <= r.weekdayTo { + return r.timeFrom.Before(o.timeTo) && o.timeFrom.Before(r.timeTo) + } + return false - if absTime.from.Before(relTimeEnd) && relTimeStart.Before(absTime.to) { - return true - } else { + case absoluteTimeSpan: + // Check if relative overlaps with absolute + for day := r.weekdayFrom; day <= r.weekdayTo; day++ { + // Convert the weekday to a specific date within the absolute timespan range + for testDate := o.from; !testDate.After(o.to); testDate = testDate.AddDate(0, 0, 1) { + if testDate.Weekday() == day { + // Create a time value for the relative timespan on this specific date + startTime := time.Date(testDate.Year(), testDate.Month(), testDate.Day(), + r.timeFrom.Hour(), r.timeFrom.Minute(), r.timeFrom.Second(), r.timeFrom.Nanosecond(), r.timezone) + endTime := time.Date(testDate.Year(), testDate.Month(), testDate.Day(), + r.timeTo.Hour(), r.timeTo.Minute(), r.timeTo.Second(), r.timeTo.Nanosecond(), r.timezone) + + // Check if this time falls within the absolute timespan + if !(endTime.Before(o.from) || startTime.After(o.to)) { + return true + } + } + } + } return false } + return false } -func convertRelativeToAbsolute(relTime relativeTimeSpan, reference time.Time) (time.Time, time.Time) { - // Calulating start and end date based on current date and relative timespan - year, month, day := reference.Date() +// overlapsWith function for absoluteTimeSpan +func (a absoluteTimeSpan) overlapsWith(span TimeSpan) bool { + switch o := span.(type) { + case absoluteTimeSpan: + return !(a.to.Before(o.from) || o.to.Before(a.from)) - start := time.Date(year, month, day+int((relTime.weekdayFrom-reference.Weekday()+7)%7), relTime.timeFrom.Hour(), relTime.timeFrom.Minute(), relTime.timeFrom.Second(), 0, relTime.timezone) - end := time.Date(year, month, day+int((relTime.weekdayTo-reference.Weekday()+7)%7), relTime.timeTo.Hour(), relTime.timeTo.Minute(), relTime.timeTo.Second(), 0, relTime.timezone) + case relativeTimeSpan: + return o.overlapsWith(a) + } + return false +} - return start, end +// function to check if both timespans overlap +func areTimespanOverlapped(span1, span2 TimeSpan) bool { + switch s1 := span1.(type) { + case absoluteTimeSpan: + return s1.overlapsWith(span2) + case relativeTimeSpan: + return s1.overlapsWith(span2) + default: + return false + } } From 4d634cc2c4c0c9b873c3cf24872a6757a96e6155 Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Fri, 30 Aug 2024 10:25:54 +0200 Subject: [PATCH 05/16] feat: add more tests --- internal/api/kubernetes/client.go | 6 +- internal/pkg/values/timespan.go | 145 +++++++++++++++-------- internal/pkg/values/timespan_test.go | 167 ++++++++++++++++++++++++++- internal/pkg/values/util.go | 4 + 4 files changed, 268 insertions(+), 54 deletions(-) diff --git a/internal/api/kubernetes/client.go b/internal/api/kubernetes/client.go index 16ea5d3..e0e386f 100644 --- a/internal/api/kubernetes/client.go +++ b/internal/api/kubernetes/client.go @@ -2,7 +2,7 @@ package kubernetes import ( "context" - "crypto/sha1" + "crypto/sha256" "errors" "fmt" "log/slog" @@ -168,8 +168,8 @@ func (c client) removeOriginalReplicas(workload scalable.Workload) { // AddErrorEvent creates or updates a new event on the workload func (c client) AddErrorEvent(reason, id, message string, workload scalable.Workload, ctx context.Context) error { - hash := sha1.Sum([]byte(fmt.Sprintf("%s.%s", id, message))) - name := fmt.Sprintf("%s.%s.%.3x", workload.GetName(), reason, hash) + hash := sha256.Sum256([]byte(fmt.Sprintf("%s.%s", id, message))) + name := fmt.Sprintf("%s.%s.%x", workload.GetName(), reason, hash) eventsClient := c.clientset.CoreV1().Events(workload.GetNamespace()) // check if event already exists diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index 7a79b56..73b9393 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -7,8 +7,10 @@ import ( "time" ) -var errInvalidWeekday = errors.New("error: specified weekday is invalid") -var errRelativeTimespanInvalid = errors.New("error: specified relative timespan is invalid") +var ( + errInvalidWeekday = errors.New("error: specified weekday is invalid") + errRelativeTimespanInvalid = errors.New("error: specified relative timespan is invalid") +) type TimeSpan interface { // inTimeSpan checks if time is in the timespan or not @@ -143,7 +145,7 @@ func (t relativeTimeSpan) isTimeOfDayInRange(timeOfDay time.Time) bool { return (t.timeFrom.Before(timeOfDay) || t.timeFrom.Equal(timeOfDay)) && t.timeTo.After(timeOfDay) } -// isTimeInSpan check if the time is in the span +// isTimeInSpan checks if the time is in the span func (t relativeTimeSpan) isTimeInSpan(targetTime time.Time) bool { targetTime = targetTime.In(t.timezone) timeOfDay := getTimeOfDay(targetTime) @@ -151,6 +153,43 @@ func (t relativeTimeSpan) isTimeInSpan(targetTime time.Time) bool { return t.isTimeOfDayInRange(timeOfDay) && t.isWeekdayInRange(weekday) } +// inLocation returns an array of relative timespans matching the timespan converted to the given location +func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan { + var result []relativeTimeSpan + fmt.Println(t.timeFrom) + sameDays := relativeTimeSpan{ + timezone: timezone, + weekdayFrom: t.weekdayFrom, + weekdayTo: t.weekdayTo, + timeFrom: t.timeFrom.In(timezone), + timeTo: t.timeTo.In(timezone), + } + result = append(result, sameDays) + if sameDays.timeFrom.Year() == -1 { // check if timeFrom skipped to the day before + fmt.Println("from now day before") + daysBefore := relativeTimeSpan{ + timezone: timezone, + timeFrom: sameDays.timeFrom.Add(24 * time.Hour), + timeTo: sameDays.timeTo.Add(24 * time.Hour), + weekdayFrom: sameDays.weekdayFrom - 1, + weekdayTo: sameDays.weekdayTo - 1, + } + result = append(result, daysBefore) + } + if sameDays.timeTo.Day() == 2 { // check if timeTo skipped to the day after + fmt.Println("to now day after") + daysAfter := relativeTimeSpan{ + timezone: timezone, + timeFrom: sameDays.timeFrom.Add(-24 * time.Hour), + timeTo: sameDays.timeTo.Add(-24 * time.Hour), + weekdayFrom: sameDays.weekdayFrom + 1, + weekdayTo: sameDays.weekdayTo + 1, + } + result = append(result, daysAfter) + } + return result +} + type absoluteTimeSpan struct { from time.Time to time.Time @@ -196,60 +235,72 @@ func getTimeOfDay(targetTime time.Time) time.Time { ) } -// overlapsWith function for relativeTimeSpan -func (r relativeTimeSpan) overlapsWith(span TimeSpan) bool { - switch o := span.(type) { +// 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) + } + return relAndAbsOverlap(span2.(relativeTimeSpan), s1) case relativeTimeSpan: - // Check if two timespan are overlapping - if r.weekdayFrom <= o.weekdayTo && o.weekdayFrom <= r.weekdayTo { - return r.timeFrom.Before(o.timeTo) && o.timeFrom.Before(r.timeTo) + if s2, ok := span2.(absoluteTimeSpan); ok { + return relAndAbsOverlap(s1, s2) } - return false + return relAndRelOverlap(s1, span2.(relativeTimeSpan)) + } + return false // this shouldn't ever be reached +} - case absoluteTimeSpan: - // Check if relative overlaps with absolute - for day := r.weekdayFrom; day <= r.weekdayTo; day++ { - // Convert the weekday to a specific date within the absolute timespan range - for testDate := o.from; !testDate.After(o.to); testDate = testDate.AddDate(0, 0, 1) { - if testDate.Weekday() == day { - // Create a time value for the relative timespan on this specific date - startTime := time.Date(testDate.Year(), testDate.Month(), testDate.Day(), - r.timeFrom.Hour(), r.timeFrom.Minute(), r.timeFrom.Second(), r.timeFrom.Nanosecond(), r.timezone) - endTime := time.Date(testDate.Year(), testDate.Month(), testDate.Day(), - r.timeTo.Hour(), r.timeTo.Minute(), r.timeTo.Second(), r.timeTo.Nanosecond(), r.timezone) - - // Check if this time falls within the absolute timespan - if !(endTime.Before(o.from) || startTime.After(o.to)) { - return true - } - } - } +// relAndRelOverlap checks if two relative timespans overlap +func relAndRelOverlap(rel1 relativeTimeSpan, rel2 relativeTimeSpan) bool { + rel2List := rel2.inLocation(rel1.timezone) + for _, rel2 := range rel2List { + if relAndRelSameTimezoneOverlap(rel1, rel2) { + return true } - return false } return false } -// overlapsWith function for absoluteTimeSpan -func (a absoluteTimeSpan) overlapsWith(span TimeSpan) bool { - switch o := span.(type) { - case absoluteTimeSpan: - return !(a.to.Before(o.from) || o.to.Before(a.from)) - - case relativeTimeSpan: - return o.overlapsWith(a) +// relAndRelSameTimezoneOverlap checks if two relative timespans overlap, without converting them to the same timezone before +func relAndRelSameTimezoneOverlap(rel1 relativeTimeSpan, rel2 relativeTimeSpan) bool { + if rel1.timeFrom.After(rel1.timeTo) && rel2.timeFrom.After(rel2.timeTo) { // if both timespans are reversed, they both overlap anytime + return true } - return false + overlappingWeekdays := rel1.isWeekdayInRange(rel2.weekdayFrom) || + rel1.isWeekdayInRange(rel2.weekdayTo) || + rel2.isWeekdayInRange(rel1.weekdayFrom) || + rel2.isWeekdayInRange(rel1.weekdayTo) + if !overlappingWeekdays { + return false + } + overlappingTimeOfDays := rel1.isTimeOfDayInRange(rel2.timeFrom) || + rel1.isTimeOfDayInRange(asExclusiveTimestamp(rel2.timeTo)) || + rel2.isTimeOfDayInRange(rel1.timeFrom) || + rel2.isTimeOfDayInRange(asExclusiveTimestamp(rel1.timeTo)) + return overlappingTimeOfDays } -// function to check if both timespans overlap -func areTimespanOverlapped(span1, span2 TimeSpan) bool { - switch s1 := span1.(type) { - case absoluteTimeSpan: - return s1.overlapsWith(span2) - case relativeTimeSpan: - return s1.overlapsWith(span2) - default: - return false +// relAndAbsOverlap checks if a relative and an aboslute timespan overlap +func relAndAbsOverlap(rel relativeTimeSpan, abs absoluteTimeSpan) bool { + abs.from = abs.from.In(rel.timezone) + abs.to = abs.to.In(rel.timezone) + if abs.to.Sub(abs.from) >= 7*24*time.Hour { + return true + } + if rel.isTimeInSpan(abs.from) || rel.isTimeInSpan(asExclusiveTimestamp(abs.to)) { + return true + } + if rel.weekdayFrom > rel.weekdayTo { // check if weekdays are in reverse + return abs.from.Weekday() > rel.weekdayFrom || abs.from.Weekday() < rel.weekdayTo || + abs.to.Weekday() > rel.weekdayFrom || abs.to.Weekday() < rel.weekdayTo } + return rel.weekdayFrom > abs.from.Weekday() && rel.weekdayFrom < abs.to.Weekday() || + rel.weekdayTo > abs.from.Weekday() && rel.weekdayTo < abs.to.Weekday() +} + +// absAndAbsOverlap checks if two absolute timespans overlap +func absAndAbsOverlap(abs1 absoluteTimeSpan, abs2 absoluteTimeSpan) bool { + return abs1.from.Before(abs2.to) && abs1.to.After(abs2.from) } diff --git a/internal/pkg/values/timespan_test.go b/internal/pkg/values/timespan_test.go index 44e1826..98a65ef 100644 --- a/internal/pkg/values/timespan_test.go +++ b/internal/pkg/values/timespan_test.go @@ -1,13 +1,18 @@ package values import ( + "fmt" "testing" "time" "github.com/stretchr/testify/assert" ) -var zeroTime = time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC) +var ( + UTC1 = time.FixedZone("UTC+1", int(time.Hour/time.Second)) + zeroTime = time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC) + zeroTimeUTC1 = time.Date(0, time.January, 1, 0, 0, 0, 0, UTC1) +) func TestParseRelativeTimeSpan(t *testing.T) { tests := []struct { @@ -344,15 +349,169 @@ func TestOverlappingTimespans(t *testing.T) { timeTo: zeroTime.Add(20 * time.Hour), }, 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 6st of January - to: time.Date(2024, time.January, 7, 0, 0, 0, 0, time.UTC), // to Sunday 7st of January + 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 + }, + wantedResult: false, + }, + { + name: "rel rel overlap different timezones", + 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{ + timezone: UTC1, + weekdayFrom: time.Monday, + weekdayTo: time.Friday, + timeFrom: zeroTimeUTC1.Add(20 * time.Hour), + timeTo: zeroTimeUTC1.Add(21 * time.Hour), + }, + wantedResult: true, + }, + { + name: "rel rel dont overlap different timezones", + 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{ + timezone: UTC1, + weekdayFrom: time.Monday, + weekdayTo: time.Friday, + timeFrom: zeroTimeUTC1.Add(8 * time.Hour), + timeTo: zeroTimeUTC1.Add(9 * time.Hour), + }, + wantedResult: false, + }, + { + name: "rel abs overlap different timezones", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "rel abs dont overlap different timezones", + 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 + 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 + }, + wantedResult: false, + }, + { + name: "rel abs overlap reverse weekdays", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "rev-rel rev-rel overlap", + 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{ + timezone: time.UTC, + weekdayFrom: time.Monday, + weekdayTo: time.Monday, + timeFrom: zeroTime.Add(23 * time.Hour), + timeTo: zeroTime.Add(2 * time.Hour), + }, + wantedResult: true, + }, + { + name: "rel abs overlap different timezones rel split days", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "rel rel overlap different timezones rel split days", + 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{ + timezone: UTC1, + weekdayFrom: time.Wednesday, + weekdayTo: time.Wednesday, + timeFrom: zeroTimeUTC1.Add(0 * time.Hour), + timeTo: zeroTimeUTC1.Add(24 * time.Hour), + }, + wantedResult: true, + }, + { + name: "rel rel dont overlap different timezones rel split days", + span1: relativeTimeSpan{ + timezone: UTC1, + weekdayFrom: time.Wednesday, + weekdayTo: time.Wednesday, + timeFrom: zeroTimeUTC1.Add(0 * time.Hour), + timeTo: zeroTimeUTC1.Add(8 * time.Hour), + }, + span2: relativeTimeSpan{ + timezone: time.UTC, + weekdayFrom: time.Tuesday, + weekdayTo: time.Tuesday, + timeFrom: zeroTime.Add(4 * time.Hour), + timeTo: zeroTime.Add(6 * time.Hour), }, wantedResult: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - gotResult := areTimespanOverlapped(test.span1, test.span2) + gotResult := doTimespansOverlap(test.span1, test.span2) + assert.Equal(t, test.wantedResult, gotResult) + }) + t.Run(fmt.Sprintf("%s reverse span order", test.name), func(t *testing.T) { + gotResult := doTimespansOverlap(test.span2, test.span1) assert.Equal(t, test.wantedResult, gotResult) }) } diff --git a/internal/pkg/values/util.go b/internal/pkg/values/util.go index d381c5d..76c6423 100644 --- a/internal/pkg/values/util.go +++ b/internal/pkg/values/util.go @@ -143,3 +143,7 @@ func GetLayerFromEnv() (Layer, error) { } return result, nil } + +func asExclusiveTimestamp(inc time.Time) time.Time { + return inc.Add(-time.Nanosecond) +} From b7cefe7ecdef7ece002d94153cc63b51362948e2 Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Fri, 30 Aug 2024 11:22:20 +0200 Subject: [PATCH 06/16] feat: added benchmark --- internal/pkg/values/timespan.go | 3 - internal/pkg/values/timespan_test.go | 512 ++++++++++++++------------- 2 files changed, 264 insertions(+), 251 deletions(-) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index 73b9393..c125a1b 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -156,7 +156,6 @@ 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 { var result []relativeTimeSpan - fmt.Println(t.timeFrom) sameDays := relativeTimeSpan{ timezone: timezone, weekdayFrom: t.weekdayFrom, @@ -166,7 +165,6 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan } result = append(result, sameDays) if sameDays.timeFrom.Year() == -1 { // check if timeFrom skipped to the day before - fmt.Println("from now day before") daysBefore := relativeTimeSpan{ timezone: timezone, timeFrom: sameDays.timeFrom.Add(24 * time.Hour), @@ -177,7 +175,6 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan result = append(result, daysBefore) } if sameDays.timeTo.Day() == 2 { // check if timeTo skipped to the day after - fmt.Println("to now day after") daysAfter := relativeTimeSpan{ timezone: timezone, timeFrom: sameDays.timeFrom.Add(-24 * time.Hour), diff --git a/internal/pkg/values/timespan_test.go b/internal/pkg/values/timespan_test.go index 98a65ef..1337b93 100644 --- a/internal/pkg/values/timespan_test.go +++ b/internal/pkg/values/timespan_test.go @@ -257,255 +257,256 @@ func TestAbsoluteTimeSpan_isTimeInSpan(t *testing.T) { } } +var doTimespansOverlapTests = []struct { + name string + span1 TimeSpan + span2 TimeSpan + wantedResult bool +}{ + { + name: "rel rel overlap", + 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{ + timezone: time.UTC, + weekdayFrom: time.Monday, + weekdayTo: time.Monday, + timeFrom: zeroTime.Add(12 * time.Hour), + timeTo: zeroTime.Add(18 * time.Hour), + }, + wantedResult: true, + }, + { + name: "rel rel dont overlap", + 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{ + timezone: time.UTC, + weekdayFrom: time.Tuesday, + weekdayTo: time.Tuesday, + timeFrom: zeroTime.Add(22 * time.Hour), + timeTo: zeroTime.Add(24 * time.Hour), + }, + wantedResult: false, + }, + { + name: "abs abs overlap", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "abs abs dont overlap", + 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 + 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 + }, + wantedResult: false, + }, + { + name: "rel abs overlap", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "rel abs dont overlap", + 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) + 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 + }, + wantedResult: false, + }, + { + name: "rel rel overlap different timezones", + 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{ + timezone: UTC1, + weekdayFrom: time.Monday, + weekdayTo: time.Friday, + timeFrom: zeroTimeUTC1.Add(20 * time.Hour), + timeTo: zeroTimeUTC1.Add(21 * time.Hour), + }, + wantedResult: true, + }, + { + name: "rel rel dont overlap different timezones", + 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{ + timezone: UTC1, + weekdayFrom: time.Monday, + weekdayTo: time.Friday, + timeFrom: zeroTimeUTC1.Add(8 * time.Hour), + timeTo: zeroTimeUTC1.Add(9 * time.Hour), + }, + wantedResult: false, + }, + { + name: "rel abs overlap different timezones", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "rel abs dont overlap different timezones", + 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 + 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 + }, + wantedResult: false, + }, + { + name: "rel abs overlap reverse weekdays", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "rev-rel rev-rel overlap", + 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{ + timezone: time.UTC, + weekdayFrom: time.Monday, + weekdayTo: time.Monday, + timeFrom: zeroTime.Add(23 * time.Hour), + timeTo: zeroTime.Add(2 * time.Hour), + }, + wantedResult: true, + }, + { + name: "rel abs overlap different timezones rel split days", + 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 + 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 + }, + wantedResult: true, + }, + { + name: "rel rel overlap different timezones rel split days", + 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{ + timezone: UTC1, + weekdayFrom: time.Wednesday, + weekdayTo: time.Wednesday, + timeFrom: zeroTimeUTC1.Add(0 * time.Hour), + timeTo: zeroTimeUTC1.Add(24 * time.Hour), + }, + wantedResult: true, + }, + { + name: "rel rel dont overlap different timezones rel split days", + span1: relativeTimeSpan{ + timezone: UTC1, + weekdayFrom: time.Wednesday, + weekdayTo: time.Wednesday, + timeFrom: zeroTimeUTC1.Add(0 * time.Hour), + timeTo: zeroTimeUTC1.Add(8 * time.Hour), + }, + span2: relativeTimeSpan{ + timezone: time.UTC, + weekdayFrom: time.Tuesday, + weekdayTo: time.Tuesday, + timeFrom: zeroTime.Add(4 * time.Hour), + timeTo: zeroTime.Add(6 * time.Hour), + }, + wantedResult: false, + }, +} + func TestOverlappingTimespans(t *testing.T) { - tests := []struct { - name string - span1 TimeSpan - span2 TimeSpan - wantedResult bool - }{ - { - name: "rel rel overlap", - 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{ - timezone: time.UTC, - weekdayFrom: time.Monday, - weekdayTo: time.Monday, - timeFrom: zeroTime.Add(12 * time.Hour), - timeTo: zeroTime.Add(18 * time.Hour), - }, - wantedResult: true, - }, - { - name: "rel rel dont overlap", - 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{ - timezone: time.UTC, - weekdayFrom: time.Tuesday, - weekdayTo: time.Tuesday, - timeFrom: zeroTime.Add(22 * time.Hour), - timeTo: zeroTime.Add(24 * time.Hour), - }, - wantedResult: false, - }, - { - name: "abs abs overlap", - 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 - 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 - }, - wantedResult: true, - }, - { - name: "abs abs dont overlap", - 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 - 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 - }, - wantedResult: false, - }, - { - name: "rel abs overlap", - 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 - 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 - }, - wantedResult: true, - }, - { - name: "rel abs dont overlap", - 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) - 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 - }, - wantedResult: false, - }, - { - name: "rel rel overlap different timezones", - 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{ - timezone: UTC1, - weekdayFrom: time.Monday, - weekdayTo: time.Friday, - timeFrom: zeroTimeUTC1.Add(20 * time.Hour), - timeTo: zeroTimeUTC1.Add(21 * time.Hour), - }, - wantedResult: true, - }, - { - name: "rel rel dont overlap different timezones", - 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{ - timezone: UTC1, - weekdayFrom: time.Monday, - weekdayTo: time.Friday, - timeFrom: zeroTimeUTC1.Add(8 * time.Hour), - timeTo: zeroTimeUTC1.Add(9 * time.Hour), - }, - wantedResult: false, - }, - { - name: "rel abs overlap different timezones", - 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 - 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 - }, - wantedResult: true, - }, - { - name: "rel abs dont overlap different timezones", - 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 - 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 - }, - wantedResult: false, - }, - { - name: "rel abs overlap reverse weekdays", - 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 - 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 - }, - wantedResult: true, - }, - { - name: "rev-rel rev-rel overlap", - 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{ - timezone: time.UTC, - weekdayFrom: time.Monday, - weekdayTo: time.Monday, - timeFrom: zeroTime.Add(23 * time.Hour), - timeTo: zeroTime.Add(2 * time.Hour), - }, - wantedResult: true, - }, - { - name: "rel abs overlap different timezones rel split days", - 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 - 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 - }, - wantedResult: true, - }, - { - name: "rel rel overlap different timezones rel split days", - 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{ - timezone: UTC1, - weekdayFrom: time.Wednesday, - weekdayTo: time.Wednesday, - timeFrom: zeroTimeUTC1.Add(0 * time.Hour), - timeTo: zeroTimeUTC1.Add(24 * time.Hour), - }, - wantedResult: true, - }, - { - name: "rel rel dont overlap different timezones rel split days", - span1: relativeTimeSpan{ - timezone: UTC1, - weekdayFrom: time.Wednesday, - weekdayTo: time.Wednesday, - timeFrom: zeroTimeUTC1.Add(0 * time.Hour), - timeTo: zeroTimeUTC1.Add(8 * time.Hour), - }, - span2: relativeTimeSpan{ - timezone: time.UTC, - weekdayFrom: time.Tuesday, - weekdayTo: time.Tuesday, - timeFrom: zeroTime.Add(4 * time.Hour), - timeTo: zeroTime.Add(6 * time.Hour), - }, - wantedResult: false, - }, - } - for _, test := range tests { + for _, test := range doTimespansOverlapTests { t.Run(test.name, func(t *testing.T) { gotResult := doTimespansOverlap(test.span1, test.span2) assert.Equal(t, test.wantedResult, gotResult) @@ -516,3 +517,18 @@ func TestOverlappingTimespans(t *testing.T) { }) } } + +func BenchmarkDoTimespansOverlap(b *testing.B) { + for _, test := range doTimespansOverlapTests { + b.Run(test.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + doTimespansOverlap(test.span1, test.span2) + } + }) + b.Run(fmt.Sprintf("%s reverse span order", test.name), func(b *testing.B) { + for i := 0; i < b.N; i++ { + doTimespansOverlap(test.span2, test.span1) + } + }) + } +} From 77255ef8b56a22c30e6d3bc140d96e8d6a1e22a8 Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Fri, 30 Aug 2024 12:47:17 +0200 Subject: [PATCH 07/16] perf: added optimized way for comparing rel rel with same tz --- internal/pkg/values/timespan.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index c125a1b..0653edc 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -174,7 +174,7 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan } result = append(result, daysBefore) } - if sameDays.timeTo.Day() == 2 { // check if timeTo skipped to the day after + if asExclusiveTimestamp(sameDays.timeTo).Day() == 2 { // check if timeTo skipped to the day after daysAfter := relativeTimeSpan{ timezone: timezone, timeFrom: sameDays.timeFrom.Add(-24 * time.Hour), @@ -251,6 +251,13 @@ func doTimespansOverlap(span1, span2 TimeSpan) bool { // relAndRelOverlap checks if two relative timespans overlap func relAndRelOverlap(rel1 relativeTimeSpan, rel2 relativeTimeSpan) bool { + _, tzOffset1 := rel1.timeFrom.Zone() + _, tzOffset2 := rel2.timeFrom.Zone() + if tzOffset1 == tzOffset2 { // optimized path for timespans with same timezone offset + return relAndRelSameTimezoneOverlap(rel1, rel2) + } + + // slow path for timespans with different timezones rel2List := rel2.inLocation(rel1.timezone) for _, rel2 := range rel2List { if relAndRelSameTimezoneOverlap(rel1, rel2) { From 45d859a639f5ba40d45b55108bb74f0f16d89d6f Mon Sep 17 00:00:00 2001 From: Jonas Klug Date: Mon, 2 Sep 2024 11:47:10 +0200 Subject: [PATCH 08/16] refactor: inLocation function --- internal/pkg/values/timespan.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index 0653edc..b2cc7f2 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -164,7 +164,7 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan timeTo: t.timeTo.In(timezone), } result = append(result, sameDays) - if sameDays.timeFrom.Year() == -1 { // check if timeFrom skipped to the day before + if isTimeFromSkippedToPreviousDay(sameDays.timeFrom) { daysBefore := relativeTimeSpan{ timezone: timezone, timeFrom: sameDays.timeFrom.Add(24 * time.Hour), @@ -174,7 +174,7 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan } result = append(result, daysBefore) } - if asExclusiveTimestamp(sameDays.timeTo).Day() == 2 { // check if timeTo skipped to the day after + if isTimeToSkippedToNextDay(sameDays.timeTo) { daysAfter := relativeTimeSpan{ timezone: timezone, timeFrom: sameDays.timeFrom.Add(-24 * time.Hour), @@ -187,6 +187,16 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan return result } +// Check if timeFrom skipped to the previous day +func isTimeFromSkippedToPreviousDay(timeFrom time.Time) bool { + return timeFrom.Year() == -1 +} + +// Check if timeTo skipped to the following day +func isTimeToSkippedToNextDay(timeTo time.Time) bool { + return asExclusiveTimestamp(timeTo).Day() == 2 +} + type absoluteTimeSpan struct { from time.Time to time.Time From 5e99255b3f1f6444bbd9a5853348413c401f6172 Mon Sep 17 00:00:00 2001 From: Jonas Klug Date: Wed, 4 Sep 2024 14:52:53 +0200 Subject: [PATCH 09/16] feat: added check if up & downscale periods overlapping --- internal/pkg/values/layer.go | 19 ++++++++++++++----- internal/pkg/values/layer_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/internal/pkg/values/layer.go b/internal/pkg/values/layer.go index db72639..9c28ac2 100644 --- a/internal/pkg/values/layer.go +++ b/internal/pkg/values/layer.go @@ -7,11 +7,12 @@ import ( ) var ( - errForceUpAndDownTime = errors.New("error: both forceUptime and forceDowntime are defined") - errUpAndDownTime = errors.New("error: both uptime and downtime are defined") - errTimeAndPeriod = errors.New("error: both a time and a period is defined") - errInvalidDownscaleReplicas = errors.New("error: downscale replicas value is invalid") - errValueNotSet = errors.New("error: value isn't set") + errForceUpAndDownTime = errors.New("error: both forceUptime and forceDowntime are defined") + errUpAndDownTime = errors.New("error: both uptime and downtime are defined") + errTimeAndPeriod = errors.New("error: both a time and a period is defined") + errInvalidDownscaleReplicas = errors.New("error: downscale replicas value is invalid") + errValueNotSet = errors.New("error: value isn't set") + errUpAndDownscaleOverlapping = errors.New("error: up- and downscale periods are overlapping") ) const Undefined = -1 // Undefined represents an undefined integer value @@ -79,6 +80,14 @@ func (l Layer) checkForIncompatibleFields() error { (l.UpscalePeriod != nil || l.DownscalePeriod != nil) { return errTimeAndPeriod } + // up- and downscale periods overlapping + for _, upscale := range l.UpscalePeriod { + for _, downscale := range l.DownscalePeriod { + if doTimespansOverlap(upscale, downscale) { + return errUpAndDownscaleOverlapping + } + } + } return nil } diff --git a/internal/pkg/values/layer_test.go b/internal/pkg/values/layer_test.go index f9f596c..d464a41 100644 --- a/internal/pkg/values/layer_test.go +++ b/internal/pkg/values/layer_test.go @@ -68,6 +68,34 @@ func TestLayer_checkForIncompatibleFields(t *testing.T) { }, wantErr: true, }, + { + name: "down- and upscale periods overlapping", + layer: Layer{ + DownscalePeriod: timeSpans{absoluteTimeSpan{ + from: time.Now(), + to: time.Now().Add(1 * time.Hour), + }}, + UpscalePeriod: timeSpans{absoluteTimeSpan{ // overlapping + from: time.Now(), + to: time.Now().Add(1 * time.Hour), + }}, + }, + wantErr: true, + }, + { + name: "down- and upscale do not overlap", + layer: Layer{ + DownscalePeriod: timeSpans{absoluteTimeSpan{ + from: time.Now(), + to: time.Now().Add(time.Hour), + }}, + UpscalePeriod: timeSpans{absoluteTimeSpan{ + from: time.Now().Add(2 * time.Hour), + to: time.Now().Add(3 * time.Hour), + }}, + }, + wantErr: false, + }, { name: "valid", layer: Layer{ From a3b9cc08969c0a27f9265a3f88182d0b030709aa Mon Sep 17 00:00:00 2001 From: Jonas Klug Date: Thu, 5 Sep 2024 10:14:16 +0200 Subject: [PATCH 10/16] refactor: mv isTimeTo & -fromSkipped into util.go --- internal/pkg/values/timespan.go | 10 ---------- internal/pkg/values/util.go | 10 ++++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index b2cc7f2..35b8b9b 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -187,16 +187,6 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan return result } -// Check if timeFrom skipped to the previous day -func isTimeFromSkippedToPreviousDay(timeFrom time.Time) bool { - return timeFrom.Year() == -1 -} - -// Check if timeTo skipped to the following day -func isTimeToSkippedToNextDay(timeTo time.Time) bool { - return asExclusiveTimestamp(timeTo).Day() == 2 -} - type absoluteTimeSpan struct { from time.Time to time.Time diff --git a/internal/pkg/values/util.go b/internal/pkg/values/util.go index 76c6423..18c278b 100644 --- a/internal/pkg/values/util.go +++ b/internal/pkg/values/util.go @@ -147,3 +147,13 @@ func GetLayerFromEnv() (Layer, error) { 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 +} + +// isTimeToSkppedToNextDay checks if timeTo skipped to the following day +func isTimeToSkippedToNextDay(timeTo time.Time) bool { + return asExclusiveTimestamp(timeTo).Day() == 2 +} From 236439a6b9444398b3df604ecf4dbf36183dc662 Mon Sep 17 00:00:00 2001 From: Jonas Klug Date: Thu, 5 Sep 2024 15:20:52 +0200 Subject: [PATCH 11/16] chore: refactor getWeekdayAfter & getWeekdayBefore --- internal/pkg/values/timespan.go | 8 ++++---- internal/pkg/values/timespan_test.go | 4 ++++ internal/pkg/values/util.go | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index 35b8b9b..20bdb1d 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -169,8 +169,8 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan timezone: timezone, timeFrom: sameDays.timeFrom.Add(24 * time.Hour), timeTo: sameDays.timeTo.Add(24 * time.Hour), - weekdayFrom: sameDays.weekdayFrom - 1, - weekdayTo: sameDays.weekdayTo - 1, + weekdayFrom: getWeekdayBefore(sameDays.weekdayFrom), + weekdayTo: getWeekdayBefore(sameDays.weekdayTo), } result = append(result, daysBefore) } @@ -179,8 +179,8 @@ func (t relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpan timezone: timezone, timeFrom: sameDays.timeFrom.Add(-24 * time.Hour), timeTo: sameDays.timeTo.Add(-24 * time.Hour), - weekdayFrom: sameDays.weekdayFrom + 1, - weekdayTo: sameDays.weekdayTo + 1, + weekdayFrom: getWeekdayAfter(sameDays.weekdayFrom), + weekdayTo: getWeekdayAfter(sameDays.weekdayTo), } result = append(result, daysAfter) } diff --git a/internal/pkg/values/timespan_test.go b/internal/pkg/values/timespan_test.go index 1337b93..982c091 100644 --- a/internal/pkg/values/timespan_test.go +++ b/internal/pkg/values/timespan_test.go @@ -62,6 +62,10 @@ func TestParseRelativeTimeSpan(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { gotResult, gotErr := parseRelativeTimeSpan(test.timespanString) + + // Debugging + //t.Errorf("Error: %v", gotResult) + if test.wantErr { assert.Error(t, gotErr) } else { diff --git a/internal/pkg/values/util.go b/internal/pkg/values/util.go index 18c278b..b3befd4 100644 --- a/internal/pkg/values/util.go +++ b/internal/pkg/values/util.go @@ -157,3 +157,23 @@ func isTimeFromSkippedToPreviousDay(timeFrom time.Time) bool { 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 { + if weekday == 0 { + weekday = 6 + return weekday + } else { + return weekday - 1 + } +} + +// getWeekdayAfter returns the day after (it automatically checks if value is below 0 or above 6) +func getWeekdayAfter(weekday time.Weekday) time.Weekday { + if weekday == 6 { + weekday = 0 + return weekday + } else { + return weekday + 1 + } +} From cac0db0dbab4d157e4254c4ffbc0f76955cc7771 Mon Sep 17 00:00:00 2001 From: Jonas Klug Date: Fri, 6 Sep 2024 09:46:04 +0200 Subject: [PATCH 12/16] chore: refactored getWeekdayBefore & after function --- internal/pkg/values/util.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/values/util.go b/internal/pkg/values/util.go index b3befd4..3bcebdf 100644 --- a/internal/pkg/values/util.go +++ b/internal/pkg/values/util.go @@ -164,7 +164,7 @@ func getWeekdayBefore(weekday time.Weekday) time.Weekday { weekday = 6 return weekday } else { - return weekday - 1 + return (weekday + 6) % 7 } } @@ -174,6 +174,6 @@ func getWeekdayAfter(weekday time.Weekday) time.Weekday { weekday = 0 return weekday } else { - return weekday + 1 + return (weekday + 1) % 7 } } From 033bc777fa44e004e68038fb326cd654bd36411b Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Thu, 12 Sep 2024 14:27:29 +0200 Subject: [PATCH 13/16] refactor: util.go --- internal/pkg/values/util.go | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/internal/pkg/values/util.go b/internal/pkg/values/util.go index 51dc39b..291171f 100644 --- a/internal/pkg/values/util.go +++ b/internal/pkg/values/util.go @@ -144,6 +144,7 @@ func GetLayerFromEnv() (Layer, error) { return result, nil } +// asExclusiveTimestamp removes the smallest unit of time, so the timestamp will be treated as exclusive func asExclusiveTimestamp(inc time.Time) time.Time { return inc.Add(-time.Nanosecond) } @@ -153,27 +154,17 @@ func isTimeFromSkippedToPreviousDay(timeFrom time.Time) bool { return timeFrom.Year() == -1 } -// isTimeToSkppedToNextDay checks if timeTo skipped to the following day +// 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 { - if weekday == 0 { - weekday = 6 - return weekday - } else { - return (weekday + 6) % 7 - } + return (weekday + 6) % 7 } // getWeekdayAfter returns the day after (it automatically checks if value is below 0 or above 6) func getWeekdayAfter(weekday time.Weekday) time.Weekday { - if weekday == 6 { - weekday = 0 - return weekday - } else { - return (weekday + 1) % 7 - } + return (weekday + 1) % 7 } From 5538d22de5aa8ba5774fd596fa107b74868a9f8e Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Fri, 13 Sep 2024 10:32:16 +0200 Subject: [PATCH 14/16] 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 From 23f6545bf2c53408b333e784213bc50b1d9397d5 Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Fri, 13 Sep 2024 10:40:27 +0200 Subject: [PATCH 15/16] refactor: rename to weekdays --- internal/pkg/values/timespan.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index a4f31fb..c56216b 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -169,31 +169,31 @@ 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 { var result []relativeTimeSpan - sameWeedays := relativeTimeSpan{ + sameWeekdays := relativeTimeSpan{ timezone: timezone, weekdayFrom: t.weekdayFrom, weekdayTo: t.weekdayTo, timeFrom: t.timeFrom.In(timezone), timeTo: t.timeTo.In(timezone), } - result = append(result, sameWeedays) - if sameWeedays.overlapsIntoPreviousDay() { + result = append(result, sameWeekdays) + if sameWeekdays.overlapsIntoPreviousDay() { weekdaysBefore := relativeTimeSpan{ timezone: timezone, - timeFrom: sameWeedays.timeFrom.Add(24 * time.Hour), - timeTo: sameWeedays.timeTo.Add(24 * time.Hour), - weekdayFrom: getWeekdayBefore(sameWeedays.weekdayFrom), - weekdayTo: getWeekdayBefore(sameWeedays.weekdayTo), + timeFrom: sameWeekdays.timeFrom.Add(24 * time.Hour), + timeTo: sameWeekdays.timeTo.Add(24 * time.Hour), + weekdayFrom: getWeekdayBefore(sameWeekdays.weekdayFrom), + weekdayTo: getWeekdayBefore(sameWeekdays.weekdayTo), } result = append(result, weekdaysBefore) } - if sameWeedays.overlapsIntoNextDay() { + if sameWeekdays.overlapsIntoNextDay() { weekdaysAfter := relativeTimeSpan{ timezone: timezone, - timeFrom: sameWeedays.timeFrom.Add(-24 * time.Hour), - timeTo: sameWeedays.timeTo.Add(-24 * time.Hour), - weekdayFrom: getWeekdayAfter(sameWeedays.weekdayFrom), - weekdayTo: getWeekdayAfter(sameWeedays.weekdayTo), + timeFrom: sameWeekdays.timeFrom.Add(-24 * time.Hour), + timeTo: sameWeekdays.timeTo.Add(-24 * time.Hour), + weekdayFrom: getWeekdayAfter(sameWeekdays.weekdayFrom), + weekdayTo: getWeekdayAfter(sameWeekdays.weekdayTo), } result = append(result, weekdaysAfter) } From 180de44b3086cc1153019fd4cdf9638388e883ea Mon Sep 17 00:00:00 2001 From: jonathan-mayer Date: Fri, 13 Sep 2024 12:41:48 +0200 Subject: [PATCH 16/16] feat: add tests and find out breaking bug --- internal/pkg/values/timespan.go | 4 ++-- internal/pkg/values/timespan_test.go | 36 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/internal/pkg/values/timespan.go b/internal/pkg/values/timespan.go index c56216b..2d9a439 100644 --- a/internal/pkg/values/timespan.go +++ b/internal/pkg/values/timespan.go @@ -202,12 +202,12 @@ func (t *relativeTimeSpan) inLocation(timezone *time.Location) []relativeTimeSpa // overlapsIntoPreviousDay checks if timeFrom overlaps into the previous day func (r *relativeTimeSpan) overlapsIntoPreviousDay() bool { - return r.timeFrom.Year() == -1 + return r.timeFrom.Year() == -1 // || r.timeTo.Year() == -1 } // overlapsIntoNextDay checks if timeTo overlaps into the following day func (r *relativeTimeSpan) overlapsIntoNextDay() bool { - return asExclusiveTimestamp(r.timeTo).Day() == 2 + return asExclusiveTimestamp(r.timeTo).Day() == 2 // || r.timeFrom.Day() == 2 } type absoluteTimeSpan struct { diff --git a/internal/pkg/values/timespan_test.go b/internal/pkg/values/timespan_test.go index 8357728..8a2ff28 100644 --- a/internal/pkg/values/timespan_test.go +++ b/internal/pkg/values/timespan_test.go @@ -570,6 +570,42 @@ var doTimespansOverlapTests = []struct { }, wantedResult: false, }, + { + name: "rev-rel rel overlap different timezones day before", + span1: &relativeTimeSpan{ + timezone: time.UTC, + weekdayFrom: time.Monday, + weekdayTo: time.Monday, + timeFrom: zeroTime.Add(8 * time.Hour), + timeTo: zeroTime.Add(24 * time.Hour), + }, + span2: &relativeTimeSpan{ + timezone: UTC1, + weekdayFrom: time.Monday, + weekdayTo: time.Monday, + timeFrom: zeroTimeUTC1.Add(24 * time.Hour), + timeTo: zeroTimeUTC1.Add(30 * time.Minute), + }, + wantedResult: true, + }, + { + name: "rel rev-rel dont overlap different timezones day before", + span1: &relativeTimeSpan{ + timezone: time.UTC, + weekdayFrom: time.Wednesday, + weekdayTo: time.Wednesday, + timeFrom: zeroTime.Add(23 * time.Hour), + timeTo: zeroTime.Add(24 * time.Hour), + }, + span2: &relativeTimeSpan{ + timezone: UTC1, + weekdayFrom: time.Wednesday, + weekdayTo: time.Wednesday, + timeFrom: zeroTimeUTC1.Add(23 * time.Hour), + timeTo: zeroTimeUTC1.Add(0 * time.Hour), + }, + wantedResult: false, + }, } func TestOverlappingTimespans(t *testing.T) {