diff --git a/Makefile b/Makefile index 9b3dceb00..8c7a8e190 100644 --- a/Makefile +++ b/Makefile @@ -135,13 +135,14 @@ test: manifests generate fmt vet local-unit-test integration-test## Run tests. ## ## workaround to bypass the pkg/controllers/workv1alpha1 tests failure +## rollout controller tests need a bit longer to complete, so we increase the timeout ## .PHONY: local-unit-test local-unit-test: $(ENVTEST) ## Run tests. export CGO_ENABLED=1 && \ export KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" && \ go test ./pkg/controllers/workv1alpha1 -race -coverprofile=ut-coverage.xml -covermode=atomic -v && \ - go test `go list ./pkg/... ./cmd/... | grep -v pkg/controllers/workv1alpha1` -race -coverpkg=./... -coverprofile=ut-coverage.xml -covermode=atomic -v + go test `go list ./pkg/... ./cmd/... | grep -v pkg/controllers/workv1alpha1` -race -coverpkg=./... -coverprofile=ut-coverage.xml -covermode=atomic -v -timeout=20m .PHONY: integration-test integration-test: $(ENVTEST) ## Run tests. diff --git a/pkg/controllers/rollout/controller_integration_test.go b/pkg/controllers/rollout/controller_integration_test.go index f92f276c8..9b73be301 100644 --- a/pkg/controllers/rollout/controller_integration_test.go +++ b/pkg/controllers/rollout/controller_integration_test.go @@ -25,6 +25,8 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -32,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr" + clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" "github.com/kubefleet-dev/kubefleet/pkg/utils" "github.com/kubefleet-dev/kubefleet/pkg/utils/condition" @@ -44,7 +47,7 @@ const ( consistentTimeout = time.Second * 60 consistentInterval = time.Second * 5 customBindingFinalizer = "custom-binding-finalizer" - testNamespace = "test-namespace" + testNamespace = "app" // to align with the test resources in rollout/manifests ) var ( @@ -314,6 +317,95 @@ var _ = Describe("Test the rollout Controller", func() { }, timeout, interval).Should(Succeed(), "Failed to verify that all the bindings have their status refreshed") }) + It("should trigger binding rollout for clusterResourceOverrideSnapshot but not resourceOverrideSnapshot with Namespaced scope", func() { + // Create a CRP. + targetClusterCount := int32(2) + rolloutCRP = clusterResourcePlacementForTest( + testCRPName, + createPlacementPolicyForTest(placementv1beta1.PickNPlacementType, targetClusterCount), + createPlacementRolloutStrategyForTest(placementv1beta1.RollingUpdateRolloutStrategyType, generateDefaultRollingUpdateConfig(), nil)) + Expect(k8sClient.Create(ctx, rolloutCRP)).Should(Succeed(), "Failed to create CRP") + + // Create a master cluster resource snapshot. + resourceSnapshot := generateClusterResourceSnapshot(rolloutCRP.Name, 0, true) + Expect(k8sClient.Create(ctx, resourceSnapshot)).Should(Succeed(), "Failed to create cluster resource snapshot") + + // Create bindings. + clusters := make([]string, targetClusterCount) + for i := 0; i < int(targetClusterCount); i++ { + clusters[i] = "cluster-" + utils.RandStr() + binding := generateClusterResourceBinding(placementv1beta1.BindingStateScheduled, resourceSnapshot.Name, clusters[i]) + Expect(k8sClient.Create(ctx, binding)).Should(Succeed(), "Failed to create cluster resource binding") + bindings = append(bindings, binding) + + memberCluster := generateMemberCluster(i, clusters[i]) + Expect(k8sClient.Create(ctx, memberCluster)).Should(Succeed(), "Failed to create member cluster") + } + + // Verify that all the bindings are rolled out initially. + verifyBindingsRolledOut(controller.ConvertCRBArrayToBindingObjs(bindings), resourceSnapshot, timeout) + + // Mark the bindings to be available. + for _, binding := range bindings { + markBindingAvailable(binding, true) + } + + // Create a resourceOverrideSnapshot with the same placement name but Namespaced scope and verify bindings are not updated. + testROName1 := "ro" + utils.RandStr() + resourceOverrideSnapshot1 := generateResourceOverrideSnapshot(testROName1, testCRPName, placementv1beta1.NamespaceScoped) + By(fmt.Sprintf("Creating resourceOverrideSnapshot %s to refer a resourcePlacement", resourceOverrideSnapshot1.Name)) + Expect(k8sClient.Create(ctx, resourceOverrideSnapshot1)).Should(Succeed(), "Failed to create resource override snapshot") + + // Verify bindings are NOT updated (rollout not triggered) by resourceOverrideSnapshot. + verifyBindingsNotUpdatedWithOverridesConsistently(controller.ConvertCRBArrayToBindingObjs(bindings), nil, nil) + + By(fmt.Sprintf("Updating resourceOverrideSnapshot %s to refer the clusterResourcePlacement instead", resourceOverrideSnapshot1.Name)) + resourceOverrideSnapshot1.Spec.OverrideSpec.Placement.Scope = placementv1beta1.ClusterScoped + Expect(k8sClient.Update(ctx, resourceOverrideSnapshot1)).Should(Succeed(), "Failed to update resource override snapshot") + + // Verify bindings are NOT updated (rollout not triggered) by resourceOverrideSnapshot. + // This is because rollout controller is not triggered by overrideSnapshot update events. + verifyBindingsNotUpdatedWithOverridesConsistently(controller.ConvertCRBArrayToBindingObjs(bindings), nil, nil) + + // Create a clusterResourceOverrideSnapshot and verify it triggers rollout. + testCROName := "cro" + utils.RandStr() + clusterResourceOverrideSnapshot := generateClusterResourceOverrideSnapshot(testCROName, testCRPName) + By(fmt.Sprintf("Creating clusterResourceOverrideSnapshot %s to refer the clusterResourcePlacement", clusterResourceOverrideSnapshot.Name)) + Expect(k8sClient.Create(ctx, clusterResourceOverrideSnapshot)).Should(Succeed(), "Failed to create cluster resource override snapshot") + + // Verify bindings are updated, note that both clusterResourceOverrideSnapshot and resourceOverrideSnapshot are set in the bindings. + waitUntilRolloutCompleted(controller.ConvertCRBArrayToBindingObjs(bindings), []string{clusterResourceOverrideSnapshot.Name}, + []placementv1beta1.NamespacedName{{Name: resourceOverrideSnapshot1.Name, Namespace: resourceOverrideSnapshot1.Namespace}}) + + // Create another resourceOverrideSnapshot referencing the same CRP and verify bindings are updated again. + testROName2 := "ro" + utils.RandStr() + resourceOverrideSnapshot2 := generateResourceOverrideSnapshot(testROName2, testCRPName, placementv1beta1.ClusterScoped) + By(fmt.Sprintf("Creating resourceOverrideSnapshot %s to refer a clusterResourcePlacement", resourceOverrideSnapshot2.Name)) + Expect(k8sClient.Create(ctx, resourceOverrideSnapshot2)).Should(Succeed(), "Failed to create resource override snapshot") + + // Verify bindings are updated, note that both clusterResourceOverrideSnapshot and resourceOverrideSnapshot are set in the bindings. + waitUntilRolloutCompleted(controller.ConvertCRBArrayToBindingObjs(bindings), []string{clusterResourceOverrideSnapshot.Name}, + []placementv1beta1.NamespacedName{ + {Name: resourceOverrideSnapshot1.Name, Namespace: resourceOverrideSnapshot1.Namespace}, + {Name: resourceOverrideSnapshot2.Name, Namespace: resourceOverrideSnapshot2.Namespace}, + }, + ) + + // Clean up the override snapshots. + Expect(k8sClient.Delete(ctx, resourceOverrideSnapshot1)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, clusterResourceOverrideSnapshot)).Should(Succeed()) + + // Clean up the member clusters. + for _, cluster := range clusters { + memberCluster := &clusterv1beta1.MemberCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: cluster, + }, + } + Expect(k8sClient.Delete(ctx, memberCluster)).Should(SatisfyAny(Succeed(), utils.NotFoundMatcher{})) + } + }) + It("Should rollout all the selected bindings when the rollout strategy is not set", func() { // create CRP var targetCluster int32 = 11 @@ -619,7 +711,7 @@ var _ = Describe("Test the rollout Controller", func() { By("Verified that the rollout is finally unblocked") }) - It("Should rollout both the old applied and failed to apply bond the new resources", func() { + It("Should rollout both the old applied and failed to apply bound the new resources", func() { // create CRP var targetCluster int32 = 5 rolloutCRP = clusterResourcePlacementForTest(testCRPName, @@ -1481,7 +1573,7 @@ var _ = Describe("Test the rollout Controller for ResourcePlacement", func() { By("Verified that the rollout is finally unblocked") }) - It("Should rollout both the old applied and failed to apply bond the new resources", func() { + It("Should rollout both the old applied and failed to apply bound the new resources", func() { // create RP var targetCluster int32 = 5 rolloutRP = resourcePlacementForTest(testNamespace, testRPName, @@ -1538,6 +1630,79 @@ var _ = Describe("Test the rollout Controller for ResourcePlacement", func() { return allMatch }, 5*defaultUnavailablePeriod*time.Second, interval).Should(BeTrue(), "rollout controller should roll all the bindings to use the latest resource snapshot") }) + + It("should trigger binding rollout for resourceOverrideSnapshot but not clusterResourceOverrideSnapshot", func() { + // Create a RP. + targetClusterCount := int32(2) + rolloutRP = resourcePlacementForTest( + testNamespace, testRPName, + createPlacementPolicyForTest(placementv1beta1.PickNPlacementType, targetClusterCount), + createPlacementRolloutStrategyForTest(placementv1beta1.RollingUpdateRolloutStrategyType, generateDefaultRollingUpdateConfig(), nil)) + Expect(k8sClient.Create(ctx, rolloutRP)).Should(Succeed(), "Failed to create RP") + + // Create a master resource snapshot. + resourceSnapshot := generateResourceSnapshot(rolloutRP.Namespace, rolloutRP.Name, 0, true) + Expect(k8sClient.Create(ctx, resourceSnapshot)).Should(Succeed(), "Failed to create resource snapshot") + + // Create bindings. + clusters := make([]string, targetClusterCount) + for i := 0; i < int(targetClusterCount); i++ { + clusters[i] = "cluster-" + utils.RandStr() + binding := generateResourceBinding(placementv1beta1.BindingStateScheduled, resourceSnapshot.Name, clusters[i], testNamespace) + Expect(k8sClient.Create(ctx, binding)).Should(Succeed(), "Failed to create resource binding") + bindings = append(bindings, binding) + + memberCluster := generateMemberCluster(i, clusters[i]) + Expect(k8sClient.Create(ctx, memberCluster)).Should(Succeed(), "Failed to create member cluster") + } + + // Verify that all the bindings are rolled out initially. + verifyBindingsRolledOut(controller.ConvertRBArrayToBindingObjs(bindings), resourceSnapshot, timeout) + + // Mark the bindings to be available. + for _, binding := range bindings { + markBindingAvailable(binding, true) + } + + // Create a clusterResourceOverrideSnapshot and a resourceOverrideSnapshot with cluster-scope placement and verify bindings are not updated. + testCROName := "cro" + utils.RandStr() + clusterResourceOverrideSnapshot := generateClusterResourceOverrideSnapshot(testCROName, testRPName) + By(fmt.Sprintf("Creating cluster resource override snapshot %s", clusterResourceOverrideSnapshot.Name)) + Expect(k8sClient.Create(ctx, clusterResourceOverrideSnapshot)).Should(Succeed(), "Failed to create cluster resource override snapshot") + + testROName1 := "ro" + utils.RandStr() + resourceOverrideSnapshot1 := generateResourceOverrideSnapshot(testROName1, testRPName, placementv1beta1.ClusterScoped) + By(fmt.Sprintf("Creating resource override snapshot %s", resourceOverrideSnapshot1.Name)) + Expect(k8sClient.Create(ctx, resourceOverrideSnapshot1)).Should(Succeed(), "Failed to create resource override snapshot") + + // Verify bindings are NOT updated (rollout not triggered) by clusterResourceOverrideSnapshot. + verifyBindingsNotUpdatedWithOverridesConsistently(controller.ConvertRBArrayToBindingObjs(bindings), nil, nil) + + // Create a resourceOverrideSnapshot and verify it triggers rollout. + testROName2 := "ro" + utils.RandStr() + resourceOverrideSnapshot2 := generateResourceOverrideSnapshot(testROName2, testRPName, placementv1beta1.NamespaceScoped) + By(fmt.Sprintf("Creating resource override snapshot %s", resourceOverrideSnapshot2.Name)) + Expect(k8sClient.Create(ctx, resourceOverrideSnapshot2)).Should(Succeed(), "Failed to create resource override snapshot") + + waitUntilRolloutCompleted(controller.ConvertRBArrayToBindingObjs(bindings), nil, []placementv1beta1.NamespacedName{ + {Name: resourceOverrideSnapshot2.Name, Namespace: resourceOverrideSnapshot2.Namespace}, + }) + + // Clean up the override snapshots. + Expect(k8sClient.Delete(ctx, resourceOverrideSnapshot1)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, resourceOverrideSnapshot2)).Should(Succeed()) + Expect(k8sClient.Delete(ctx, clusterResourceOverrideSnapshot)).Should(Succeed()) + + // Clean up the member clusters. + for _, cluster := range clusters { + memberCluster := &clusterv1beta1.MemberCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: cluster, + }, + } + Expect(k8sClient.Delete(ctx, memberCluster)).Should(SatisfyAny(Succeed(), utils.NotFoundMatcher{})) + } + }) }) func resourcePlacementForTest(namespace, rpName string, policy *placementv1beta1.PlacementPolicy, strategy placementv1beta1.RolloutStrategy) *placementv1beta1.ResourcePlacement { @@ -1609,6 +1774,92 @@ func verifyBindingsRolledOut(bindings []placementv1beta1.BindingObj, masterSnaps }, timeout, interval).Should(Succeed(), "rollout controller should roll out all the bindings") } +func verifyBindingsNotUpdatedWithOverridesConsistently( + bindings []placementv1beta1.BindingObj, + wantClusterResourceOverrideSnapshots []string, + wantResourceOverrideSnapshots []placementv1beta1.NamespacedName, +) { + Consistently(func() error { + for _, binding := range bindings { + bindingKey := types.NamespacedName{Name: binding.GetName(), Namespace: binding.GetNamespace()} + if _, err := checkIfBindingUpdatedWithOverrides(bindingKey, wantClusterResourceOverrideSnapshots, wantResourceOverrideSnapshots); err != nil { + return fmt.Errorf("binding %s should not be updated with overrides: %w", bindingKey, err) + } + } + return nil + }, consistentTimeout, interval).Should(Succeed(), "Bindings should not be updated with new overrides consistently") +} + +func waitUntilRolloutCompleted( + bindings []placementv1beta1.BindingObj, + wantClusterResourceOverrideSnapshots []string, + wantResourceOverrideSnapshots []placementv1beta1.NamespacedName, +) { + notUpdatedBindings := make(map[types.NamespacedName]bool, len(bindings)) + for _, binding := range bindings { + notUpdatedBindings[types.NamespacedName{Name: binding.GetName(), Namespace: binding.GetNamespace()}] = true + } + + for len(notUpdatedBindings) > 0 { + // In each round, try to find a binding that has been updated and update it to available so rollout can proceed. + var gotBinding placementv1beta1.BindingObj + var err error + Eventually(func() error { + for bindingKey := range notUpdatedBindings { + gotBinding, err = checkIfBindingUpdatedWithOverrides(bindingKey, wantClusterResourceOverrideSnapshots, wantResourceOverrideSnapshots) + if err != nil { + continue // current binding not updated yet, continue to check the next one. + } + delete(notUpdatedBindings, bindingKey) + return nil // found an updated binding, can exit this round. + } + return fmt.Errorf("failed to find a binding with updated overrides") + }, timeout, interval).Should(Succeed(), "One of the bindings should be updated with overrides") + // Mark the binding as available so rollout can proceed. + markBindingAvailable(gotBinding, true) + } +} + +func checkIfBindingUpdatedWithOverrides( + bindingKey types.NamespacedName, + wantClusterResourceOverrideSnapshots []string, + wantResourceOverrideSnapshots []placementv1beta1.NamespacedName, +) (placementv1beta1.BindingObj, error) { + var gotBinding placementv1beta1.BindingObj + if bindingKey.Namespace == "" { + gotBinding = &placementv1beta1.ClusterResourceBinding{} + } else { + gotBinding = &placementv1beta1.ResourceBinding{} + } + if err := k8sClient.Get(ctx, bindingKey, gotBinding); err != nil { + return gotBinding, fmt.Errorf("failed to get binding %s: %w", bindingKey, err) + } + + // Check that RolloutStarted condition is True. + if !condition.IsConditionStatusTrue(gotBinding.GetCondition(string(placementv1beta1.ResourceBindingRolloutStarted)), gotBinding.GetGeneration()) { + return gotBinding, fmt.Errorf("binding %s RolloutStarted condition is not True", bindingKey) + } + + // Check that override snapshots in spec are the want ones. + cmpOptions := []cmp.Option{ + cmpopts.EquateEmpty(), + cmpopts.SortSlices(func(a, b string) bool { return a < b }), + cmpopts.SortSlices(func(a, b placementv1beta1.NamespacedName) bool { + if a.Namespace == b.Namespace { + return a.Name < b.Name + } + return a.Namespace < b.Namespace + }), + } + if !cmp.Equal(gotBinding.GetBindingSpec().ClusterResourceOverrideSnapshots, wantClusterResourceOverrideSnapshots, cmpOptions...) || + !cmp.Equal(gotBinding.GetBindingSpec().ResourceOverrideSnapshots, wantResourceOverrideSnapshots, cmpOptions...) { + return gotBinding, fmt.Errorf("binding %s override snapshots mismatch: want %v and %v, got %v and %v", bindingKey, + wantClusterResourceOverrideSnapshots, wantResourceOverrideSnapshots, + gotBinding.GetBindingSpec().ClusterResourceOverrideSnapshots, gotBinding.GetBindingSpec().ResourceOverrideSnapshots) + } + return gotBinding, nil +} + func markBindingAvailable(binding placementv1beta1.BindingObj, trackable bool) { Eventually(func() error { reason := "trackable" @@ -1707,7 +1958,8 @@ func generateClusterResourceSnapshot(testCRPName string, resourceIndex int, isLa placementv1beta1.IsLatestSnapshotLabel: strconv.FormatBool(isLatest), }, Annotations: map[string]string{ - placementv1beta1.ResourceGroupHashAnnotation: "hash", + placementv1beta1.ResourceGroupHashAnnotation: "hash", + placementv1beta1.NumberOfResourceSnapshotsAnnotation: "1", }, }, } @@ -1734,7 +1986,8 @@ func generateResourceSnapshot(namespace, testRPName string, resourceIndex int, i placementv1beta1.IsLatestSnapshotLabel: strconv.FormatBool(isLatest), }, Annotations: map[string]string{ - placementv1beta1.ResourceGroupHashAnnotation: "hash", + placementv1beta1.ResourceGroupHashAnnotation: "hash", + placementv1beta1.NumberOfResourceSnapshotsAnnotation: "1", }, }, } @@ -1750,3 +2003,116 @@ func generateResourceSnapshot(namespace, testRPName string, resourceIndex int, i } return resourceSnapshot } + +func generateMemberCluster(idx int, clusterName string) *clusterv1beta1.MemberCluster { + clusterLabels := map[string]string{ + "index": strconv.Itoa(idx), + } + return &clusterv1beta1.MemberCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + Labels: clusterLabels, + }, + Spec: clusterv1beta1.MemberClusterSpec{ + Identity: rbacv1.Subject{ + Name: "testUser", + Kind: "ServiceAccount", + Namespace: utils.FleetSystemNamespace, + }, + HeartbeatPeriodSeconds: 60, + }, + } +} + +func generateClusterResourceOverrideSnapshot(testCROName, testPlacementName string) *placementv1beta1.ClusterResourceOverrideSnapshot { + return &placementv1beta1.ClusterResourceOverrideSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(placementv1beta1.OverrideSnapshotNameFmt, testCROName, 0), + Labels: map[string]string{ + placementv1beta1.OverrideIndexLabel: "0", + placementv1beta1.IsLatestSnapshotLabel: "true", + placementv1beta1.OverrideTrackingLabel: testCROName, + }, + }, + Spec: placementv1beta1.ClusterResourceOverrideSnapshotSpec{ + OverrideHash: []byte("cluster-override-hash"), + OverrideSpec: placementv1beta1.ClusterResourceOverrideSpec{ + Placement: &placementv1beta1.PlacementRef{ + Name: testPlacementName, + Scope: placementv1beta1.ClusterScoped, + }, + Policy: &placementv1beta1.OverridePolicy{ + OverrideRules: []placementv1beta1.OverrideRule{ + { + ClusterSelector: &placementv1beta1.ClusterSelector{ + ClusterSelectorTerms: []placementv1beta1.ClusterSelectorTerm{}, + }, + JSONPatchOverrides: []placementv1beta1.JSONPatchOverride{ + { + Operator: placementv1beta1.JSONPatchOverrideOpAdd, + Path: "/metadata/labels/test", + Value: apiextensionsv1.JSON{Raw: []byte(`"test"`)}, + }, + }, + }, + }, + }, + ClusterResourceSelectors: []placementv1beta1.ClusterResourceSelector{ + { + Group: "", + Version: "v1", + Kind: "Namespace", + Name: "app", // from manifests/test_namespace.yaml + }, + }, + }, + }, + } +} + +func generateResourceOverrideSnapshot(testROName, testPlacementName string, scope placementv1beta1.ResourceScope) *placementv1beta1.ResourceOverrideSnapshot { + return &placementv1beta1.ResourceOverrideSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(placementv1beta1.OverrideSnapshotNameFmt, testROName, 0), + Namespace: testNamespace, + Labels: map[string]string{ + placementv1beta1.OverrideIndexLabel: "0", + placementv1beta1.IsLatestSnapshotLabel: "true", + placementv1beta1.OverrideTrackingLabel: testROName, + }, + }, + Spec: placementv1beta1.ResourceOverrideSnapshotSpec{ + OverrideHash: []byte("resource-override-hash"), + OverrideSpec: placementv1beta1.ResourceOverrideSpec{ + Placement: &placementv1beta1.PlacementRef{ + Name: testPlacementName, + Scope: scope, + }, + Policy: &placementv1beta1.OverridePolicy{ + OverrideRules: []placementv1beta1.OverrideRule{ + { + ClusterSelector: &placementv1beta1.ClusterSelector{ + ClusterSelectorTerms: []placementv1beta1.ClusterSelectorTerm{}, + }, + JSONPatchOverrides: []placementv1beta1.JSONPatchOverride{ + { + Operator: placementv1beta1.JSONPatchOverrideOpAdd, + Path: "/metadata/labels/test", + Value: apiextensionsv1.JSON{Raw: []byte(`"test"`)}, + }, + }, + }, + }, + }, + ResourceSelectors: []placementv1beta1.ResourceSelector{ + { + Group: "", + Version: "v1", + Kind: "ConfigMap", + Name: "test-configmap", + }, + }, + }, + }, + } +} diff --git a/pkg/controllers/rollout/suite_test.go b/pkg/controllers/rollout/suite_test.go index 556a5c48a..5649838a3 100644 --- a/pkg/controllers/rollout/suite_test.go +++ b/pkg/controllers/rollout/suite_test.go @@ -29,6 +29,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/klog/v2" @@ -42,6 +43,8 @@ import ( clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" placementv1alpha1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1alpha1" placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" + "github.com/kubefleet-dev/kubefleet/pkg/utils" + "github.com/kubefleet-dev/kubefleet/pkg/utils/informer" ) var ( @@ -114,17 +117,29 @@ var _ = BeforeSuite(func() { } Expect(k8sClient.Create(ctx, namespace)).Should(Succeed()) + // setup informer manager for the reconciler + dynamicClient, err := dynamic.NewForConfig(cfg) + Expect(err).Should(Succeed()) + dynamicInformerManager := informer.NewInformerManager(dynamicClient, 0, ctx.Done()) + dynamicInformerManager.AddStaticResource(informer.APIResourceMeta{ + GroupVersionKind: utils.NamespaceGVK, + GroupVersionResource: utils.NamespaceGVR, + IsClusterScoped: true, + }, nil) + // setup our cluster scoped reconciler err = (&Reconciler{ - Client: k8sClient, - UncachedReader: mgr.GetAPIReader(), + Client: k8sClient, + UncachedReader: mgr.GetAPIReader(), + InformerManager: dynamicInformerManager, }).SetupWithManagerForClusterResourcePlacement(mgr) Expect(err).Should(Succeed()) // setup our namespace scoped reconciler err = (&Reconciler{ - Client: k8sClient, - UncachedReader: mgr.GetAPIReader(), + Client: k8sClient, + UncachedReader: mgr.GetAPIReader(), + InformerManager: dynamicInformerManager, }).SetupWithManagerForResourcePlacement(mgr) Expect(err).Should(Succeed())