Skip to content

Commit e5a160a

Browse files
committed
Fix zero-delay resyncs for certain registry update policies.
If the registry update polling interval is greater than and a multiple of the operator's default resync period (15m), and there are fewer than 15 minutes remaining until the next poll is due, the catalog-operator will continuously resync its associated CatalogSource until the next poll time is reached. This is not beneficial to update polling and can generate excessive load that negatively impacts useful work on the cluster. Signed-off-by: Ben Luddy <[email protected]>
1 parent 93c7978 commit e5a160a

File tree

3 files changed

+43
-19
lines changed

3 files changed

+43
-19
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ func (o *Operator) syncRegistryServer(logger *logrus.Entry, in *v1alpha1.Catalog
640640
// requeue the catalog sync based on the polling interval, for accurate syncs of catalogs with polling enabled
641641
if out.Spec.UpdateStrategy != nil {
642642
logger.Debugf("requeuing registry server sync based on polling interval %s", out.Spec.UpdateStrategy.Interval.Duration.String())
643-
resyncPeriod := reconciler.SyncRegistryUpdateInterval(out)
643+
resyncPeriod := reconciler.SyncRegistryUpdateInterval(out, time.Now())
644644
o.catsrcQueueSet.RequeueAfter(out.GetNamespace(), out.GetName(), queueinformer.ResyncWithJitter(resyncPeriod, 0.1)())
645645
return
646646
}

pkg/controller/registry/reconciler/grpc_polling.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
// SyncRegistryUpdateInterval returns a duration to use when requeuing the catalog source for reconciliation.
1313
// This ensures that the catalog is being synced on the correct time interval based on its spec.
1414
// Note: this function assumes the catalog has an update strategy set.
15-
func SyncRegistryUpdateInterval(source *v1alpha1.CatalogSource) time.Duration {
15+
func SyncRegistryUpdateInterval(source *v1alpha1.CatalogSource, now time.Time) time.Duration {
1616
pollingInterval := source.Spec.UpdateStrategy.Interval.Duration
1717
latestPoll := source.Status.LatestImageRegistryPoll
1818
creationTimestamp := source.CreationTimestamp.Time
@@ -22,19 +22,21 @@ func SyncRegistryUpdateInterval(source *v1alpha1.CatalogSource) time.Duration {
2222
return pollingInterval
2323
}
2424
// Resync based on the delta between the default sync and the actual last poll if the interval is greater than the default
25-
return defaultOr(latestPoll, pollingInterval, creationTimestamp)
25+
return defaultOr(latestPoll, pollingInterval, creationTimestamp, now)
2626
}
2727

28-
// defaultOr returns either the default resync period or the modulus of the polling interval and the default.
28+
// defaultOr returns either the default resync period or the time remaining until the next poll is due, whichever is smaller.
2929
// For example, if the polling interval is 40 minutes, OLM will sync after ~30 minutes and see that the next sync
30-
// for this catalog should be sooner than the default (15 minutes) and return 10 minutes (40 % 15).
31-
func defaultOr(latestPoll *metav1.Time, pollingInterval time.Duration, creationTimestamp time.Time) time.Duration {
30+
// for this catalog should be in 10 minutes, sooner than the default 15 minutes, and return 10 minutes.
31+
func defaultOr(latestPoll *metav1.Time, pollingInterval time.Duration, creationTimestamp time.Time, now time.Time) time.Duration {
3232
if latestPoll.IsZero() {
3333
latestPoll = &metav1.Time{Time: creationTimestamp}
3434
}
35+
36+
remaining := latestPoll.Add(pollingInterval).Sub(now)
3537
// sync ahead of the default interval in the case where now + default sync is after the last sync plus the interval
36-
if time.Now().Add(queueinformer.DefaultResyncPeriod).After(latestPoll.Add(pollingInterval)) {
37-
return (pollingInterval % queueinformer.DefaultResyncPeriod).Round(time.Minute)
38+
if remaining < queueinformer.DefaultResyncPeriod {
39+
return remaining
3840
}
3941
// return the default sync period otherwise: the next sync cycle will check again
4042
return queueinformer.DefaultResyncPeriod

pkg/controller/registry/reconciler/grpc_polling_test.go

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
)
1212

1313
func TestSyncRegistryUpdateInterval(t *testing.T) {
14+
now := time.Date(2021, time.January, 29, 14, 47, 0, 0, time.UTC)
1415
tests := []struct {
1516
name string
1617
source *v1alpha1.CatalogSource
@@ -46,12 +47,32 @@ func TestSyncRegistryUpdateInterval(t *testing.T) {
4647
},
4748
expected: 15 * time.Minute,
4849
},
50+
{
51+
name: "PollingIntervalMultipleOfDefaultResyncPeriod",
52+
source: &v1alpha1.CatalogSource{
53+
Spec: v1alpha1.CatalogSourceSpec{
54+
UpdateStrategy: &v1alpha1.UpdateStrategy{
55+
RegistryPoll: &v1alpha1.RegistryPoll{
56+
Interval: &metav1.Duration{
57+
Duration: 2 * queueinformer.DefaultResyncPeriod,
58+
},
59+
},
60+
},
61+
},
62+
Status: v1alpha1.CatalogSourceStatus{
63+
LatestImageRegistryPoll: &metav1.Time{
64+
Time: now.Add(1*time.Second - 2*queueinformer.DefaultResyncPeriod),
65+
},
66+
},
67+
},
68+
expected: 1 * time.Second,
69+
},
4970
{
5071
name: "PollingInterval10Minutes/AlreadyUpdated",
5172
source: &v1alpha1.CatalogSource{
5273
Status: v1alpha1.CatalogSourceStatus{
5374
LatestImageRegistryPoll: &metav1.Time{
54-
time.Now().Add(-(5 * time.Minute)),
75+
Time: now.Add(-(5 * time.Minute)),
5576
},
5677
},
5778
Spec: v1alpha1.CatalogSourceSpec{
@@ -71,7 +92,7 @@ func TestSyncRegistryUpdateInterval(t *testing.T) {
7192
source: &v1alpha1.CatalogSource{
7293
ObjectMeta: metav1.ObjectMeta{
7394
CreationTimestamp: metav1.Time{
74-
Time: time.Now().Add(-(35 * time.Minute)),
95+
Time: now.Add(-(35 * time.Minute)),
7596
},
7697
},
7798
Spec: v1alpha1.CatalogSourceSpec{
@@ -84,14 +105,14 @@ func TestSyncRegistryUpdateInterval(t *testing.T) {
84105
},
85106
},
86107
},
87-
expected: 10 * time.Minute,
108+
expected: 5 * time.Minute,
88109
},
89110
{
90111
name: "PollingInterval40Minutes/AlreadyUpdated30MinutesAgo",
91112
source: &v1alpha1.CatalogSource{
92113
Status: v1alpha1.CatalogSourceStatus{
93114
LatestImageRegistryPoll: &metav1.Time{
94-
time.Now().Add(-(30 * time.Minute)),
115+
Time: now.Add(-(30 * time.Minute)),
95116
},
96117
},
97118
Spec: v1alpha1.CatalogSourceSpec{
@@ -111,7 +132,7 @@ func TestSyncRegistryUpdateInterval(t *testing.T) {
111132
source: &v1alpha1.CatalogSource{
112133
ObjectMeta: metav1.ObjectMeta{
113134
CreationTimestamp: metav1.Time{
114-
Time: time.Now().Add(-(15 * time.Minute)),
135+
Time: now.Add(-(15 * time.Minute)),
115136
},
116137
},
117138
Spec: v1alpha1.CatalogSourceSpec{
@@ -131,7 +152,7 @@ func TestSyncRegistryUpdateInterval(t *testing.T) {
131152
source: &v1alpha1.CatalogSource{
132153
Status: v1alpha1.CatalogSourceStatus{
133154
LatestImageRegistryPoll: &metav1.Time{
134-
time.Now().Add(-(15 * time.Minute)),
155+
Time: now.Add(-(15 * time.Minute)),
135156
},
136157
},
137158
Spec: v1alpha1.CatalogSourceSpec{
@@ -149,10 +170,11 @@ func TestSyncRegistryUpdateInterval(t *testing.T) {
149170
}
150171

151172
for _, tt := range tests {
152-
t.Logf("name %s", tt.name)
153-
d := SyncRegistryUpdateInterval(tt.source)
154-
if d != tt.expected {
155-
t.Fatalf("unexpected registry sync interval for %s: expected %#v got %#v", tt.name, tt.expected, d)
156-
}
173+
t.Run(tt.name, func(t *testing.T) {
174+
d := SyncRegistryUpdateInterval(tt.source, now)
175+
if d != tt.expected {
176+
t.Errorf("unexpected registry sync interval for %s: expected %s got %s", tt.name, tt.expected, d)
177+
}
178+
})
157179
}
158180
}

0 commit comments

Comments
 (0)