Skip to content

Commit 19bdd53

Browse files
Merge pull request #1731 from exdx/fix/catalog-sync
Bug 1867802: shorten catalog sync interval if polling is enabled
2 parents f83b235 + 1bc18c5 commit 19bdd53

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,14 @@ func (o *Operator) syncRegistryServer(logger *logrus.Entry, in *v1alpha1.Catalog
637637

638638
logger.Debug("ensured registry server")
639639

640+
// requeue the catalog sync based on the polling interval, for accurate syncs of catalogs with polling enabled
641+
if out.Spec.UpdateStrategy != nil {
642+
logger.Debugf("requeuing registry server sync based on polling interval %s", out.Spec.UpdateStrategy.Interval.Duration.String())
643+
resyncPeriod := reconciler.SyncRegistryUpdateInterval(out)
644+
o.catsrcQueueSet.RequeueAfter(out.GetNamespace(), out.GetName(), queueinformer.ResyncWithJitter(resyncPeriod, 0.1)())
645+
return
646+
}
647+
640648
if err := o.sources.Remove(sourceKey); err != nil {
641649
o.logger.WithError(err).Debug("error closing client connection")
642650
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package reconciler
2+
3+
import (
4+
"time"
5+
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
8+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
9+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
10+
)
11+
12+
// SyncRegistryUpdateInterval returns a duration to use when requeuing the catalog source for reconciliation.
13+
// This ensures that the catalog is being synced on the correct time interval based on its spec.
14+
// Note: this function assumes the catalog has an update strategy set.
15+
func SyncRegistryUpdateInterval(source *v1alpha1.CatalogSource) time.Duration {
16+
pollingInterval := source.Spec.UpdateStrategy.Interval.Duration
17+
latestPoll := source.Status.LatestImageRegistryPoll
18+
creationTimestamp := source.CreationTimestamp.Time
19+
20+
// Resync before next default sync if the polling interval is less than the default
21+
if pollingInterval <= queueinformer.DefaultResyncPeriod {
22+
return pollingInterval
23+
}
24+
// 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)
26+
}
27+
28+
// defaultOr returns either the default resync period or the modulus of the polling interval and the default.
29+
// 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 {
32+
if latestPoll.IsZero() {
33+
latestPoll = &metav1.Time{Time: creationTimestamp}
34+
}
35+
// 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+
}
39+
// return the default sync period otherwise: the next sync cycle will check again
40+
return queueinformer.DefaultResyncPeriod
41+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package reconciler
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
9+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
10+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
11+
)
12+
13+
func TestSyncRegistryUpdateInterval(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
source *v1alpha1.CatalogSource
17+
expected time.Duration
18+
}{
19+
{
20+
name: "PollingInterval10Minutes/FirstUpdate",
21+
source: &v1alpha1.CatalogSource{
22+
Spec: v1alpha1.CatalogSourceSpec{
23+
UpdateStrategy: &v1alpha1.UpdateStrategy{
24+
RegistryPoll: &v1alpha1.RegistryPoll{
25+
Interval: &metav1.Duration{
26+
Duration: 10 * time.Minute,
27+
},
28+
},
29+
},
30+
},
31+
},
32+
expected: 10 * time.Minute,
33+
},
34+
{
35+
name: "PollingInterval15Minutes/FirstUpdate",
36+
source: &v1alpha1.CatalogSource{
37+
Spec: v1alpha1.CatalogSourceSpec{
38+
UpdateStrategy: &v1alpha1.UpdateStrategy{
39+
RegistryPoll: &v1alpha1.RegistryPoll{
40+
Interval: &metav1.Duration{
41+
Duration: 15 * time.Minute,
42+
},
43+
},
44+
},
45+
},
46+
},
47+
expected: 15 * time.Minute,
48+
},
49+
{
50+
name: "PollingInterval10Minutes/AlreadyUpdated",
51+
source: &v1alpha1.CatalogSource{
52+
Status: v1alpha1.CatalogSourceStatus{
53+
LatestImageRegistryPoll: &metav1.Time{
54+
time.Now().Add(-(5 * time.Minute)),
55+
},
56+
},
57+
Spec: v1alpha1.CatalogSourceSpec{
58+
UpdateStrategy: &v1alpha1.UpdateStrategy{
59+
RegistryPoll: &v1alpha1.RegistryPoll{
60+
Interval: &metav1.Duration{
61+
Duration: 10 * time.Minute,
62+
},
63+
},
64+
},
65+
},
66+
},
67+
expected: 10 * time.Minute,
68+
},
69+
{
70+
name: "PollingInterval40Minutes/FirstUpdate",
71+
source: &v1alpha1.CatalogSource{
72+
ObjectMeta: metav1.ObjectMeta{
73+
CreationTimestamp: metav1.Time{
74+
Time: time.Now().Add(-(35 * time.Minute)),
75+
},
76+
},
77+
Spec: v1alpha1.CatalogSourceSpec{
78+
UpdateStrategy: &v1alpha1.UpdateStrategy{
79+
RegistryPoll: &v1alpha1.RegistryPoll{
80+
Interval: &metav1.Duration{
81+
Duration: 40 * time.Minute,
82+
},
83+
},
84+
},
85+
},
86+
},
87+
expected: 10 * time.Minute,
88+
},
89+
{
90+
name: "PollingInterval40Minutes/AlreadyUpdated30MinutesAgo",
91+
source: &v1alpha1.CatalogSource{
92+
Status: v1alpha1.CatalogSourceStatus{
93+
LatestImageRegistryPoll: &metav1.Time{
94+
time.Now().Add(-(30 * time.Minute)),
95+
},
96+
},
97+
Spec: v1alpha1.CatalogSourceSpec{
98+
UpdateStrategy: &v1alpha1.UpdateStrategy{
99+
RegistryPoll: &v1alpha1.RegistryPoll{
100+
Interval: &metav1.Duration{
101+
Duration: 40 * time.Minute,
102+
},
103+
},
104+
},
105+
},
106+
},
107+
expected: 10 * time.Minute,
108+
},
109+
{
110+
name: "PollingInterval1hour/FirstUpdate",
111+
source: &v1alpha1.CatalogSource{
112+
ObjectMeta: metav1.ObjectMeta{
113+
CreationTimestamp: metav1.Time{
114+
Time: time.Now().Add(-(15 * time.Minute)),
115+
},
116+
},
117+
Spec: v1alpha1.CatalogSourceSpec{
118+
UpdateStrategy: &v1alpha1.UpdateStrategy{
119+
RegistryPoll: &v1alpha1.RegistryPoll{
120+
Interval: &metav1.Duration{
121+
Duration: 1 * time.Hour,
122+
},
123+
},
124+
},
125+
},
126+
},
127+
expected: queueinformer.DefaultResyncPeriod,
128+
},
129+
{
130+
name: "PollingInterval10Hours/AlreadyUpdated",
131+
source: &v1alpha1.CatalogSource{
132+
Status: v1alpha1.CatalogSourceStatus{
133+
LatestImageRegistryPoll: &metav1.Time{
134+
time.Now().Add(-(15 * time.Minute)),
135+
},
136+
},
137+
Spec: v1alpha1.CatalogSourceSpec{
138+
UpdateStrategy: &v1alpha1.UpdateStrategy{
139+
RegistryPoll: &v1alpha1.RegistryPoll{
140+
Interval: &metav1.Duration{
141+
Duration: 10 * time.Hour,
142+
},
143+
},
144+
},
145+
},
146+
},
147+
expected: queueinformer.DefaultResyncPeriod,
148+
},
149+
}
150+
151+
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+
}
157+
}
158+
}

0 commit comments

Comments
 (0)