Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/controllers/cloud_config_sync_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
39 changes: 36 additions & 3 deletions pkg/controllers/cloud_config_sync_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Comment on lines +431 to +438

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wait for ConfigMap deletions to be observed before proceeding.

Line 431-438 deletes resources but doesn’t verify they’re gone; this can race with subsequent creates in the same namespace and cause flakes (AlreadyExists/conflicts).

As per coding guidelines, “Operations interacting with clusters must include timeouts” and tests should keep setup/cleanup reliable in BeforeEach/AfterEach.

Suggested fix
 		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())
+			Eventually(func() error {
+				return cl.Get(ctx, client.ObjectKeyFromObject(cm.DeepCopy()), &corev1.ConfigMap{})
+			}, timeout).Should(MatchError(apierrors.IsNotFound, "IsNotFound"))
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/controllers/cloud_config_sync_controller_test.go` around lines 431 - 438,
The code in the deletion loop deletes ConfigMaps but does not wait for the
deletions to be observed before proceeding, which can cause race conditions with
subsequent creates. After the deletion loop that calls cl.Delete() on each
ConfigMap in existingCMs.Items, add an Eventually block that polls to verify all
ConfigMaps have been deleted from the namespace by listing the ConfigMaps again
and confirming the list is empty. This ensures the deletions are fully observed
before the test continues, preventing flaky AlreadyExists or conflict errors.

Source: Coding guidelines


reconciler = &CloudConfigReconciler{
ClusterOperatorStatusClient: ClusterOperatorStatusClient{
Client: cl,
Expand Down Expand Up @@ -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"},
Expand Down Expand Up @@ -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"))
})
})

Expand Down