Skip to content

Commit f1bdb11

Browse files
committed
tiny post-GA tool to reduce exec frequency of periodics
Signed-off-by: Jakub Guzik <[email protected]>
1 parent 64da4f1 commit f1bdb11

File tree

2 files changed

+217
-0
lines changed

2 files changed

+217
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"math/rand"
7+
"regexp"
8+
"strings"
9+
"time"
10+
11+
"github.com/sirupsen/logrus"
12+
13+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
14+
15+
"github.com/openshift/ci-tools/pkg/api"
16+
"github.com/openshift/ci-tools/pkg/api/ocplifecycle"
17+
"github.com/openshift/ci-tools/pkg/config"
18+
"gopkg.in/robfig/cron.v2"
19+
)
20+
21+
type options struct {
22+
config.ConfirmableOptions
23+
currentOCPVersion string
24+
}
25+
26+
func (o options) validate() error {
27+
var errs []error
28+
if err := o.ConfirmableOptions.Validate(); err != nil {
29+
errs = append(errs, err)
30+
}
31+
32+
return utilerrors.NewAggregate(errs)
33+
}
34+
35+
func gatherOptions() options {
36+
o := options{}
37+
flag.StringVar(&o.currentOCPVersion, "current-release", "", "Current OCP version")
38+
39+
o.Bind(flag.CommandLine)
40+
flag.Parse()
41+
42+
return o
43+
}
44+
45+
func main() {
46+
o := gatherOptions()
47+
if err := o.validate(); err != nil {
48+
logrus.Fatalf("Invalid options: %v", err)
49+
}
50+
51+
ocpVersion, err := ocplifecycle.ParseMajorMinor(o.currentOCPVersion)
52+
if err != nil {
53+
logrus.Fatalf("Not valid --current-release: %v", err)
54+
}
55+
56+
if err := o.ConfirmableOptions.Complete(); err != nil {
57+
logrus.Fatalf("Couldn't complete the config options: %v", err)
58+
}
59+
60+
if err := o.OperateOnCIOperatorConfigDir(o.ConfigDir, func(configuration *api.ReleaseBuildConfiguration, info *config.Info) error {
61+
output := config.DataWithInfo{Configuration: *configuration, Info: *info}
62+
updateIntervalFieldsForMatchedSteps(&output, *ocpVersion)
63+
64+
if err := output.CommitTo(o.ConfigDir); err != nil {
65+
logrus.WithError(err).Fatal("commitTo failed")
66+
}
67+
return nil
68+
}); err != nil {
69+
logrus.WithError(err).Fatal("Could not branch configurations.")
70+
}
71+
72+
}
73+
74+
func updateIntervalFieldsForMatchedSteps(
75+
configuration *config.DataWithInfo,
76+
version ocplifecycle.MajorMinor,
77+
) {
78+
testVersion, err := ocplifecycle.ParseMajorMinor(extractVersion(configuration.Info.Metadata.Branch))
79+
if err != nil {
80+
return
81+
}
82+
if configuration.Info.Metadata.Org == "openshift" || configuration.Info.Metadata.Org == "openshift-priv" {
83+
for _, test := range configuration.Configuration.Tests {
84+
if !strings.Contains(test.As, "mirror-nightly-image") && !strings.Contains(test.As, "promote-") {
85+
if test.Cron != nil {
86+
// check if less then past past version
87+
if testVersion.Less(ocplifecycle.MajorMinor{Major: version.Major, Minor: version.Minor - 2}) {
88+
correctCron, err := isExecutedAtMostXTimesAMonth(*test.Cron, 1)
89+
if err != nil {
90+
logrus.Warningf("Can't parse cron string %s", *test.Cron)
91+
continue
92+
}
93+
if !correctCron {
94+
*test.Cron = generateMonthlyCron()
95+
}
96+
} else if testVersion.GetVersion() == version.GetPastPastVersion() {
97+
correctCron, err := isExecutedAtMostXTimesAMonth(*test.Cron, 2)
98+
if err != nil {
99+
logrus.Warningf("Can't parse cron string %s", *test.Cron)
100+
continue
101+
}
102+
if !correctCron {
103+
*test.Cron = generateBiWeeklyCron()
104+
}
105+
} else if testVersion.GetVersion() == version.GetPastVersion() {
106+
correctCron, err := isExecutedAtMostXTimesAMonth(*test.Cron, 4)
107+
if err != nil {
108+
logrus.Warningf("Can't parse cron string %s", *test.Cron)
109+
continue
110+
}
111+
if !correctCron {
112+
*test.Cron = generateWeeklyWeekendCron()
113+
}
114+
}
115+
}
116+
if test.Interval != nil {
117+
if testVersion.Less(ocplifecycle.MajorMinor{Major: version.Major, Minor: version.Minor - 2}) {
118+
duration, err := time.ParseDuration(*test.Interval)
119+
if err != nil {
120+
logrus.Warningf("Can't parse interval string %s", *test.Cron)
121+
continue
122+
}
123+
if duration < time.Hour*24*28 {
124+
cronExpr := generateWeeklyWeekendCron()
125+
test.Cron = &cronExpr
126+
test.Interval = nil
127+
}
128+
} else if testVersion.GetVersion() == version.GetPastPastVersion() {
129+
duration, err := time.ParseDuration(*test.Interval)
130+
if err != nil {
131+
logrus.Warningf("Can't parse interval string %s", *test.Cron)
132+
continue
133+
}
134+
if duration < time.Hour*24*14 {
135+
cronExpr := generateBiWeeklyCron()
136+
test.Cron = &cronExpr
137+
test.Interval = nil
138+
}
139+
} else if testVersion.GetVersion() == version.GetPastVersion() {
140+
duration, err := time.ParseDuration(*test.Interval)
141+
if err != nil {
142+
logrus.Warningf("Can't parse interval string %s", *test.Cron)
143+
continue
144+
}
145+
if duration < time.Hour*24*7 {
146+
cronExpr := generateWeeklyWeekendCron()
147+
test.Cron = &cronExpr
148+
test.Interval = nil
149+
}
150+
}
151+
}
152+
}
153+
}
154+
}
155+
}
156+
157+
func isExecutedAtMostXTimesAMonth(cronExpr string, x int) (bool, error) {
158+
switch strings.ToLower(cronExpr) {
159+
case "@daily":
160+
cronExpr = "0 0 * * *"
161+
case "@weekly":
162+
cronExpr = "0 0 * * 0"
163+
case "@monthly":
164+
cronExpr = "0 0 1 * *"
165+
case "@yearly", "@annually":
166+
cronExpr = "0 0 1 1 *"
167+
}
168+
169+
schedule, err := cron.Parse(cronExpr)
170+
if err != nil {
171+
return false, err
172+
}
173+
start := time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC)
174+
end := start.AddDate(0, 1, 0)
175+
176+
executionCount := 0
177+
for {
178+
next := schedule.Next(start)
179+
if next.After(end) {
180+
break
181+
}
182+
executionCount++
183+
start = next
184+
}
185+
186+
return executionCount <= x, nil
187+
}
188+
189+
func generateWeeklyWeekendCron() string {
190+
randDay := rand.Intn(2)
191+
selectedDay := randDay * 6
192+
return fmt.Sprintf("%d %d * * %d", rand.Intn(60), rand.Intn(24), selectedDay)
193+
}
194+
195+
func generateBiWeeklyCron() string {
196+
return fmt.Sprintf("%d %d %d,%d * *", rand.Intn(60), rand.Intn(24), rand.Intn(10)+5, rand.Intn(14)+15)
197+
}
198+
199+
func generateMonthlyCron() string {
200+
return fmt.Sprintf("%d %d %d * *", rand.Intn(60), rand.Intn(24), rand.Intn(28)+1)
201+
}
202+
203+
func extractVersion(s string) string {
204+
pattern := `^(release|openshift)-(\d+\.\d+)$`
205+
re := regexp.MustCompile(pattern)
206+
207+
matches := re.FindStringSubmatch(s)
208+
209+
if len(matches) > 2 {
210+
return matches[2]
211+
}
212+
return ""
213+
}

pkg/api/ocplifecycle/ocplifecycle.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ func (m MajorMinor) GetPastVersion() string {
212212
return fmt.Sprintf("%d.%d", m.Major, m.Minor-1)
213213
}
214214

215+
func (m MajorMinor) GetPastPastVersion() string {
216+
return fmt.Sprintf("%d.%d", m.Major, m.Minor-1)
217+
}
218+
215219
func (m MajorMinor) GetVersion() string {
216220
return fmt.Sprintf("%d.%d", m.Major, m.Minor)
217221
}

0 commit comments

Comments
 (0)