Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Heartbeat] Adds maintenance windows !! #41508

Merged
merged 49 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6824d0d
Add maint windows
andrewvc Jan 19, 2023
4593430
Initial maint win MVP
andrewvc Jan 19, 2023
b8a4d3c
Merge branch 'main' into maint-win
shahzad31 Nov 2, 2024
e75ec1d
added maintenance windows
shahzad31 Nov 4, 2024
09b0587
destroy cluster
shahzad31 Nov 4, 2024
1a722aa
format
shahzad31 Nov 4, 2024
82e3faf
revert file
shahzad31 Nov 4, 2024
3d3d14f
revert file
shahzad31 Nov 4, 2024
b16af0d
added lib
shahzad31 Nov 4, 2024
f3db602
revert
shahzad31 Nov 6, 2024
87410e9
Merge branch 'main' of https://github.com/elastic/beats into maintena…
shahzad31 Nov 6, 2024
2693d6a
space
shahzad31 Nov 6, 2024
7ae80b7
add license
shahzad31 Nov 6, 2024
b7b9f29
update
shahzad31 Nov 14, 2024
3a792e1
Merge remote-tracking branch 'upstream/main' into maintenance-windows
shahzad31 Nov 14, 2024
3767903
update
shahzad31 Nov 14, 2024
aa34408
fix tests
shahzad31 Nov 14, 2024
2a6b6e0
lint
shahzad31 Nov 21, 2024
de4df2b
skip run once
shahzad31 Nov 21, 2024
31a04f4
Fix linting
emilioalvap Feb 6, 2025
9641f53
Merge branch 'main' into maintenance-windows
shahzad31 Feb 6, 2025
c39784b
Remove unused fields
emilioalvap Feb 6, 2025
2998d8c
Merge branch 'main' into maintenance-windows
emilioalvap Feb 6, 2025
4160a74
Make update
emilioalvap Feb 6, 2025
7a3493d
add more test cases
shahzad31 Feb 7, 2025
474de92
utc
shahzad31 Feb 7, 2025
8027954
lint
shahzad31 Feb 10, 2025
e086be3
handle errors
shahzad31 Feb 13, 2025
62decb4
bit of refactor
shahzad31 Feb 13, 2025
7d17a2d
emilio suggestion
shahzad31 Feb 13, 2025
3de2b53
More PR feedback
shahzad31 Feb 13, 2025
9ba538e
lint
shahzad31 Feb 13, 2025
f581a44
add validation for map
shahzad31 Feb 13, 2025
e0e318d
nil condition
shahzad31 Feb 13, 2025
e34daec
set count to default
shahzad31 Feb 13, 2025
4da6fc1
add validation for only daily
shahzad31 Feb 13, 2025
3551154
add dt start validation
shahzad31 Feb 13, 2025
5f32cb6
revrt
shahzad31 Feb 13, 2025
88f3f3d
revrt
shahzad31 Feb 13, 2025
954b78d
revert
shahzad31 Feb 13, 2025
734e85b
revert
shahzad31 Feb 13, 2025
e9c2e61
format
shahzad31 Feb 13, 2025
ad3ec73
PR feedback
shahzad31 Feb 14, 2025
7b13c96
add docs
shahzad31 Feb 17, 2025
f0f96b3
Update heartbeat/monitors/maintwin/maintwin.go
shahzad31 Feb 17, 2025
f28455a
Update heartbeat/monitors/maintwin/maintwin.go
shahzad31 Feb 17, 2025
897f3e7
fomat
shahzad31 Feb 17, 2025
a80dc6e
Merge branch 'main' of https://github.com/elastic/beats into maintena…
shahzad31 Feb 17, 2025
364bc72
change log
shahzad31 Feb 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22969,6 +22969,37 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


--------------------------------------------------------------------------------
Dependency : github.com/teambition/rrule-go
Version: v1.8.2
Licence type (autodetected): MIT
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/github.com/teambition/[email protected]/LICENSE:

MIT License

Copyright (c) 2017-2023 Teambition

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


