Skip to content

Commit 98092c1

Browse files
stuggiopenshift-merge-bot[bot]
authored andcommitted
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: openstack-k8s-operators/keystone-operator#591 Jira: OSPRH-16994 Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
1 parent 9c97b11 commit 98092c1

9 files changed

Lines changed: 230 additions & 39 deletions

controllers/watcherapi_controller.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,9 @@ func (r *WatcherAPIReconciler) SetupWithManager(mgr ctrl.Manager) error {
916916
Watches(&topologyv1.Topology{},
917917
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
918918
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
919+
Watches(&keystonev1.KeystoneAPI{},
920+
handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc),
921+
builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)).
919922
Complete(r)
920923
}
921924

@@ -953,6 +956,37 @@ func (r *WatcherAPIReconciler) findObjectsForSrc(ctx context.Context, src client
953956
return requests
954957
}
955958

959+
func (r *WatcherAPIReconciler) findObjectForSrc(ctx context.Context, src client.Object) []reconcile.Request {
960+
requests := []reconcile.Request{}
961+
962+
l := log.FromContext(ctx).WithName("Controllers").WithName("WatcherAPI")
963+
964+
crList := &watcherv1beta1.WatcherAPIList{}
965+
listOps := &client.ListOptions{
966+
Namespace: src.GetNamespace(),
967+
}
968+
err := r.Client.List(ctx, crList, listOps)
969+
if err != nil {
970+
l.Error(err, fmt.Sprintf("listing %s for namespace: %s", crList.GroupVersionKind().Kind, src.GetNamespace()))
971+
return requests
972+
}
973+
974+
for _, item := range crList.Items {
975+
l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace()))
976+
977+
requests = append(requests,
978+
reconcile.Request{
979+
NamespacedName: types.NamespacedName{
980+
Name: item.GetName(),
981+
Namespace: item.GetNamespace(),
982+
},
983+
},
984+
)
985+
}
986+
987+
return requests
988+
}
989+
956990
func (r *WatcherAPIReconciler) createHashOfInputHashes(
957991
ctx context.Context,
958992
instance *watcherv1beta1.WatcherAPI,

controllers/watcherapplier_controller.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,9 @@ func (r *WatcherApplierReconciler) SetupWithManager(mgr ctrl.Manager) error {
493493
Watches(&topologyv1.Topology{},
494494
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
495495
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
496+
Watches(&keystonev1.KeystoneAPI{},
497+
handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc),
498+
builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)).
496499
Complete(r)
497500
}
498501

@@ -530,6 +533,37 @@ func (r *WatcherApplierReconciler) findObjectsForSrc(ctx context.Context, src cl
530533
return requests
531534
}
532535

536+
func (r *WatcherApplierReconciler) findObjectForSrc(ctx context.Context, src client.Object) []reconcile.Request {
537+
requests := []reconcile.Request{}
538+
539+
l := log.FromContext(ctx).WithName("Controllers").WithName("WatcherApplier")
540+
541+
crList := &watcherv1beta1.WatcherApplierList{}
542+
listOps := &client.ListOptions{
543+
Namespace: src.GetNamespace(),
544+
}
545+
err := r.Client.List(ctx, crList, listOps)
546+
if err != nil {
547+
l.Error(err, fmt.Sprintf("listing %s for namespace: %s", crList.GroupVersionKind().Kind, src.GetNamespace()))
548+
return requests
549+
}
550+
551+
for _, item := range crList.Items {
552+
l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace()))
553+
554+
requests = append(requests,
555+
reconcile.Request{
556+
NamespacedName: types.NamespacedName{
557+
Name: item.GetName(),
558+
Namespace: item.GetNamespace(),
559+
},
560+
},
561+
)
562+
}
563+
564+
return requests
565+
}
566+
533567
func getApplierServiceLabels() map[string]string {
534568
return map[string]string{
535569
common.AppSelector: WatcherApplierLabelPrefix,

controllers/watcherdecisionengine_controller.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,9 @@ func (r *WatcherDecisionEngineReconciler) SetupWithManager(mgr ctrl.Manager) err
384384
Watches(&topologyv1.Topology{},
385385
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc),
386386
builder.WithPredicates(predicate.GenerationChangedPredicate{})).
387+
Watches(&keystonev1.KeystoneAPI{},
388+
handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc),
389+
builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)).
387390
Complete(r)
388391
}
389392

@@ -421,6 +424,37 @@ func (r *WatcherDecisionEngineReconciler) findObjectsForSrc(ctx context.Context,
421424
return requests
422425
}
423426

