From 042c11c277d794374cdb349d56884470c364bbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Ma=C5=88=C3=A1k?= Date: Tue, 16 Jun 2026 15:05:57 +0200 Subject: [PATCH] Treat missing cloud config sources as transient. Keep brief source ConfigMap outages on the retry path so CCCMO does not immediately degrade during recovery, while preserving terminal behavior for real bad-key misconfigurations. --- .../cloud_config_sync_controller.go | 2 + .../cloud_config_sync_controller_test.go | 39 +++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/pkg/controllers/cloud_config_sync_controller.go b/pkg/controllers/cloud_config_sync_controller.go index 4ad41e972..df1c9dd36 100644 --- a/pkg/controllers/cloud_config_sync_controller.go +++ b/pkg/controllers/cloud_config_sync_controller.go @@ -187,6 +187,8 @@ func (r *CloudConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) klog.Infof("Initializing minimal config for platform %s", platformType) minimalConfig := getMinimalConfigForPlatform(platformType) sourceCM.Data = map[string]string{defaultConfigKey: minimalConfig} + } else { + return ctrl.Result{}, fmt.Errorf("cloud-config source configmap %s/%s not found", openshiftUnmanagedCMKey.Namespace, openshiftUnmanagedCMKey.Name) } } else if err != nil { klog.Errorf("unable to get cloud-config for sync: %v", err) diff --git a/pkg/controllers/cloud_config_sync_controller_test.go b/pkg/controllers/cloud_config_sync_controller_test.go index a9e370ef9..fba962d47 100644 --- a/pkg/controllers/cloud_config_sync_controller_test.go +++ b/pkg/controllers/cloud_config_sync_controller_test.go @@ -428,6 +428,15 @@ var _ = Describe("Cloud config sync reconciler", func() { targetNamespaceName := testManagedNamespace BeforeEach(func() { + deleteOptions := &client.DeleteOptions{ + GracePeriodSeconds: ptr.To[int64](0), + } + existingCMs := &corev1.ConfigMapList{} + Expect(cl.List(ctx, existingCMs, &client.ListOptions{Namespace: targetNamespaceName})).To(Succeed()) + for _, cm := range existingCMs.Items { + Expect(cl.Delete(ctx, cm.DeepCopy(), deleteOptions)).To(Succeed()) + } + reconciler = &CloudConfigReconciler{ ClusterOperatorStatusClient: ClusterOperatorStatusClient{ Client: cl, @@ -548,6 +557,31 @@ var _ = Describe("Cloud config sync reconciler", func() { Expect(degradedCond.Status).To(Equal(configv1.ConditionTrue)) }) + It("should treat a missing source configmap as transient until it reappears", func() { + Expect(cl.Delete(ctx, makeInfraCloudConfig(configv1.AWSPlatformType))).To(Succeed()) + + infraResource := makeInfrastructureResource(configv1.AWSPlatformType) + Expect(cl.Create(ctx, infraResource)).To(Succeed()) + + infraResource.Status = makeInfraStatus(infraResource.Spec.PlatformSpec.Type) + Expect(cl.Status().Update(ctx, infraResource.DeepCopy())).To(Succeed()) + + _, err := reconciler.Reconcile(context.TODO(), ctrl.Request{}) + Expect(err).To(HaveOccurred()) + + co := &configv1.ClusterOperator{} + Expect(cl.Get(ctx, client.ObjectKey{Name: clusterOperatorName}, co)).To(MatchError(apierrors.IsNotFound, "IsNotFound")) + + Expect(cl.Create(ctx, makeInfraCloudConfig(configv1.AWSPlatformType))).To(Succeed()) + + _, err = reconciler.Reconcile(context.TODO(), ctrl.Request{}) + Expect(err).NotTo(HaveOccurred()) + + Expect(cl.Get(ctx, client.ObjectKey{Name: clusterOperatorName}, co)).To(Succeed()) + Expect(v1helpers.IsStatusConditionTrue(co.Status.Conditions, cloudConfigControllerDegradedCondition)).To(BeFalse()) + Expect(v1helpers.IsStatusConditionTrue(co.Status.Conditions, cloudConfigControllerAvailableCondition)).To(BeTrue()) + }) + It("should continue with reconcile when feature gates are available", func() { reconciler.FeatureGateAccess = featuregates.NewHardcodedFeatureGateAccessForTesting( []configv1.FeatureGateName{"CloudControllerManagerWebhook", "ChocobombVanilla", "ChocobombStrawberry"}, @@ -604,9 +638,8 @@ var _ = Describe("Cloud config sync reconciler", func() { Expect(cl.Status().Update(ctx, infraResource.DeepCopy())).To(Succeed()) _, err := reconciler.Reconcile(context.TODO(), ctrl.Request{}) Expect(err).To(BeNil()) - allCMs := &corev1.ConfigMapList{} - Expect(cl.List(ctx, allCMs, &client.ListOptions{Namespace: targetNamespaceName})).To(Succeed()) - Expect(len(allCMs.Items)).To(BeZero()) + Expect(cl.Get(ctx, client.ObjectKey{Namespace: targetNamespaceName, Name: syncedCloudConfigMapName}, &corev1.ConfigMap{})). + To(MatchError(apierrors.IsNotFound, "IsNotFound")) }) })