diff --git a/pkg/controller/actions/deploy/action_deploy.go b/pkg/controller/actions/deploy/action_deploy.go index 93f77772a5c..b392b99d086 100644 --- a/pkg/controller/actions/deploy/action_deploy.go +++ b/pkg/controller/actions/deploy/action_deploy.go @@ -100,9 +100,9 @@ func WithAnnotations(values map[string]string) ActionOpts { } } -func WithCache() ActionOpts { +func WithCache(opts ...CacheOpt) ActionOpts { return func(action *Action) { - action.cache = newCache() + action.cache = newCache(opts...) } } diff --git a/pkg/controller/actions/deploy/action_deploy_cache.go b/pkg/controller/actions/deploy/action_deploy_cache.go index a3abfbae75a..5768d0eab35 100644 --- a/pkg/controller/actions/deploy/action_deploy_cache.go +++ b/pkg/controller/actions/deploy/action_deploy_cache.go @@ -10,27 +10,44 @@ import ( // This code is heavily inspired by https://github.com/kubernetes-sigs/cluster-api/tree/main/internal/util/ssa const ( - // ttl is the duration for which we keep the keys in the cache. - ttl = 10 * time.Minute + DefaultCacheTTL = 10 * time.Minute ) type Cache struct { - s cache.Store + s cache.Store + ttl time.Duration } -func newCache() *Cache { - r := &Cache{ - s: cache.NewTTLStore(func(obj interface{}) (string, error) { +type CacheOpt func(*Cache) + +func WithTTL(ttl time.Duration) CacheOpt { + return func(c *Cache) { + c.ttl = ttl + } +} + +func newCache(opts ...CacheOpt) *Cache { + c := Cache{ + ttl: DefaultCacheTTL, + } + + for _, opt := range opts { + opt(&c) + } + + c.s = cache.NewTTLStore( + func(obj interface{}) (string, error) { s, ok := obj.(string) if !ok { return "", errors.New("failed to cast object to string") } return s, nil - }, ttl), - } + }, + c.ttl, + ) - return r + return &c } func (r *Cache) Add(key string) { diff --git a/pkg/controller/actions/deploy/action_deploy_cache_test.go b/pkg/controller/actions/deploy/action_deploy_cache_test.go index 6c53fdb5f9d..456014b7d8a 100644 --- a/pkg/controller/actions/deploy/action_deploy_cache_test.go +++ b/pkg/controller/actions/deploy/action_deploy_cache_test.go @@ -4,6 +4,7 @@ import ( "context" "path/filepath" "testing" + "time" "github.com/blang/semver/v4" "github.com/operator-framework/api/pkg/lib/version" @@ -102,6 +103,22 @@ func TestDeployWithCacheAction(t *testing.T) { }, false) }) + + t.Run("CacheTTL(", func(t *testing.T) { + testCacheTTL( + t, + cli, + &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: gvk.ConfigMap.GroupVersion().String(), + Kind: gvk.ConfigMap.Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: xid.New().String(), + Namespace: xid.New().String(), + }, + }) + }) } func testResourceNotReDeployed(t *testing.T, cli *client.Client, obj ctrlCli.Object, create bool) { @@ -182,3 +199,66 @@ func testResourceNotReDeployed(t *testing.T, cli *client.Client, obj ctrlCli.Obj // check that the resource version has not changed g.Expect(out1.GetResourceVersion()).Should(Equal(out2.GetResourceVersion())) } + +func testCacheTTL(t *testing.T, cli *client.Client, obj ctrlCli.Object) { + t.Helper() + + g := NewWithT(t) + ctx := context.Background() + + in, err := resources.ToUnstructured(obj) + g.Expect(err).ShouldNot(HaveOccurred()) + + err = cli.Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: in.GetNamespace(), + }, + }) + + g.Expect(err).ShouldNot(HaveOccurred()) + + rr := types.ReconciliationRequest{ + Client: cli, + DSCI: &dsciv1.DSCInitialization{Spec: dsciv1.DSCInitializationSpec{ + ApplicationsNamespace: in.GetNamespace()}, + }, + DSC: &dscv1.DataScienceCluster{}, + Instance: &componentsv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + }, + Release: cluster.Release{ + Name: cluster.OpenDataHub, + Version: version.OperatorVersion{Version: semver.Version{ + Major: 1, Minor: 2, Patch: 3, + }}}, + Resources: []unstructured.Unstructured{ + *in.DeepCopy(), + }, + } + + ttl := 1 * time.Second + + action := deploy.NewAction( + deploy.WithCache(deploy.WithTTL(ttl)), + deploy.WithMode(deploy.ModeSSA), + deploy.WithFieldOwner(xid.New().String()), + ) + + deploy.DeployedResourcesTotal.Reset() + + err = action(ctx, &rr) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(testutil.ToFloat64(deploy.DeployedResourcesTotal)).Should(BeNumerically("==", 1)) + + g.Eventually(func() (float64, error) { + if err := action(ctx, &rr); err != nil { + return 0, err + } + + return testutil.ToFloat64(deploy.DeployedResourcesTotal), nil + }).WithTimeout(5 * ttl).WithPolling(2 * ttl).Should( + BeNumerically("==", 2), + ) +}