427+
func (r *WatcherDecisionEngineReconciler) findObjectForSrc(ctx context.Context, src client.Object) []reconcile.Request {
428+
requests := []reconcile.Request{}
429+
430+
l := log.FromContext(ctx).WithName("Controllers").WithName("WatcherDecisionEngine")
431+
432+
crList := &watcherv1beta1.WatcherDecisionEngineList{}
433+
listOps := &client.ListOptions{
434+
Namespace: src.GetNamespace(),
435+
}
436+
err := r.Client.List(ctx, crList, listOps)
437+
if err != nil {
438+
l.Error(err, fmt.Sprintf("listing %s for namespace: %s", crList.GroupVersionKind().Kind, src.GetNamespace()))
439+
return requests
440+
}
441+
442+
for _, item := range crList.Items {
443+
l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace()))
444+
445+
requests = append(requests,
446+
reconcile.Request{
447+
NamespacedName: types.NamespacedName{
448+
Name: item.GetName(),
449+
Namespace: item.GetNamespace(),
450+
},
451+
},
452+
)
453+
}
454+
455+
return requests
456+
}
457+
424458
// generateServiceConfigs - create Secret which holds the service configuration
425459
// NOTE - jgilaber this function is WIP, currently implements a fraction of its
426460
// functionality and will be expanded of further iteration to actually generate

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/onsi/gomega v1.34.1
1010
github.com/openshift/api v3.9.0+incompatible
1111
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250530085921-2b2be72badf4
12-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250528115713-faa7ebf8f7fc
12+
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250604143452-2260c431b9f1
1313
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250508141203-be026d3164f7
1414
github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20250508141203-be026d3164f7
1515
github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20250524131103-7ebaceec882b

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094 h1:J1wuGhVxpsHykZBa6
8080
github.com/openshift/api v0.0.0-20240830023148-b7d0481c9094/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4=
8181
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250530085921-2b2be72badf4 h1:FmgzhJRoXETu1zY+WJItpw3MEq+uR/7Gx5yXtfMs3UI=
8282
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20250530085921-2b2be72badf4/go.mod h1:47iJk3vedZWnBkZyNyYij4ma2HjG4l2VCqKz3f+XDkQ=
83-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250528115713-faa7ebf8f7fc h1:gYSgcZ4cCz41GIElVe122d4ZzoUQZ8ag886Pvse6HVU=
84-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250528115713-faa7ebf8f7fc/go.mod h1:dgYQJbbVaRuP98yZZB3K1rNpqnF54I1HM1ZTaOzPKBY=
83+
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250604143452-2260c431b9f1 h1:YQuJwgoQ9mEyzNq9/SgS3wPCtLG0wMQWH/caWAMZeSc=
84+
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20250604143452-2260c431b9f1/go.mod h1:dgYQJbbVaRuP98yZZB3K1rNpqnF54I1HM1ZTaOzPKBY=
8585
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250508141203-be026d3164f7 h1:c3h1q3fDoit3NmvNL89xUL9A12bJivaTF+IOPEOAwLc=
8686
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20250508141203-be026d3164f7/go.mod h1:UwHXRIrMSPJD3lFqrA4oKmRXVLFQCRkLAj9x6KLEHiQ=
8787
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20250508141203-be026d3164f7 h1:IybBq3PrxwdvzAF19TjdMCqbEVkX2p3gIkme/Fju6do=

