Skip to content

Commit 103ed08

Browse files
committed
Give Sources control of invalidation for Snapshots they produce.
This moves the hard-coded 5-minute TTL out of the cache and into the registry-based implementation of cache.Source. The hack that makes snapshots produced by "virtual" sources is no longer necessary. Signed-off-by: Ben Luddy <[email protected]>
1 parent 857c9da commit 103ed08

File tree

9 files changed

+133
-132
lines changed

9 files changed

+133
-132
lines changed

pkg/controller/operators/catalog/operator.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ type Operator struct {
117117
bundleUnpackTimeout time.Duration
118118
clientFactory clients.Factory
119119
muInstallPlan sync.Mutex
120+
resolverSourceProvider *resolver.RegistrySourceProvider
120121
}
121122

122123
type CatalogSourceSyncFunc func(logger *logrus.Entry, in *v1alpha1.CatalogSource) (out *v1alpha1.CatalogSource, continueSync bool, syncError error)
@@ -188,8 +189,9 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
188189
clientFactory: clients.NewFactory(config),
189190
}
190191
op.sources = grpc.NewSourceStore(logger, 10*time.Second, 10*time.Minute, op.syncSourceState)
192+
op.resolverSourceProvider = resolver.SourceProviderFromRegistryClientProvider(op.sources, logger)
191193
op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now, ssaClient)
192-
res := resolver.NewOperatorStepResolver(lister, crClient, operatorNamespace, op.sources, logger)
194+
res := resolver.NewOperatorStepResolver(lister, crClient, operatorNamespace, op.resolverSourceProvider, logger)
193195
op.resolver = resolver.NewInstrumentedResolver(res, metrics.RegisterDependencyResolutionSuccess, metrics.RegisterDependencyResolutionFailure)
194196

