@@ -15,7 +15,7 @@ import (
1515 corev1 "k8s.io/api/core/v1"
1616 networkingv1 "k8s.io/api/networking/v1"
1717 rbacv1 "k8s.io/api/rbac/v1"
18- "k8s.io/apimachinery/pkg/api/errors"
18+ apierrors "k8s.io/apimachinery/pkg/api/errors"
1919 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020 "k8s.io/apimachinery/pkg/types"
2121 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -219,6 +219,132 @@ var _ = Describe("App server reconciliator", Ordered, func() {
219219 Expect (dep .Spec .Template .Spec .Tolerations ).To (Equal (olsConfig .Spec .OLSConfig .DeploymentConfig .APIContainer .Tolerations ))
220220 })
221221
222+ It ("should track MCP Server ConfigMap ResourceVersion and trigger restart on introspection toggle" , func () {
223+ By ("Disable introspection initially" )
224+ olsConfig := & olsv1alpha1.OLSConfig {}
225+ err := k8sClient .Get (ctx , crNamespacedName , olsConfig )
226+ Expect (err ).NotTo (HaveOccurred ())
227+ olsConfig .Spec .OLSConfig .IntrospectionEnabled = false
228+
229+ By ("Reconcile with introspection disabled" )
230+ err = ReconcileAppServer (testReconcilerInstance , ctx , olsConfig )
231+ Expect (err ).NotTo (HaveOccurred ())
232+
233+ By ("Verify MCP Server ConfigMap does not exist" )
234+ mcpCm := & corev1.ConfigMap {}
235+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OpenShiftMCPServerConfigCmName , Namespace : utils .OLSNamespaceDefault }, mcpCm )
236+ Expect (apierrors .IsNotFound (err )).To (BeTrue (), "MCP ConfigMap should not exist when introspection is disabled" )
237+
238+ By ("Get deployment and verify MCP annotation is empty" )
239+ dep := & appsv1.Deployment {}
240+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OLSAppServerDeploymentName , Namespace : utils .OLSNamespaceDefault }, dep )
241+ Expect (err ).NotTo (HaveOccurred ())
242+ mcpAnnotation := dep .Annotations [utils .OpenShiftMCPServerConfigMapResourceVersionAnnotation ]
243+ Expect (mcpAnnotation ).To (BeEmpty (), "MCP annotation should be empty when ConfigMap doesn't exist" )
244+
245+ By ("Enable introspection" )
246+ err = k8sClient .Get (ctx , crNamespacedName , olsConfig )
247+ Expect (err ).NotTo (HaveOccurred ())
248+ olsConfig .Spec .OLSConfig .IntrospectionEnabled = true
249+
250+ By ("Reconcile with introspection enabled" )
251+ err = ReconcileAppServer (testReconcilerInstance , ctx , olsConfig )
252+ Expect (err ).NotTo (HaveOccurred ())
253+
254+ By ("Verify MCP Server ConfigMap was created" )
255+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OpenShiftMCPServerConfigCmName , Namespace : utils .OLSNamespaceDefault }, mcpCm )
256+ Expect (err ).NotTo (HaveOccurred (), "MCP ConfigMap should exist after enabling introspection" )
257+ firstResourceVersion := mcpCm .ResourceVersion
258+ Expect (firstResourceVersion ).NotTo (BeEmpty ())
259+
260+ By ("Verify deployment annotation tracks MCP ConfigMap ResourceVersion" )
261+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OLSAppServerDeploymentName , Namespace : utils .OLSNamespaceDefault }, dep )
262+ Expect (err ).NotTo (HaveOccurred ())
263+ mcpAnnotation = dep .Annotations [utils .OpenShiftMCPServerConfigMapResourceVersionAnnotation ]
264+ Expect (mcpAnnotation ).To (Equal (firstResourceVersion ), "Deployment annotation should match ConfigMap ResourceVersion" )
265+
266+ By ("Disable introspection again" )
267+ err = k8sClient .Get (ctx , crNamespacedName , olsConfig )
268+ Expect (err ).NotTo (HaveOccurred ())
269+ olsConfig .Spec .OLSConfig .IntrospectionEnabled = false
270+
271+ By ("Reconcile with introspection disabled again" )
272+ err = ReconcileAppServer (testReconcilerInstance , ctx , olsConfig )
273+ Expect (err ).NotTo (HaveOccurred ())
274+
275+ By ("Verify MCP Server ConfigMap was deleted" )
276+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OpenShiftMCPServerConfigCmName , Namespace : utils .OLSNamespaceDefault }, mcpCm )
277+ Expect (apierrors .IsNotFound (err )).To (BeTrue (), "MCP ConfigMap should be deleted when introspection is disabled" )
278+
279+ By ("Verify deployment annotation changed (triggering restart)" )
280+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OLSAppServerDeploymentName , Namespace : utils .OLSNamespaceDefault }, dep )
281+ Expect (err ).NotTo (HaveOccurred ())
282+ newMcpAnnotation := dep .Annotations [utils .OpenShiftMCPServerConfigMapResourceVersionAnnotation ]
283+ Expect (newMcpAnnotation ).NotTo (Equal (firstResourceVersion ), "Annotation should change when ConfigMap is deleted to trigger pod restart" )
284+
285+ By ("Verify pod template restart annotation is set" )
286+ forceReloadValue := dep .Spec .Template .Annotations [utils .ForceReloadAnnotationKey ]
287+ Expect (forceReloadValue ).NotTo (BeEmpty (), "Pod template restart annotation should be set to trigger rolling update" )
288+ })
289+
290+ It ("should trigger rolling update when MCP ConfigMap is modified externally" , func () {
291+ By ("Enable introspection" )
292+ olsConfig := & olsv1alpha1.OLSConfig {}
293+ err := k8sClient .Get (ctx , crNamespacedName , olsConfig )
294+ Expect (err ).NotTo (HaveOccurred ())
295+ olsConfig .Spec .OLSConfig .IntrospectionEnabled = true
296+
297+ By ("Reconcile to create MCP ConfigMap" )
298+ err = ReconcileAppServer (testReconcilerInstance , ctx , olsConfig )
299+ Expect (err ).NotTo (HaveOccurred ())
300+
301+ By ("Get the MCP ConfigMap and capture initial ResourceVersion" )
302+ mcpCm := & corev1.ConfigMap {}
303+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OpenShiftMCPServerConfigCmName , Namespace : utils .OLSNamespaceDefault }, mcpCm )
304+ Expect (err ).NotTo (HaveOccurred ())
305+ initialResourceVersion := mcpCm .ResourceVersion
306+
307+ By ("Get deployment and capture initial annotation" )
308+ dep := & appsv1.Deployment {}
309+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OLSAppServerDeploymentName , Namespace : utils .OLSNamespaceDefault }, dep )
310+ Expect (err ).NotTo (HaveOccurred ())
311+ initialAnnotation := dep .Annotations [utils .OpenShiftMCPServerConfigMapResourceVersionAnnotation ]
312+ Expect (initialAnnotation ).To (Equal (initialResourceVersion ))
313+
314+ By ("Manually modify MCP ConfigMap data (simulating external change)" )
315+ mcpCm .Data ["mcp-server-config.toml" ] = "# Modified externally"
316+ err = k8sClient .Update (ctx , mcpCm )
317+ Expect (err ).NotTo (HaveOccurred ())
318+
319+ By ("Get updated ResourceVersion after manual modification" )
320+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OpenShiftMCPServerConfigCmName , Namespace : utils .OLSNamespaceDefault }, mcpCm )
321+ Expect (err ).NotTo (HaveOccurred ())
322+ modifiedResourceVersion := mcpCm .ResourceVersion
323+ Expect (modifiedResourceVersion ).NotTo (Equal (initialResourceVersion ), "ResourceVersion should change after update" )
324+
325+ By ("Reconcile again to correct the ConfigMap" )
326+ err = ReconcileAppServer (testReconcilerInstance , ctx , olsConfig )
327+ Expect (err ).NotTo (HaveOccurred ())
328+
329+ By ("Verify ConfigMap was corrected and has new ResourceVersion" )
330+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OpenShiftMCPServerConfigCmName , Namespace : utils .OLSNamespaceDefault }, mcpCm )
331+ Expect (err ).NotTo (HaveOccurred ())
332+ correctedResourceVersion := mcpCm .ResourceVersion
333+ Expect (correctedResourceVersion ).NotTo (Equal (modifiedResourceVersion ), "ResourceVersion should change after reconciler correction" )
334+ Expect (mcpCm .Data ["mcp-server-config.toml" ]).NotTo (ContainSubstring ("Modified externally" ), "ConfigMap should be corrected to proper content" )
335+
336+ By ("Verify deployment annotation updated to new ResourceVersion" )
337+ err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .OLSAppServerDeploymentName , Namespace : utils .OLSNamespaceDefault }, dep )
338+ Expect (err ).NotTo (HaveOccurred ())
339+ newAnnotation := dep .Annotations [utils .OpenShiftMCPServerConfigMapResourceVersionAnnotation ]
340+ Expect (newAnnotation ).To (Equal (correctedResourceVersion ), "Deployment annotation should track corrected ConfigMap ResourceVersion" )
341+ Expect (newAnnotation ).NotTo (Equal (initialAnnotation ), "Deployment annotation should change to trigger restart" )
342+
343+ By ("Verify pod template restart annotation is set" )
344+ forceReloadValue := dep .Spec .Template .Annotations [utils .ForceReloadAnnotationKey ]
345+ Expect (forceReloadValue ).NotTo (BeEmpty (), "Pod template restart annotation should be set to trigger rolling update" )
346+ })
347+
222348 It ("should trigger rolling update of the deployment when updating the nodeselector " , func () {
223349 By ("Get the deployment" )
224350 dep := & appsv1.Deployment {}
@@ -413,7 +539,7 @@ var _ = Describe("App server reconciliator", Ordered, func() {
413539
414540 By ("Verify exporter configmap has been deleted" )
415541 err = k8sClient .Get (ctx , types.NamespacedName {Name : utils .ExporterConfigCmName , Namespace : utils .OLSNamespaceDefault }, & corev1.ConfigMap {})
416- Expect (errors .IsNotFound (err )).To (BeTrue ())
542+ Expect (apierrors .IsNotFound (err )).To (BeTrue ())
417543 })
418544
419545 // Note: LLM credential validation is now done in annotateExternalResources
@@ -473,7 +599,7 @@ var _ = Describe("App server reconciliator", Ordered, func() {
473599 By ("Delete the tls secret" )
474600 secretDeletionErr = testReconcilerInstance .Delete (ctx , tlsSecret )
475601 if secretDeletionErr != nil {
476- Expect (errors .IsNotFound (secretDeletionErr )).To (BeTrue ())
602+ Expect (apierrors .IsNotFound (secretDeletionErr )).To (BeTrue ())
477603 } else {
478604 Expect (secretDeletionErr ).NotTo (HaveOccurred ())
479605 }
0 commit comments