From 35b75febe155a575e7d8de5c639bae184742bff2 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Thu, 5 Mar 2026 10:06:31 +0530 Subject: [PATCH 1/2] Adds predicates to filter Nova reconciliation triggers When a RabbitMQ notification pod restarts, the operator controller is reconciling lot of times (~40+) due to status-only updates from owned resources without any actual change in Nova CR spec The issues occured because Nova is watching all resources without predicates. this change adds inbuilt GenerationChangedPredicate to child CRs (Nova services only) to only reconcile on spec changes, not status-only updates. Related: #OSPRH-26922 --- internal/controller/nova_controller.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/internal/controller/nova_controller.go b/internal/controller/nova_controller.go index 345ff7db8..86f2d629e 100644 --- a/internal/controller/nova_controller.go +++ b/internal/controller/nova_controller.go @@ -2341,12 +2341,18 @@ func (r *NovaReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&mariadbv1.MariaDBDatabase{}). Owns(&mariadbv1.MariaDBAccount{}). Owns(&keystonev1.KeystoneService{}). - Owns(&novav1.NovaAPI{}). - Owns(&novav1.NovaScheduler{}). - Owns(&novav1.NovaCell{}). - Owns(&novav1.NovaMetadata{}). + Owns(&novav1.NovaAPI{}, + builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Owns(&novav1.NovaScheduler{}, + builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Owns(&novav1.NovaCell{}, + builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Owns(&novav1.NovaConductor{}, + builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Owns(&novav1.NovaMetadata{}, + builder.WithPredicates(predicate.GenerationChangedPredicate{})). Owns(&rabbitmqv1.TransportURL{}, - builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). + builder.WithPredicates(predicate.GenerationChangedPredicate{})). Owns(&batchv1.Job{}). Owns(&corev1.ServiceAccount{}). Owns(&rbacv1.Role{}). From fb03b85ea8c422075f338eda92db1916d9955624 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Thu, 5 Mar 2026 11:47:49 +0530 Subject: [PATCH 2/2] Add tests to see reconciler filter works Add tests to verify that GenerationChangedPredicate correctly filters status-only updates and prevents unnecessary Nova reconciliations. --- test/functional/nova_controller_test.go | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/functional/nova_controller_test.go b/test/functional/nova_controller_test.go index 37f83656b..110655983 100644 --- a/test/functional/nova_controller_test.go +++ b/test/functional/nova_controller_test.go @@ -2103,3 +2103,74 @@ var _ = Describe("application credentials", func() { }) }) }) + +var _ = Describe("Nova controller - predicates", func() { + When("Nova CR with child resources exists", func() { + BeforeEach(func() { + CreateNovaWithNCellsAndEnsureReady(1, &novaNames) + }) + + It("do not - reconcile Nova when NovaAPI CR status changes", func() { + nova := GetNova(novaNames.NovaName) + // get initial resourceVersion + initialResourceVersion := nova.ResourceVersion + + // Update nova-api status (not spec) + Eventually(func(g Gomega) { + api := GetNovaAPI(novaNames.APIName) + api.Status.ReadyCount = 999 + // here only updating status and not object spec + g.Expect(k8sClient.Status().Update(ctx, api)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + // Check nova resourceVersion did not change + // so above update call did not make any new reconciliation to change resourceversion + Consistently(func(g Gomega) { + nova = GetNova(novaNames.NovaName) + g.Expect(nova.ResourceVersion).To(Equal(initialResourceVersion)) + // here we must wait for full duration to ensure no delayed reconciliation was triggered. + // checking consistently for 25 sec + }, consistencyTimeout, interval).Should(Succeed()) + }) + + It("do - reconcile Nova when child CR spec changes", func() { + nova := GetNova(novaNames.NovaName) + initialResourceVersion := nova.ResourceVersion + + Eventually(func(g Gomega) { + api := GetNovaAPI(novaNames.APIName) + api.Spec.Replicas = ptr.To(int32(3)) + // update api spec + g.Expect(k8sClient.Update(ctx, api)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + // Verify Nova resourceVersion did change + // Nova should reconcile due to spec change + Eventually(func(g Gomega) { + nova = GetNova(novaNames.NovaName) + g.Expect(nova.ResourceVersion).ToNot(Equal(initialResourceVersion)) + }, timeout, interval).Should(Succeed()) + }) + + It("do - reconcile Nova when TransportURL spec changes", func() { + transportURL := infra.GetTransportURL(cell0.TransportURLName) + Expect(transportURL).ToNot(BeNil()) + + nova := GetNova(novaNames.NovaName) + initialResourceVersion := nova.ResourceVersion + + // Update TransportURL spec + Eventually(func(g Gomega) { + transportURL = infra.GetTransportURL(cell0.TransportURLName) + transportURL.Spec.RabbitmqClusterName = "rabbitmq-notification" + g.Expect(k8sClient.Update(ctx, transportURL)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + // Verify Nova resourceVersion did change + Eventually(func(g Gomega) { + nova = GetNova(novaNames.NovaName) + g.Expect(nova.ResourceVersion).ToNot(Equal(initialResourceVersion)) + }, timeout, interval).Should(Succeed()) + }) + }) +})