--------------------------------------------------------------------------------
Dependency : github.com/tklauser/go-sysconf
Version: v0.3.12
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ require (
github.com/pkg/xattr v0.4.9
github.com/prometheus/prometheus v0.54.1
github.com/shirou/gopsutil/v4 v4.24.10
github.com/teambition/rrule-go v1.8.2
github.com/tklauser/go-sysconf v0.3.12
github.com/xdg-go/scram v1.1.2
github.com/zyedidia/generic v1.2.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8=
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
Expand Down
109 changes: 109 additions & 0 deletions heartbeat/monitors/maintwin/maintwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package maintwin

import (
"fmt"
"time"

"github.com/teambition/rrule-go"
)

var weekdayLookup = map[string]rrule.Weekday{
"MO": rrule.MO, "TU": rrule.TU, "WE": rrule.WE, "TH": rrule.TH, "FR": rrule.FR, "SA": rrule.SA, "SU": rrule.SU,
}

type MaintWin struct {
Freq rrule.Frequency `config:"freq" validate:"required"`
Dtstart string `config:"dtstart" validate:"required"`
Interval int `config:"interval"`
Duration time.Duration `config:"duration" validate:"required"`
Wkst rrule.Weekday `config:"wkst"`
Count int `config:"count"`
Bysetpos []int `config:"bysetpos"`
Bymonth []int `config:"bymonth"`
Bymonthday []int `config:"bymonthday"`
Byyearday []int `config:"byyearday"`
Byweekno []int `config:"byweekno"`
Byweekday []string `config:"byweekday"`
Byhour []int `config:"byhour"`
Byminute []int `config:"byminute"`
Bysecond []int `config:"bysecond"`
Byeaster []int `config:"byeaster"`
}

func (mw *MaintWin) Parse() (r *rrule.RRule, err error) {

// validate the frequency, we don't support less than daily
if mw.Freq > rrule.DAILY {
return nil, fmt.Errorf("invalid frequency: only yearly, monthly, weekly, and daily are supported")
}

dtstart, err := time.Parse(time.RFC3339, mw.Dtstart)
if err != nil {
return nil, err
}

// Convert the string weekdays to rrule.Weekday
weekdays := []rrule.Weekday{}
for _, wd := range mw.Byweekday {
if weekday, exists := weekdayLookup[wd]; exists {
weekdays = append(weekdays, weekday)
}
}

dtstart = dtstart.UTC()

r, err = rrule.NewRRule(rrule.ROption{
Freq: mw.Freq,
Count: mw.Count,
Dtstart: dtstart,
Interval: mw.Interval,
Byweekday: weekdays,
Byhour: mw.Byhour,
Byminute: mw.Byminute,
Bysecond: mw.Bysecond,
Byeaster: mw.Byeaster,
Bysetpos: mw.Bysetpos,
Bymonth: mw.Bymonth,
Byweekno: mw.Byweekno,
Byyearday: mw.Byyearday,
Bymonthday: mw.Bymonthday,
Wkst: mw.Wkst,
})
if err != nil {
return nil, err
}

return r, nil
}

type ParsedMaintWin struct {
Rule *rrule.RRule
Duration time.Duration
}

func (pmw ParsedMaintWin) IsActive(tOrig time.Time) bool {
if pmw.Rule == nil {
return false
}
tOrig = tOrig.UTC()
r := pmw.Rule
window := r.Before(tOrig, true)
return tOrig.Before(window.Add(pmw.Duration))
}
188 changes: 188 additions & 0 deletions heartbeat/monitors/maintwin/maintwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package maintwin

import (
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/teambition/rrule-go"
)

func TestMaintWin(t *testing.T) {
cases := []struct {
name string
mw MaintWin
positiveMatches []string
negativeMatches []string
}{
{
"Every sunday at midnight to 1 AM",
MaintWin{
Freq: 3,
Dtstart: time.Now().Format(time.RFC3339),
Duration: mustParseDuration("2h"),
Byweekday: []string{"SU", "MO", "TU", "WE", "TH", "FR", "SA"},
Count: 10,
},
// add 30 minutes, 1 hour, 1 hour 30 minutes to the start time
[]string{time.Now().Add(30 * time.Minute).Format(time.RFC3339), time.Now().Add(60 * time.Minute).Format(time.RFC3339), time.Now().Add(90 * time.Minute).Format(time.RFC3339)},
[]string{time.Now().Add(180 * time.Minute).Format(time.RFC3339), time.Now().Add(540 * time.Minute).Format(time.RFC3339)},
},

{
name: "Daily maintenance window for 2 hours",
mw: MaintWin{
Freq: rrule.DAILY,
Dtstart: "2025-02-06T21:00:00Z",
Duration: mustParseDuration("2h"),
},
positiveMatches: []string{"2025-02-06T21:30:00Z", "2025-02-06T22:45:00Z"},
negativeMatches: []string{"2025-02-06T23:01:00Z", "2025-02-07T00:00:00Z"},
},

{
name: "Monthly maintenance window on the 1st",
mw: MaintWin{
Freq: rrule.MONTHLY,
Dtstart: "2025-02-01T10:00:00Z",
Duration: mustParseDuration("2h"),
Bymonthday: []int{1},
},
positiveMatches: []string{"2025-03-01T10:30:00Z", "2025-04-01T11:45:00Z"},
negativeMatches: []string{"2025-02-02T10:30:00Z", "2025-02-01T12:01:00Z"},
},

{
name: "Weekly on Monday and Wednesday from 8 AM to 10 AM",
mw: MaintWin{
Freq: rrule.WEEKLY,
Dtstart: "2025-02-03T08:00:00Z",
Duration: mustParseDuration("2h"),
Byweekday: []string{"MO", "WE"},
},
positiveMatches: []string{"2025-02-10T09:30:00Z", "2025-02-12T08:15:00Z"},
negativeMatches: []string{"2025-02-10T10:30:00Z", "2025-02-11T09:30:00Z"},
},

{
name: "First Friday of every month",
mw: MaintWin{
Freq: rrule.MONTHLY,
Dtstart: "2025-02-07T12:00:00Z",
Duration: mustParseDuration("2h"),
Byweekday: []string{"FR"},
Bysetpos: []int{1}, // First Friday of the month
},
positiveMatches: []string{"2025-03-07T12:30:00Z"},
negativeMatches: []string{"2025-02-14T12:30:00Z", "2025-04-14T13:00:00Z"},
},

{
name: "Every Saturday and Sunday from 5 PM to 8 PM",
mw: MaintWin{
Freq: rrule.WEEKLY,
Dtstart: "2025-02-08T17:00:00Z",
Duration: mustParseDuration("3h"),
Byweekday: []string{"SA", "SU"},
},
positiveMatches: []string{"2025-02-09T18:30:00Z", "2025-02-15T19:00:00Z"},
negativeMatches: []string{"2025-02-09T20:30:00Z", "2025-02-10T17:30:00Z"},
},

{
name: "Monthly on the 15th from 6 AM to 9 AM",
mw: MaintWin{
Freq: rrule.MONTHLY,
Dtstart: "2025-02-15T06:00:00Z",
Duration: mustParseDuration("3h"),
Bymonthday: []int{15},
},
positiveMatches: []string{"2025-03-15T07:30:00Z", "2025-04-15T08:45:00Z"},
negativeMatches: []string{"2025-02-16T07:30:00Z", "2025-02-15T09:30:00Z"},
},

{
name: "Yearly maintenance on Jan 1 from Midnight to 3 AM",
mw: MaintWin{
Freq: rrule.YEARLY,
Dtstart: "2025-01-01T00:00:00Z",
Duration: mustParseDuration("3h"),
Bymonthday: []int{1},
},
positiveMatches: []string{"2026-01-01T01:30:00Z", "2027-01-01T02:45:00Z"},
negativeMatches: []string{"2025-01-02T01:30:00Z", "2025-01-01T03:30:00Z"},
},

{
name: "Every other day for 4 hours",
mw: MaintWin{
Freq: rrule.DAILY,
Dtstart: "2025-02-06T08:00:00Z",
Duration: mustParseDuration("4h"),
Interval: 2, // Every other day
Count: 10,
},
positiveMatches: []string{"2025-02-08T09:30:00Z", "2025-02-10T11:00:00Z"},
negativeMatches: []string{"2025-02-07T09:30:00Z", "2025-02-06T13:00:00Z"},
},
{
name: "Every day",
mw: MaintWin{
Freq: rrule.DAILY,
Dtstart: "2005-02-06T08:00:00Z",
Duration: mustParseDuration("1h"),
},
positiveMatches: []string{"2025-02-08T08:30:00Z"},
negativeMatches: []string{"2025-02-07T09:30:00Z"},
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
r, err := c.mw.Parse()
require.NoError(t, err)
pmw := ParsedMaintWin{Rule: r, Duration: c.mw.Duration}
for _, m := range c.positiveMatches {
t.Run(fmt.Sprintf("does match %s", m), func(t *testing.T) {
pt, err := time.Parse(time.RFC3339, m)
require.NoError(t, err)
assert.True(t, pmw.IsActive(pt.UTC()))
})
}
for _, m := range c.negativeMatches {
t.Run(fmt.Sprintf("does not match %s", m), func(t *testing.T) {
pt, err := time.Parse(time.RFC3339, m)
require.NoError(t, err)
assert.False(t, pmw.IsActive(pt))
})
}
})
}
}

func mustParseDuration(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
panic(fmt.Sprintf("could not parse duration %s: %s", s, err))
}
return d
}
Loading
Loading