tests/functional/watcher_test_data.go

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,41 @@ type APIType string
3030
// WatcherTestData is the data structure used to provide input data to envTest
3131
type WatcherTestData struct {
3232
//DatabaseHostname string
33-
DatabaseInstance string
34-
RabbitMqClusterName string
35-
Instance types.NamespacedName
36-
Watcher types.NamespacedName
37-
WatcherDatabaseName types.NamespacedName
38-
WatcherDatabaseAccount types.NamespacedName
39-
WatcherDatabaseAccountSecret types.NamespacedName
40-
InternalTopLevelSecretName types.NamespacedName
41-
PrometheusSecretName types.NamespacedName
42-
WatcherTransportURL types.NamespacedName
43-
KeystoneServiceName types.NamespacedName
44-
WatcherAPI types.NamespacedName
45-
MemcachedNamespace types.NamespacedName
46-
ServiceAccountName types.NamespacedName
47-
RoleName types.NamespacedName
48-
RoleBindingName types.NamespacedName
49-
WatcherDBSync types.NamespacedName
50-
WatcherAPIStatefulSet types.NamespacedName
51-
WatcherDecisionEngine types.NamespacedName
52-
WatcherDecisionEngineStatefulSet types.NamespacedName
53-
WatcherDecisionEngineSecret types.NamespacedName
54-
WatcherPublicServiceName types.NamespacedName
55-
WatcherInternalServiceName types.NamespacedName
56-
WatcherRouteName types.NamespacedName
57-
WatcherInternalRouteName types.NamespacedName
58-
WatcherKeystoneEndpointName types.NamespacedName
59-
WatcherApplier types.NamespacedName
60-
WatcherApplierStatefulSet types.NamespacedName
61-
WatcherRouteCertSecret types.NamespacedName
62-
WatcherPublicCertSecret types.NamespacedName
63-
WatcherInternalCertSecret types.NamespacedName
64-
WatcherApplierSecret types.NamespacedName
33+
DatabaseInstance string
34+
RabbitMqClusterName string
35+
Instance types.NamespacedName
36+
Watcher types.NamespacedName
37+
WatcherDatabaseName types.NamespacedName
38+
WatcherDatabaseAccount types.NamespacedName
39+
WatcherDatabaseAccountSecret types.NamespacedName
40+
InternalTopLevelSecretName types.NamespacedName
41+
PrometheusSecretName types.NamespacedName
42+
WatcherTransportURL types.NamespacedName
43+
KeystoneServiceName types.NamespacedName
44+
WatcherAPI types.NamespacedName
45+
WatcherAPIConfigSecret types.NamespacedName
46+
MemcachedNamespace types.NamespacedName
47+
ServiceAccountName types.NamespacedName
48+
RoleName types.NamespacedName
49+
RoleBindingName types.NamespacedName
50+
WatcherDBSync types.NamespacedName
51+
WatcherAPIStatefulSet types.NamespacedName
52+
WatcherDecisionEngine types.NamespacedName
53+
WatcherDecisionEngineConfigSecret types.NamespacedName
54+
WatcherDecisionEngineStatefulSet types.NamespacedName
55+
WatcherDecisionEngineSecret types.NamespacedName
56+
WatcherPublicServiceName types.NamespacedName
57+
WatcherInternalServiceName types.NamespacedName
58+
WatcherRouteName types.NamespacedName
59+
WatcherInternalRouteName types.NamespacedName
60+
WatcherKeystoneEndpointName types.NamespacedName
61+
WatcherApplier types.NamespacedName
62+
WatcherApplierConfigSecret types.NamespacedName
63+
WatcherApplierStatefulSet types.NamespacedName
64+
WatcherRouteCertSecret types.NamespacedName
65+
WatcherPublicCertSecret types.NamespacedName
66+
WatcherInternalCertSecret types.NamespacedName
67+
WatcherApplierSecret types.NamespacedName
6568
}
6669

