Skip to content

Commit a0f12e6

Browse files
authored
Populate/update cache on ClusterCatalog reconcile (#1284)
Currently we fetch catalog data and populate cache on demand during ClusterExtension reconciliation. This works but the first reconciliation after ClusterCatalog creation or update is slow due to the need to fetch data. With this change we proactively populate cache on ClusterCatalog creation and check if cache needs to be updated on ClusterCatalog update. Signed-off-by: Mikalai Radchuk <[email protected]>
1 parent cd0b096 commit a0f12e6

File tree

5 files changed

+206
-75
lines changed

5 files changed

+206
-75
lines changed

cmd/manager/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,9 @@ func main() {
314314
}
315315

316316
if err = (&controllers.ClusterCatalogReconciler{
317-
Client: cl,
318-
Cache: catalogClientBackend,
317+
Client: cl,
318+
CatalogCache: catalogClientBackend,
319+
CatalogCachePopulator: catalogClient,
319320
}).SetupWithManager(mgr); err != nil {
320321
setupLog.Error(err, "unable to create controller", "controller", "ClusterCatalog")
321322
os.Exit(1)

internal/catalogmetadata/client/client.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,10 @@ func (c *Client) GetPackage(ctx context.Context, catalog *catalogd.ClusterCatalo
6565

6666
catalogFsys, err := c.cache.Get(catalog.Name, catalog.Status.ResolvedSource.Image.Ref)
6767
if err != nil {
68-
return nil, fmt.Errorf("error retrieving catalog cache: %v", err)
68+
return nil, fmt.Errorf("error retrieving cache for catalog %q: %v", catalog.Name, err)
6969
}
7070
if catalogFsys == nil {
71-
// TODO: https://github.com/operator-framework/operator-controller/pull/1284
72-
// For now we are still populating cache (if absent) on-demand,
73-
// but we might end up just returning a "cache not found" error here
74-
// once we implement cache population in the controller.
75-
catalogFsys, err = c.PopulateCache(ctx, catalog)
76-
if err != nil {
77-
return nil, fmt.Errorf("error fetching catalog contents: %v", err)
78-
}
71+
return nil, fmt.Errorf("cache for catalog %q not found", catalog.Name)
7972
}
8073

8174
pkgFsys, err := fs.Sub(catalogFsys, pkgName)

internal/catalogmetadata/client/client_test.go

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func TestClientGetPackage(t *testing.T) {
6262
catalog: defaultCatalog,
6363
cache: &fakeCache{getErr: errors.New("fetch error")},
6464
assert: func(t *testing.T, dc *declcfg.DeclarativeConfig, err error) {
65-
assert.ErrorContains(t, err, `error retrieving catalog cache`)
65+
assert.ErrorContains(t, err, `error retrieving cache for catalog "catalog-1"`)
6666
},
6767
},
6868
{
@@ -114,18 +114,7 @@ func TestClientGetPackage(t *testing.T) {
114114
return testFS, nil
115115
}},
116116
assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) {
117-
require.NoError(t, err)
118-
assert.Equal(t, &declcfg.DeclarativeConfig{Packages: []declcfg.Package{{Schema: declcfg.SchemaPackage, Name: "pkg-present"}}}, fbc)
119-
},
120-
},
121-
{
122-
name: "cache unpopulated and fails to populate",
123-
catalog: defaultCatalog,
124-
pkgName: "pkg-present",
125-
cache: &fakeCache{putErr: errors.New("fake cache put error")},
126-
assert: func(t *testing.T, fbc *declcfg.DeclarativeConfig, err error) {
127-
assert.Nil(t, fbc)
128-
assert.ErrorContains(t, err, "error fetching catalog contents")
117+
assert.ErrorContains(t, err, `cache for catalog "catalog-1" not found`)
129118
},
130119
},
131120
} {
@@ -278,7 +267,6 @@ type fakeCache struct {
278267
getErr error
279268

280269
putFunc func(source string, errToCache error) (fs.FS, error)
281-
putErr error
282270
}
283271

284272
func (c *fakeCache) Get(catalogName, resolvedRef string) (fs.FS, error) {
@@ -293,9 +281,6 @@ func (c *fakeCache) Put(catalogName, resolvedRef string, source io.Reader, errTo
293281
}
294282
return c.putFunc(buf.String(), errToCache)
295283
}
296-
if c.putErr != nil {
297-
return nil, c.putErr
298-
}
299284

300285
return nil, errors.New("unexpected error")
301286
}

