From 717c747816c56a5cad320bd6bb1299c9393a9ff7 Mon Sep 17 00:00:00 2001 From: DQ Date: Wed, 16 Apr 2025 17:41:56 +1000 Subject: [PATCH 1/3] feat: add cached requirement feature to integration tests --- test/integration/integration_test.go | 190 ++++++++++++++++++++++++++- test/integration/main_test.go | 2 +- 2 files changed, 188 insertions(+), 4 deletions(-) diff --git a/test/integration/integration_test.go b/test/integration/integration_test.go index 0c895db..dc3e0b1 100644 --- a/test/integration/integration_test.go +++ b/test/integration/integration_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/stretchr/testify/assert" + apierr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/e2e-framework/pkg/envconf" "sigs.k8s.io/e2e-framework/pkg/features" @@ -18,10 +19,11 @@ import ( type requirementKey struct{} const ( - testRequirementName = "test-requirement" + testRequirementName = "test-requirement" + cachedRequirementName = "cached-requirement" ) -var CacheFeature = features.New("Simple Requirements"). +var SimpleRequirementFeature = features.New("Simple Requirements"). Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context { // start a deployment requiremnt := utils.NewRequirement(testRequirementName, utils.TestNamespcae) @@ -33,7 +35,7 @@ var CacheFeature = features.New("Simple Requirements"). return ctx }). - Assess("create requirement", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + Assess("requirement created successfully", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { var requirement v1.Requirement if err := cfg.Client().Resources().Get(ctx, testRequirementName, utils.TestNamespcae, &requirement); err != nil { t.Fatal(err) @@ -60,3 +62,185 @@ var CacheFeature = features.New("Simple Requirements"). } return ctx }).Feature() + +func newRequirementWithCache(name string) *v1.Requirement { + requirement := utils.NewRequirement(name, utils.TestNamespcae) + requirement.Spec.EnableCache = true + return requirement +} + +var CachedRequirementFeature = features.New("Cached Requirements"). + Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context { + // Create a requirement with cache enabled + if err := c.Client().Resources().Create(ctx, newRequirementWithCache(cachedRequirementName)); err != nil { + t.Fatal(err) + } + time.Sleep(2 * time.Second) + return ctx + }). + Assess("cache requirement created and synced", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + var requirement v1.Requirement + if err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespcae, &requirement); err != nil { + t.Fatal(err) + } + assert.Equal(t, cachedRequirementName, requirement.Name) + + // Wait for requirement to be ready + if err := wait.PollUntilContextTimeout(ctx, 10*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { + requirement := &v1.Requirement{} + if err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespcae, requirement); err != nil { + return false, err + } + if requirement.Status.Phase != rqutils.PhaseReady { + return false, nil + } + return true, nil + }); err != nil { + t.Fatal(err, "requirement not ready") + } + cacheKey := requirement.Status.CacheKey + // Get the associated Cache resource + cache := &v1.Cache{} + cacheName := "cache-" + cacheKey + if err := cfg.Client().Resources().Get(ctx, cacheName, utils.TestNamespcae, cache); err != nil { + t.Fatal(err, "cache not found") + } + // Verify the cache key matches + assert.Equal(t, cacheKey, cache.Status.CacheKey) + + // Get all Operations + var operations v1.OperationList + if err := cfg.Client().Resources().List(ctx, &operations); err != nil { + t.Fatal(err, "failed to list operations") + } + + // Verify one operation is owned by our requirement by checking owner references + var ( + ownedByRequirement []v1.Operation + ownedByCache []v1.Operation + ) + for _, op := range operations.Items { + for _, ownerRef := range op.OwnerReferences { + if ownerRef.APIVersion == v1.GroupVersion.String() && + ownerRef.Kind == "Requirement" && + ownerRef.Name == requirement.Name && + ownerRef.UID == requirement.UID { + ownedByRequirement = append(ownedByRequirement, op) + } + if ownerRef.APIVersion == v1.GroupVersion.String() && + ownerRef.Kind == "Cache" && + ownerRef.Name == cache.Name && + ownerRef.UID == cache.UID { + ownedByCache = append(ownedByCache, op) + } + } + } + // Verify one operation is owned by our requirement + assert.Equal(t, 1, len(ownedByRequirement), "expected one operation owned by requirement") + // Verify number of cache operations matches keepAlive count + assert.Equal(t, int(cache.Status.KeepAliveCount), len(ownedByCache)) + + // delete the requirement + if err := cfg.Client().Resources().Delete(ctx, &requirement); err != nil { + t.Fatal(err) + } + // wait for the requirement to be deleted + if err := wait.PollUntilContextTimeout(ctx, 10*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { + requirement := &v1.Requirement{} + err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespcae, requirement) + // err is not found, so requirement is deleted + if err != nil { + if apierr.IsNotFound(err) { + return true, nil + } + return false, err + } + return false, nil + }); err != nil { + t.Fatal(err, "requirement not deleted") + } + + newCachedRequirementName := cachedRequirementName + "-new" + // create a new requirement with the same name + if err := cfg.Client().Resources().Create(ctx, newRequirementWithCache(newCachedRequirementName)); err != nil { + t.Fatal(err) + } + + newRequirement := &v1.Requirement{} + // wait for the new requirement to be ready + if err := wait.PollUntilContextTimeout(ctx, 10*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { + if err := cfg.Client().Resources().Get(ctx, newCachedRequirementName, utils.TestNamespcae, newRequirement); err != nil { + return false, err + } + if newRequirement.Status.Phase != rqutils.PhaseReady { + return false, nil + } + return true, nil + }); err != nil { + t.Fatal(err, "new requirement not ready") + } + + // cache should be hit + cacheHit := false + for _, condition := range newRequirement.Status.Conditions { + if condition.Type == rqutils.ConditionOperationReady { + cacheHit = true + break + } + } + assert.True(t, cacheHit, "expected cache hit condition") + + // list operations and verify the new requirement has operation with the same name from original cache + var newOperations v1.OperationList + if err := cfg.Client().Resources().List(ctx, &newOperations); err != nil { + t.Fatal(err, "failed to list operations") + } + var ( + ownedByNewRequirement []v1.Operation + ownedByCurrentCache []v1.Operation + ) + + for _, op := range newOperations.Items { + for _, ownerRef := range op.OwnerReferences { + if ownerRef.APIVersion == v1.GroupVersion.String() && + ownerRef.Kind == "Requirement" && + ownerRef.Name == newCachedRequirementName && + ownerRef.UID == newRequirement.UID { + ownedByNewRequirement = append(ownedByNewRequirement, op) + } + if ownerRef.APIVersion == v1.GroupVersion.String() && + ownerRef.Kind == "Cache" && + ownerRef.Name == cacheName && + ownerRef.UID == cache.UID { + ownedByCurrentCache = append(ownedByCurrentCache, op) + } + } + } + // Verify one operation is owned by our requirement + assert.Equal(t, 1, len(ownedByNewRequirement), "expected one operation owned by requirement") + // Verify number of cache operations matches keepAlive count + assert.Equal(t, int(cache.Status.KeepAliveCount), len(ownedByCurrentCache)) + + // Verify the operation come from the original cache + found := false + for _, op := range ownedByCache { + if op.Name == ownedByNewRequirement[0].Name { + found = true + break + } + } + assert.True(t, found, "expected operation to be from original cache") + + // the operation should not be included in the new cache + for _, op := range ownedByCurrentCache { + assert.NotEqual(t, op.Name, ownedByNewRequirement[0].Name, "operation should not be included in the new cache") + } + return context.WithValue(ctx, requirementKey{}, newRequirement) + }). + Teardown(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + requirement := ctx.Value(requirementKey{}).(*v1.Requirement) + if err := cfg.Client().Resources().Delete(ctx, requirement); err != nil { + t.Fatal(err) + } + return ctx + }).Feature() diff --git a/test/integration/main_test.go b/test/integration/main_test.go index 5a1d799..94a8f72 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -86,5 +86,5 @@ func UninstallCRD(ctx context.Context, cfg *envconf.Config) (context.Context, er func TestRealCluster(t *testing.T) { // Create a new test environment configuration // Run the integration tests against the Kind cluster - testenv.Test(t, CacheFeature) + testenv.Test(t, SimpleRequirementFeature, CachedRequirementFeature) } From 362e101fa9235f738627d9feaf14e284e9cfd220 Mon Sep 17 00:00:00 2001 From: DQ Date: Wed, 16 Apr 2025 17:45:15 +1000 Subject: [PATCH 2/3] fix: correct typo in TestNamespace constant --- test/utils/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/resources.go b/test/utils/resources.go index e6abe1c..82217c8 100644 --- a/test/utils/resources.go +++ b/test/utils/resources.go @@ -8,7 +8,7 @@ import ( ) const ( - TestNamespcae = "operation-cache-controller-system" + TestNamespace = "operation-cache-controller-system" ) func NewTestJobSpec(name string) batchv1.JobSpec { From 214eb77da7cba4fe457ee4180da3bb5270efb41c Mon Sep 17 00:00:00 2001 From: DQ Date: Wed, 16 Apr 2025 17:48:39 +1000 Subject: [PATCH 3/3] fix: correct namespace references in integration tests --- test/integration/integration_test.go | 22 +++++++++++----------- test/integration/main_test.go | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/integration/integration_test.go b/test/integration/integration_test.go index dc3e0b1..2cdbac7 100644 --- a/test/integration/integration_test.go +++ b/test/integration/integration_test.go @@ -26,9 +26,9 @@ const ( var SimpleRequirementFeature = features.New("Simple Requirements"). Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context { // start a deployment - requiremnt := utils.NewRequirement(testRequirementName, utils.TestNamespcae) - requiremnt.Namespace = utils.TestNamespcae - if err := c.Client().Resources().Create(ctx, requiremnt); err != nil { + testRequirement := utils.NewRequirement(testRequirementName, utils.TestNamespace) + testRequirement.Namespace = utils.TestNamespace + if err := c.Client().Resources().Create(ctx, testRequirement); err != nil { t.Fatal(err) } time.Sleep(2 * time.Second) @@ -37,13 +37,13 @@ var SimpleRequirementFeature = features.New("Simple Requirements"). }). Assess("requirement created successfully", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { var requirement v1.Requirement - if err := cfg.Client().Resources().Get(ctx, testRequirementName, utils.TestNamespcae, &requirement); err != nil { + if err := cfg.Client().Resources().Get(ctx, testRequirementName, utils.TestNamespace, &requirement); err != nil { t.Fatal(err) } assert.Equal(t, testRequirementName, requirement.Name) if err := wait.PollUntilContextTimeout(ctx, 10*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { requirement := &v1.Requirement{} - if err := cfg.Client().Resources().Get(ctx, testRequirementName, utils.TestNamespcae, requirement); err != nil { + if err := cfg.Client().Resources().Get(ctx, testRequirementName, utils.TestNamespace, requirement); err != nil { return false, err } if requirement.Status.Phase != rqutils.PhaseReady { @@ -64,7 +64,7 @@ var SimpleRequirementFeature = features.New("Simple Requirements"). }).Feature() func newRequirementWithCache(name string) *v1.Requirement { - requirement := utils.NewRequirement(name, utils.TestNamespcae) + requirement := utils.NewRequirement(name, utils.TestNamespace) requirement.Spec.EnableCache = true return requirement } @@ -80,7 +80,7 @@ var CachedRequirementFeature = features.New("Cached Requirements"). }). Assess("cache requirement created and synced", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { var requirement v1.Requirement - if err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespcae, &requirement); err != nil { + if err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespace, &requirement); err != nil { t.Fatal(err) } assert.Equal(t, cachedRequirementName, requirement.Name) @@ -88,7 +88,7 @@ var CachedRequirementFeature = features.New("Cached Requirements"). // Wait for requirement to be ready if err := wait.PollUntilContextTimeout(ctx, 10*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { requirement := &v1.Requirement{} - if err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespcae, requirement); err != nil { + if err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespace, requirement); err != nil { return false, err } if requirement.Status.Phase != rqutils.PhaseReady { @@ -102,7 +102,7 @@ var CachedRequirementFeature = features.New("Cached Requirements"). // Get the associated Cache resource cache := &v1.Cache{} cacheName := "cache-" + cacheKey - if err := cfg.Client().Resources().Get(ctx, cacheName, utils.TestNamespcae, cache); err != nil { + if err := cfg.Client().Resources().Get(ctx, cacheName, utils.TestNamespace, cache); err != nil { t.Fatal(err, "cache not found") } // Verify the cache key matches @@ -147,7 +147,7 @@ var CachedRequirementFeature = features.New("Cached Requirements"). // wait for the requirement to be deleted if err := wait.PollUntilContextTimeout(ctx, 10*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { requirement := &v1.Requirement{} - err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespcae, requirement) + err := cfg.Client().Resources().Get(ctx, cachedRequirementName, utils.TestNamespace, requirement) // err is not found, so requirement is deleted if err != nil { if apierr.IsNotFound(err) { @@ -169,7 +169,7 @@ var CachedRequirementFeature = features.New("Cached Requirements"). newRequirement := &v1.Requirement{} // wait for the new requirement to be ready if err := wait.PollUntilContextTimeout(ctx, 10*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { - if err := cfg.Client().Resources().Get(ctx, newCachedRequirementName, utils.TestNamespcae, newRequirement); err != nil { + if err := cfg.Client().Resources().Get(ctx, newCachedRequirementName, utils.TestNamespace, newRequirement); err != nil { return false, err } if newRequirement.Status.Phase != rqutils.PhaseReady { diff --git a/test/integration/main_test.go b/test/integration/main_test.go index 94a8f72..ca20f70 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -41,14 +41,14 @@ func TestMain(m *testing.M) { BuildImage, envfuncs.CreateCluster(kind.NewProvider(), kindClusterName), envfuncs.LoadDockerImageToCluster(kindClusterName, projectImage), - envfuncs.CreateNamespace(utils.TestNamespcae), + envfuncs.CreateNamespace(utils.TestNamespace), InstallCRD, DeployControllerManager, ) // Teardown the test environment testenv = testenv.Finish( - envfuncs.DeleteNamespace(utils.TestNamespcae), + envfuncs.DeleteNamespace(utils.TestNamespace), envfuncs.DestroyCluster(kindClusterName), )