From 50d30153ee740810ed62083e06dcdb54dc5c5baf Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Wed, 4 Jun 2025 12:28:50 +0200 Subject: [PATCH] Watch KeystoneAPI status updates to reconcile Adds watches of the Nova controller on the KeystoneAPI status to reconcile if e.g. the endpoint list changes. This triggers setting the new KeystoneAuthURL on the sub components to update their configuration. Depends-On: https://github.com/openstack-k8s-operators/keystone-operator/pull/591 Jira: OSPRH-16994 Signed-off-by: Martin Schuppert --- controllers/ironic_controller.go | 38 +++++++ controllers/ironicinspector_controller.go | 35 +++++++ controllers/ironicneutronagent_controller.go | 34 ++++++ go.mod | 6 +- go.sum | 12 +-- tests/functional/base_test.go | 5 + tests/functional/ironic_controller_test.go | 103 +++++++++++++++++++ 7 files changed, 224 insertions(+), 9 deletions(-) diff --git a/controllers/ironic_controller.go b/controllers/ironic_controller.go index 7a2f904d..19fdf8eb 100644 --- a/controllers/ironic_controller.go +++ b/controllers/ironic_controller.go @@ -43,9 +43,13 @@ import ( k8s_errors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -228,9 +232,43 @@ func (r *IronicReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.ServiceAccount{}). Owns(&rbacv1.Role{}). Owns(&rbacv1.RoleBinding{}). + Watches(&keystonev1.KeystoneAPI{}, + handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc), + builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)). Complete(r) } +func (r *IronicReconciler) findObjectForSrc(ctx context.Context, src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(ctx).WithName("Controllers").WithName("Ironic") + + crList := &ironicv1.IronicList{} + listOps := &client.ListOptions{ + Namespace: src.GetNamespace(), + } + err := r.Client.List(ctx, crList, listOps) + if err != nil { + l.Error(err, fmt.Sprintf("listing %s for namespace: %s", crList.GroupVersionKind().Kind, src.GetNamespace())) + return requests + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + + return requests +} + func (r *IronicReconciler) reconcileDelete(ctx context.Context, instance *ironicv1.Ironic, helper *helper.Helper) (ctrl.Result, error) { Log := r.GetLogger(ctx) diff --git a/controllers/ironicinspector_controller.go b/controllers/ironicinspector_controller.go index 1f15903d..f4ea3527 100644 --- a/controllers/ironicinspector_controller.go +++ b/controllers/ironicinspector_controller.go @@ -43,6 +43,7 @@ import ( routev1 "github.com/openshift/api/route/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" k8s_types "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" @@ -384,6 +385,9 @@ func (r *IronicInspectorReconciler) SetupWithManager( Watches(&topologyv1.Topology{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Watches(&keystonev1.KeystoneAPI{}, + handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc), + builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)). Complete(r) } @@ -421,6 +425,37 @@ func (r *IronicInspectorReconciler) findObjectsForSrc(ctx context.Context, src c return requests } +func (r *IronicInspectorReconciler) findObjectForSrc(ctx context.Context, src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(ctx).WithName("Controllers").WithName("IronicInspector") + + crList := &ironicv1.IronicInspectorList{} + listOps := &client.ListOptions{ + Namespace: src.GetNamespace(), + } + err := r.Client.List(ctx, crList, listOps) + if err != nil { + l.Error(err, fmt.Sprintf("listing %s for namespace: %s", crList.GroupVersionKind().Kind, src.GetNamespace())) + return requests + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + + return requests +} + func (r *IronicInspectorReconciler) getTransportURL( ctx context.Context, h *helper.Helper, diff --git a/controllers/ironicneutronagent_controller.go b/controllers/ironicneutronagent_controller.go index 02985ff0..b79cb0be 100644 --- a/controllers/ironicneutronagent_controller.go +++ b/controllers/ironicneutronagent_controller.go @@ -280,6 +280,9 @@ func (r *IronicNeutronAgentReconciler) SetupWithManager( Watches(&topologyv1.Topology{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Watches(&keystonev1.KeystoneAPI{}, + handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc), + builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)). Complete(r) } @@ -317,6 +320,37 @@ func (r *IronicNeutronAgentReconciler) findObjectsForSrc(ctx context.Context, sr return requests } +func (r *IronicNeutronAgentReconciler) findObjectForSrc(ctx context.Context, src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(ctx).WithName("Controllers").WithName("IronicNeutronAgent") + + crList := &ironicv1.IronicNeutronAgentList{} + listOps := &client.ListOptions{ + Namespace: src.GetNamespace(), + } + err := r.Client.List(ctx, crList, listOps) + if err != nil { + l.Error(err, fmt.Sprintf("listing %s for namespace: %s", crList.GroupVersionKind().Kind, src.GetNamespace())) + return requests + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + + return requests +} + func (r *IronicNeutronAgentReconciler) getTransportURL( ctx context.Context, h *helper.Helper, diff --git a/go.mod b/go.mod index 49b03f9e..4cd920c4 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/openshift/api v3.9.0+incompatible github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250513115636-b549982a5d8f github.com/openstack-k8s-operators/ironic-operator/api v0.0.0-00010101000000-000000000000 - github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250521085253-60910fbce943 + github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250604143452-2260c431b9f1 github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250508141203-be026d3164f7 github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20250508141203-be026d3164f7 github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20250521084122-c6dc1ca7ed7c @@ -54,8 +54,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250423055245-3cb2ae8df6f0 // indirect - github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20250423055245-3cb2ae8df6f0 // indirect + github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250508141203-be026d3164f7 // indirect + github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20250508141203-be026d3164f7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect diff --git a/go.sum b/go.sum index 11783f1f..bfa07991 100644 --- a/go.sum +++ b/go.sum @@ -80,14 +80,14 @@ github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 h1:J1wuGhVxpsHykZBa6 github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250513115636-b549982a5d8f h1:b3WGatQCIBoNj8RvbVGNITL9RuQLwkXzXAgt7s/D5zc= github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250513115636-b549982a5d8f/go.mod h1:47iJk3vedZWnBkZyNyYij4ma2HjG4l2VCqKz3f+XDkQ= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250521085253-60910fbce943 h1:FbalBf7TrezrxTQYHBGCGqx9ZZl6rLKuCap30sp4LYg= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250521085253-60910fbce943/go.mod h1:zaXbveCF80JXvRUuoXAXFXFMCbsiERvtyVoE0lpfTJ0= +github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250604143452-2260c431b9f1 h1:YQuJwgoQ9mEyzNq9/SgS3wPCtLG0wMQWH/caWAMZeSc= +github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250604143452-2260c431b9f1/go.mod h1:dgYQJbbVaRuP98yZZB3K1rNpqnF54I1HM1ZTaOzPKBY= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250508141203-be026d3164f7 h1:c3h1q3fDoit3NmvNL89xUL9A12bJivaTF+IOPEOAwLc= github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250508141203-be026d3164f7/go.mod h1:UwHXRIrMSPJD3lFqrA4oKmRXVLFQCRkLAj9x6KLEHiQ= -github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250423055245-3cb2ae8df6f0 h1:FAHrScvlj6w17wvcDhJ0ZnmraMrrOX1CxzvqZK595hA= -github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250423055245-3cb2ae8df6f0/go.mod h1:fesgTbs2j30Fhw2hebXkPgbeAIqG0Yk2oaeOklAInZg= -github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20250423055245-3cb2ae8df6f0 h1:JejCQvZ28JmG87iGpy0tk8v4WJzZ07PIIAxXRpiMR9E= -github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20250423055245-3cb2ae8df6f0/go.mod h1:5+v92XC/PRATIiBrhNLEpJ+T4R9JpxBCgRP6QvbfwgE= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250508141203-be026d3164f7 h1:IybBq3PrxwdvzAF19TjdMCqbEVkX2p3gIkme/Fju6do= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250508141203-be026d3164f7/go.mod h1:fesgTbs2j30Fhw2hebXkPgbeAIqG0Yk2oaeOklAInZg= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20250508141203-be026d3164f7 h1:N7HNoUrjqrWZWWcQdtaZubrQ1pFeWai1Cbls0RoCjK8= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20250508141203-be026d3164f7/go.mod h1:5+v92XC/PRATIiBrhNLEpJ+T4R9JpxBCgRP6QvbfwgE= github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20250508141203-be026d3164f7 h1:SiJ+T5OK5y4dkPFvHZZeLpSHjDFULaaGKZ2dFdE8uRg= github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20250508141203-be026d3164f7/go.mod h1:oKvVb28i6wwBR5uQO2B2KMzZnCFTPCUCj31c5Zvz2lo= github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20250521084122-c6dc1ca7ed7c h1:8IysMuJ8t37aBts1N04i1mkWOJNIfzBZHkkJjNkZy5g= diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go index 89323a9e..a8a6949e 100644 --- a/tests/functional/base_test.go +++ b/tests/functional/base_test.go @@ -80,6 +80,7 @@ type IronicNames struct { InspectorConfigSecretName types.NamespacedName INAName types.NamespacedName INATransportURLName types.NamespacedName + INAConfigSecretName types.NamespacedName KeystoneServiceName types.NamespacedName InternalCertSecretName types.NamespacedName PublicCertSecretName types.NamespacedName @@ -233,6 +234,10 @@ func GetIronicNames( Namespace: ironicNeutronAgent.Namespace, Name: ironicNeutronAgent.Name + "-transport", }, + INAConfigSecretName: types.NamespacedName{ + Namespace: ironicNeutronAgent.Namespace, + Name: "ironic-ironic-neutron-agent-config-data", + }, InternalCertSecretName: types.NamespacedName{ Namespace: ironicName.Namespace, Name: "internal-tls-certs", diff --git a/tests/functional/ironic_controller_test.go b/tests/functional/ironic_controller_test.go index d7fed5d1..b0fdd45b 100644 --- a/tests/functional/ironic_controller_test.go +++ b/tests/functional/ironic_controller_test.go @@ -686,6 +686,109 @@ var _ = Describe("Ironic controller", func() { }) + Context("Ironic is fully deployed", func() { + keystoneAPIName := types.NamespacedName{} + BeforeEach(func() { + DeferCleanup( + k8sClient.Delete, + ctx, + CreateIronicSecret(ironicNames.Namespace, SecretName), + ) + DeferCleanup( + k8sClient.Delete, + ctx, + CreateMessageBusSecret(ironicNames.Namespace, MessageBusSecretName), + ) + + apiMariaDBAccount, apiMariaDBSecret := mariadb.CreateMariaDBAccountAndSecret(ironicNames.IronicDatabaseAccount, mariadbv1.MariaDBAccountSpec{}) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBAccount) + DeferCleanup(k8sClient.Delete, ctx, apiMariaDBSecret) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + ironicNames.Namespace, + "openstack", + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + keystoneAPIName = keystone.CreateKeystoneAPI(ironicNames.Namespace) + DeferCleanup( + keystone.DeleteKeystoneAPI, + keystoneAPIName) + DeferCleanup( + th.DeleteInstance, + CreateIronic(ironicNames.IronicName, GetDefaultIronicSpec()), + ) + mariadb.GetMariaDBDatabase(ironicNames.IronicDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(ironicNames.IronicDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(ironicNames.IronicDatabaseName) + th.SimulateJobSuccess(ironicNames.IronicDBSyncJobName) + + keystone.SimulateKeystoneServiceReady(ironicNames.IronicName) + keystone.SimulateKeystoneEndpointReady(ironicNames.IronicName) + + mariadb.GetMariaDBDatabase(ironicNames.InspectorDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(ironicNames.InspectorDatabaseAccount) + mariadb.SimulateMariaDBDatabaseCompleted(ironicNames.InspectorDatabaseName) + th.SimulateJobSuccess(ironicNames.InspectorDBSyncJobName) + + nestedINATransportURLName := ironicNames.INATransportURLName + nestedINATransportURLName.Name = ironicNames.IronicName.Name + "-" + nestedINATransportURLName.Name + infra.GetTransportURL(nestedINATransportURLName) + infra.SimulateTransportURLReady(nestedINATransportURLName) + + th.SimulateDeploymentReplicaReady(ironicNames.IronicName) + th.SimulateStatefulSetReplicaReady(ironicNames.ConductorName) + th.SimulateStatefulSetReplicaReady(ironicNames.InspectorName) + keystone.SimulateKeystoneServiceReady(ironicNames.InspectorName) + keystone.SimulateKeystoneEndpointReady(ironicNames.InspectorName) + th.SimulateDeploymentReplicaReady(ironicNames.INAName) + + th.ExpectCondition( + ironicNames.IronicName, + ConditionGetterFunc(IronicConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("updates the KeystoneAuthURL if keystone internal endpoint changes", func() { + newInternalEndpoint := "https://keystone-internal" + + keystone.UpdateKeystoneAPIEndpoint(keystoneAPIName, "internal", newInternalEndpoint) + logger.Info("Reconfigured") + + Eventually(func(g Gomega) { + confSecret := th.GetSecret(ironicNames.IronicConfigSecretName) + g.Expect(confSecret).ShouldNot(BeNil()) + + conf := confSecret.Data["ironic.conf"] + g.Expect(string(conf)).Should( + ContainSubstring("auth_url=%s", newInternalEndpoint)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + confSecret := th.GetSecret(ironicNames.InspectorConfigSecretName) + g.Expect(confSecret).ShouldNot(BeNil()) + + conf := confSecret.Data["01-inspector.conf"] + g.Expect(string(conf)).Should( + ContainSubstring("auth_url=%s", newInternalEndpoint)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + confSecret := th.GetSecret(ironicNames.INAConfigSecretName) + g.Expect(confSecret).ShouldNot(BeNil()) + + conf := confSecret.Data["01-ironic_neutron_agent.conf"] + g.Expect(string(conf)).Should( + ContainSubstring("auth_url=%s", newInternalEndpoint)) + }, timeout, interval).Should(Succeed()) + }) + }) + When("Ironic is created with topologyref", func() { var topologyRef, topologyRefAlt *topologyv1.TopoRef BeforeEach(func() {