internal/controllers/clustercatalog_controller.go

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,30 @@ package controllers
1818

1919
import (
2020
"context"
21+
"fmt"
22+
"io/fs"
2123

2224
apierrors "k8s.io/apimachinery/pkg/api/errors"
2325
ctrl "sigs.k8s.io/controller-runtime"
24-
"sigs.k8s.io/controller-runtime/pkg/builder"
2526
"sigs.k8s.io/controller-runtime/pkg/client"
26-
"sigs.k8s.io/controller-runtime/pkg/event"
27-
"sigs.k8s.io/controller-runtime/pkg/predicate"
2827

2928
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
3029
)
3130

32-
type CatalogCacheRemover interface {
31+
type CatalogCache interface {
32+
Get(catalogName, resolvedRef string) (fs.FS, error)
3333
Remove(catalogName string) error
3434
}
3535

36+
type CatalogCachePopulator interface {
37+
PopulateCache(ctx context.Context, catalog *catalogd.ClusterCatalog) (fs.FS, error)
38+
}
39+
3640
// ClusterCatalogReconciler reconciles a ClusterCatalog object
3741
type ClusterCatalogReconciler struct {
3842
client.Client
39-
Cache CatalogCacheRemover
43+
CatalogCache CatalogCache
44+
CatalogCachePopulator CatalogCachePopulator
4045
}
4146

4247
//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clustercatalogs,verbs=get;list;watch
@@ -45,31 +50,44 @@ func (r *ClusterCatalogReconciler) Reconcile(ctx context.Context, req ctrl.Reque
4550
existingCatalog := &catalogd.ClusterCatalog{}
4651
err := r.Client.Get(ctx, req.NamespacedName, existingCatalog)
4752
if apierrors.IsNotFound(err) {
48-
return ctrl.Result{}, r.Cache.Remove(req.Name)
53+
if err := r.CatalogCache.Remove(req.Name); err != nil {
54+
return ctrl.Result{}, fmt.Errorf("error removing cache for catalog %q: %v", req.Name, err)
55+
}
56+
return ctrl.Result{}, nil
4957
}
5058
if err != nil {
5159
return ctrl.Result{}, err
5260
}
61+
62+
if existingCatalog.Status.ResolvedSource == nil ||
63+
existingCatalog.Status.ResolvedSource.Image == nil ||
64+
existingCatalog.Status.ResolvedSource.Image.Ref == "" {
65+
// Reference is not known yet - skip cache population with no error.
66+
// Once the reference is resolved another reconcile cycle
67+
// will be triggered and we will progress further.
68+
return ctrl.Result{}, nil
69+
}
70+
71+
catalogFsys, err := r.CatalogCache.Get(existingCatalog.Name, existingCatalog.Status.ResolvedSource.Image.Ref)
72+
if err != nil {
73+
return ctrl.Result{}, fmt.Errorf("error retrieving cache for catalog %q: %v", existingCatalog.Name, err)
74+
}
75+
if catalogFsys != nil {
76+
// Cache already exists so we do not need to populate it
77+
return ctrl.Result{}, nil
78+
}
79+
80+
if _, err = r.CatalogCachePopulator.PopulateCache(ctx, existingCatalog); err != nil {
81+
return ctrl.Result{}, fmt.Errorf("error populating cache for catalog %q: %v", existingCatalog.Name, err)
82+
}
83+
5384
return ctrl.Result{}, nil
5485
}
5586

5687
// SetupWithManager sets up the controller with the Manager.
5788
func (r *ClusterCatalogReconciler) SetupWithManager(mgr ctrl.Manager) error {
5889
_, err := ctrl.NewControllerManagedBy(mgr).
59-
For(&catalogd.ClusterCatalog{}, builder.WithPredicates(predicate.Funcs{
60-
CreateFunc: func(e event.CreateEvent) bool {
61-
return false
62-
},
63-
UpdateFunc: func(e event.UpdateEvent) bool {
64-
return false
65-
},
66-
DeleteFunc: func(e event.DeleteEvent) bool {
67-
return true
68-
},
69-
GenericFunc: func(e event.GenericEvent) bool {
70-
return false
71-
},
72-
})).
90+
For(&catalogd.ClusterCatalog{}).
7391
Build(r)
7492

7593
return err

0 commit comments

Comments
 (0)