6770
// GetWatcherTestData is a function that initialize the WatcherTestData
@@ -110,10 +113,18 @@ func GetWatcherTestData(watcherName types.NamespacedName) WatcherTestData {
110113
Namespace: watcherName.Namespace,
111114
Name: "watcher-api",
112115
},
116+
WatcherAPIConfigSecret: types.NamespacedName{
117+
Namespace: watcherName.Namespace,
118+
Name: "watcher-api-config-data",
119+
},
113120
WatcherDecisionEngine: types.NamespacedName{
114121
Namespace: watcherName.Namespace,
115122
Name: "watcher-decision-engine",
116123
},
124+
WatcherDecisionEngineConfigSecret: types.NamespacedName{
125+
Namespace: watcherName.Namespace,
126+
Name: "watcher-decision-engine-config-data",
127+
},
117128
WatcherDecisionEngineStatefulSet: types.NamespacedName{
118129
Namespace: watcherName.Namespace,
119130
Name: "watcher-decision-engine",
@@ -170,6 +181,10 @@ func GetWatcherTestData(watcherName types.NamespacedName) WatcherTestData {
170181
Namespace: watcherName.Namespace,
171182
Name: "watcher-applier",
172183
},
184+
WatcherApplierConfigSecret: types.NamespacedName{
185+
Namespace: watcherName.Namespace,
186+
Name: "watcher-applier-config-data",
187+
},
173188
WatcherApplierStatefulSet: types.NamespacedName{
174189
Namespace: watcherName.Namespace,
175190
Name: "watcher-applier",

tests/functional/watcherapi_controller_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ var _ = Describe("WatcherAPI controller", func() {
114114
})
115115
})
116116
When("the secret is created with all the expected fields and has all the required infra", func() {
117+
var keystoneAPIName types.NamespacedName
117118
BeforeEach(func() {
118119
secret := th.CreateSecret(
119120
watcherTest.InternalTopLevelSecretName,
@@ -162,7 +163,8 @@ var _ = Describe("WatcherAPI controller", func() {
162163
mariadb.SimulateMariaDBAccountCompleted(watcherTest.WatcherDatabaseAccount)
163164
mariadb.SimulateMariaDBDatabaseCompleted(watcherTest.WatcherDatabaseName)
164165
DeferCleanup(th.DeleteInstance, CreateWatcherAPI(watcherTest.WatcherAPI, GetDefaultWatcherAPISpec()))
165-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(watcherTest.WatcherAPI.Namespace))
166+
keystoneAPIName = keystone.CreateKeystoneAPI(watcherTest.WatcherAPI.Namespace)
167+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
166168
memcachedSpec := memcachedv1.MemcachedSpec{
167169
MemcachedSpecCore: memcachedv1.MemcachedSpecCore{
168170
Replicas: ptr.To(int32(1)),
@@ -269,6 +271,29 @@ var _ = Describe("WatcherAPI controller", func() {
269271
WatcherAPI := GetWatcherAPI(watcherTest.WatcherAPI)
270272
Expect(WatcherAPI.IsReady()).Should(BeTrue())
271273
})
274+
275+
It("updates the KeystoneAuthURL if keystone internal endpoint changes", func() {
276+
newInternalEndpoint := "https://keystone-internal"
277+
278+
keystone.UpdateKeystoneAPIEndpoint(keystoneAPIName, "internal", newInternalEndpoint)
279+
logger.Info("Reconfigured")
280+
281+
th.ExpectCondition(
282+
watcherTest.WatcherAPI,
283+
ConditionGetterFunc(WatcherAPIConditionGetter),
284+
condition.ServiceConfigReadyCondition,
285+
corev1.ConditionTrue,
286+
)
287+
288+
Eventually(func(g Gomega) {
289+
confSecret := th.GetSecret(watcherTest.WatcherAPIConfigSecret)
290+
g.Expect(confSecret).ShouldNot(BeNil())
291+
292+
conf := string(confSecret.Data["00-default.conf"])
293+
g.Expect(conf).Should(
294+
ContainSubstring("auth_url = %s", newInternalEndpoint))
295+
}, timeout, interval).Should(Succeed())
296+
})
272297
})
273298
When("the secret is created but missing fields", func() {
274299
BeforeEach(func() {

tests/functional/watcherapplier_controller_test.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ var _ = Describe("WatcherApplier controller", func() {
114114
})
115115
})
116116
When("the secret is created with all the expected fields and has all the required infra", func() {
117+
var keystoneAPIName types.NamespacedName
118+
117119
BeforeEach(func() {
118120
secret := th.CreateSecret(
119121
watcherTest.InternalTopLevelSecretName,
@@ -153,7 +155,8 @@ var _ = Describe("WatcherApplier controller", func() {
153155
)
154156
mariadb.SimulateMariaDBAccountCompleted(watcherTest.WatcherDatabaseAccount)
155157
mariadb.SimulateMariaDBDatabaseCompleted(watcherTest.WatcherDatabaseName)
156-
DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(watcherTest.WatcherApplier.Namespace))
158+
keystoneAPIName = keystone.CreateKeystoneAPI(watcherTest.WatcherApplier.Namespace)
159+
DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName)
157160
memcachedSpec := memcachedv1.MemcachedSpec{
158161
MemcachedSpecCore: memcachedv1.MemcachedSpecCore{
159162
Replicas: ptr.To(int32(1)),
@@ -264,6 +267,28 @@ interface = internal`,
264267
WatcherApplier := GetWatcherApplier(watcherTest.WatcherApplier)
265268
Expect(WatcherApplier.IsReady()).Should(BeTrue())
266269
})
270+
It("updates the KeystoneAuthURL if keystone internal endpoint changes", func() {
271+
newInternalEndpoint := "https://keystone-internal"
272+
273+
keystone.UpdateKeystoneAPIEndpoint(keystoneAPIName, "internal", newInternalEndpoint)
274+
logger.Info("Reconfigured")
275+
276+
th.ExpectCondition(
277+
watcherTest.WatcherApplier,
278+
ConditionGetterFunc(WatcherApplierConditionGetter),
279+
condition.ServiceConfigReadyCondition,
280+
corev1.ConditionTrue,
281+
)
282+
283+
Eventually(func(g Gomega) {
284+
confSecret := th.GetSecret(watcherTest.WatcherApplierConfigSecret)
285+
g.Expect(confSecret).ShouldNot(BeNil())
286+
287+
conf := string(confSecret.Data["00-default.conf"])
288+
g.Expect(conf).Should(
289+
ContainSubstring("auth_url = %s", newInternalEndpoint))
290+
}, timeout, interval).Should(Succeed())
291+
})
267292
})
268293
When("the secret is created but missing fields", func() {
269294
BeforeEach(func() {

0 commit comments

Comments
 (0)