195197
// Wire OLM CR sharedIndexInformers
@@ -465,7 +467,7 @@ func (o *Operator) syncSourceState(state grpc.SourceState) {
465467

466468
switch state.State {
467469
case connectivity.Ready:
468-
o.resolver.Expire(resolvercache.SourceKey(state.Key))
470+
o.resolverSourceProvider.Invalidate(resolvercache.SourceKey(state.Key))
469471
if o.namespace == state.Key.Namespace {
470472
namespaces, err := index.CatalogSubscriberNamespaces(o.catalogSubscriberIndexer,
471473
state.Key.Name, state.Key.Namespace)

pkg/controller/registry/resolver/cache/cache.go

+29-26
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ func (p StaticSourceProvider) Sources(namespaces ...string) map[SourceKey]Source
6969

7070
type OperatorCacheProvider interface {
7171
Namespaced(namespaces ...string) MultiCatalogOperatorFinder
72-
Expire(catalog SourceKey)
7372
}
7473

7574
type SourcePriorityProvider interface {
@@ -150,22 +149,11 @@ func (c *NamespacedOperatorCache) Error() error {
150149
return errors.NewAggregate(errs)
151150
}
152151

153-
func (c *Cache) Expire(catalog SourceKey) {
154-
c.m.Lock()
155-
defer c.m.Unlock()
156-
s, ok := c.snapshots[catalog]
157-
if !ok {
158-
return
159-
}
160-
s.expiry = time.Unix(0, 0)
161-
}
162-
163152
func (c *Cache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder {
164153
const (
165154
CachePopulateTimeout = time.Minute
166155
)
167156

168-
now := time.Now()
169157
sources := c.sp.Sources(namespaces...)
170158

171159
result := NamespacedOperatorCache{
@@ -182,7 +170,7 @@ func (c *Cache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder {
182170
func() {
183171
snapshot.m.RLock()
184172
defer snapshot.m.RUnlock()
185-
if snapshot.Valid(now) {
173+
if snapshot.Valid() {
186174
result.snapshots[key] = snapshot
187175
} else {
188176
misses = append(misses, key)
@@ -205,7 +193,7 @@ func (c *Cache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder {
205193
// Take the opportunity to clear expired snapshots while holding the lock.
206194
var expired []SourceKey
207195
for key, snapshot := range c.snapshots {
208-
if !snapshot.Valid(now) {
196+
if !snapshot.Valid() {
209197
snapshot.Cancel()
210198
expired = append(expired, key)
211199
}
@@ -217,7 +205,7 @@ func (c *Cache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder {
217205
// Check for any snapshots that were populated while waiting to acquire the lock.
218206
var found int
219207
for i := range misses {
220-
if hdr, ok := c.snapshots[misses[i]]; ok && hdr.Valid(now) {
208+
if hdr, ok := c.snapshots[misses[i]]; ok && hdr.Valid() {
221209
result.snapshots[misses[i]] = hdr
222210
misses[found], misses[i] = misses[i], misses[found]
223211
found++
@@ -230,17 +218,10 @@ func (c *Cache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder {
230218

231219
hdr := snapshotHeader{
232220
key: miss,
233-
expiry: now.Add(c.ttl),
234221
pop: cancel,
235222
priority: c.sourcePriorityProvider.Priority(miss),
236223
}
237224

238-
if miss.Virtual() {
239-
// hack! always refresh virtual catalogs.
240-
// todo: Sources should be responsible for determining when the Snapshots they produce become invalid
241-
hdr.expiry = time.Time{}
242-
}
243-
244225
hdr.m.Lock()
245226
c.snapshots[miss] = &hdr
246227
result.snapshots[miss] = &hdr
@@ -249,7 +230,13 @@ func (c *Cache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder {
249230
defer hdr.m.Unlock()
250231
c.sem <- struct{}{}
251232
defer func() { <-c.sem }()
252-
hdr.snapshot, hdr.err = source.Snapshot(ctx)
233+
if snapshot, err := source.Snapshot(ctx); err != nil {
234+
hdr.err = err
235+
} else if snapshot != nil {
236+
hdr.snapshot = snapshot
237+
} else {
238+
hdr.err = fmt.Errorf("source %q produced no snapshot and no error", hdr.key)
239+
}
253240
}(ctx, &hdr, sources[miss])
254241
}
255242

@@ -286,6 +273,15 @@ func (c *NamespacedOperatorCache) Find(p ...Predicate) []*Entry {
286273

287274
type Snapshot struct {
288275
Entries []*Entry
276+
277+
// Unless closed, the Snapshot is valid.
278+
Valid <-chan struct{}
279+
}
280+
281+
func ValidOnce() <-chan struct{} {
282+
c := make(chan struct{})
283+
close(c)
284+
return c
289285
}
290286

291287
var _ Source = &Snapshot{}
@@ -298,7 +294,6 @@ type snapshotHeader struct {
298294
snapshot *Snapshot
299295

300296
key SourceKey
301-
expiry time.Time
302297
m sync.RWMutex
303298
pop context.CancelFunc
304299
err error
@@ -309,10 +304,18 @@ func (hdr *snapshotHeader) Cancel() {
309304
hdr.pop()
310305
}
311306

312-
func (hdr *snapshotHeader) Valid(at time.Time) bool {
307+
func (hdr *snapshotHeader) Valid() bool {
313308
hdr.m.RLock()
314309
defer hdr.m.RUnlock()
315-
return hdr.snapshot != nil && hdr.err == nil && at.Before(hdr.expiry)
310+
if hdr.snapshot == nil || hdr.err != nil {
311+
return false
312+
}
313+
select {
314+
case <-hdr.snapshot.Valid:
315+
return false
316+
default:
317+
}
318+
return true
316319
}
317320

318321
type sortableSnapshots struct {

pkg/controller/registry/resolver/cache/cache_test.go

+12-24
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"math/rand"
88
"strconv"
99
"testing"
10-
"time"
1110

1211
"github.com/stretchr/testify/assert"
1312
"github.com/stretchr/testify/require"
@@ -68,12 +67,12 @@ func TestOperatorCacheExpiration(t *testing.T) {
6867
key := SourceKey{Namespace: "dummynamespace", Name: "dummyname"}
6968
ssp := make(StaticSourceProvider)
7069
c := New(ssp)
71-
c.ttl = 0 // instantly stale
7270

7371
ssp[key] = &Snapshot{
7472
Entries: []*Entry{
7573
{Name: "v1"},
7674
},
75+
Valid: ValidOnce(),
7776
}
7877
require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 1)
7978

@@ -108,62 +107,51 @@ func TestOperatorCacheReuse(t *testing.T) {
108107
func TestCatalogSnapshotValid(t *testing.T) {
109108
type tc struct {
110109
Name string
111-
Expiry time.Time
112110
Snapshot *Snapshot
113111
Error error
114-
At time.Time
115112
Expected bool
116113
}
117114

118115
for _, tt := range []tc{
119116
{
120-
Name: "after expiry",
121-
Expiry: time.Unix(0, 1),
122-
Snapshot: &Snapshot{},
117+
Name: "invalidated",
118+
Snapshot: &Snapshot{
119+
Valid: ValidOnce(),
120+
},
123121
Error: nil,
124-
At: time.Unix(0, 2),
125122
Expected: false,
126123
},
127124
{
128-
Name: "before expiry",
129-
Expiry: time.Unix(0, 2),
130-
Snapshot: &Snapshot{},
125+
Name: "valid",
126+
Snapshot: &Snapshot{}, // valid forever
131127
Error: nil,
132-
At: time.Unix(0, 1),
133128
Expected: true,
134129
},
135130
{
136-
Name: "nil snapshot",
137-
Expiry: time.Unix(0, 2),
131+
Name: "nil snapshot and non-nil error",
138132
Snapshot: nil,
139133
Error: errors.New(""),
140-
At: time.Unix(0, 1),
141134
Expected: false,
142135
},
143136
{
144-
Name: "non-nil error",
145-
Expiry: time.Unix(0, 2),
137+
Name: "non-nil snapshot and non-nil error",
146138
Snapshot: &Snapshot{},
147139
Error: errors.New(""),
148-
At: time.Unix(0, 1),
149140
Expected: false,
150141
},
151142
{
152-
Name: "at expiry",
153-
Expiry: time.Unix(0, 1),
154-
Snapshot: &Snapshot{},
143+
Name: "nil snapshot and nil error",
144+
Snapshot: nil,
155145
Error: nil,
156-
At: time.Unix(0, 1),
157146
Expected: false,
158147
},
159148
} {
160149
t.Run(tt.Name, func(t *testing.T) {
161150
s := snapshotHeader{
162-
expiry: tt.Expiry,
163151
snapshot: tt.Snapshot,
164152
err: tt.Error,
165153
}
166-
assert.Equal(t, tt.Expected, s.Valid(tt.At))
154+
assert.Equal(t, tt.Expected, s.Valid())
167155
})
168156
}
169157
}

pkg/controller/registry/resolver/instrumented_resolver.go

-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"time"
55

66
"github.com/operator-framework/api/pkg/operators/v1alpha1"
7-
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
87
)
98

109
type InstrumentedResolver struct {
@@ -33,7 +32,3 @@ func (ir *InstrumentedResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step
3332
}
3433
return steps, lookups, subs, err
3534
}
36-
37-
func (ir *InstrumentedResolver) Expire(key cache.SourceKey) {
38-
ir.resolver.Expire(key)
39-
}

pkg/controller/registry/resolver/instrumented_resolver_test.go

-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"time"
77

88
"github.com/operator-framework/api/pkg/operators/v1alpha1"
9-
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
109
"github.com/stretchr/testify/require"
1110
)
1211

@@ -22,16 +21,10 @@ func (r *fakeResolverWithError) ResolveSteps(namespace string) ([]*v1alpha1.Step
2221
return nil, nil, nil, errors.New("Fake error")
2322
}
2423

25-
func (r *fakeResolverWithError) Expire(key cache.SourceKey) {
26-
}
27-
2824
func (r *fakeResolverWithoutError) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) {
2925
return nil, nil, nil, nil
3026
}
3127

32-
func (r *fakeResolverWithoutError) Expire(key cache.SourceKey) {
33-
}
34-
3528
func newFakeResolverWithError() *fakeResolverWithError {
3629
return &fakeResolverWithError{}
3730
}

pkg/controller/registry/resolver/source_csvs.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ func (s *csvSource) Snapshot(ctx context.Context) (*cache.Snapshot, error) {
122122
s.logger.Printf("considered csvs without properties annotation during resolution: %v", names)
123123
}
124124

125-
return &cache.Snapshot{Entries: entries}, nil
125+
return &cache.Snapshot{
126+
Entries: entries,
127+
Valid: cache.ValidOnce(),
128+
}, nil
126129
}
127130

128131
func (s *csvSource) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription) ([]*api.Property, error) {

0 commit comments

Comments
 (0)