🐛 fix flaky e2e cleanup and addon conversion test (#1573)

* 🐛 fix flaky e2e cleanup and addon conversion test

Pre-delete ManagedClusterAddOns and wait for ManifestWorks before
ManagedCluster deletion in CleanKlusterletRelatedResources.

Merge CMA status update and v1beta1 conversion verify into one
Eventually loop to avoid race with cmaInstallProgressionController.

Fixes #1402

Signed-off-by: bot69dude <nrithvik19@gmail.com>

* Wait for ManagedClusterAddOn deletion in cleanup helper

DeleteAllManagedClusterAddOnsInCluster now waits until all addons
are removed before proceeding, so ManifestWork drain is not racing
with still-active addon controllers.

Addresses CodeRabbit review on #1573

Signed-off-by: bot69dude <nrithvik19@gmail.com>

* Address review: use single MCA API version and drop nested RetryOnConflict

Eventually already retries; listing both converted API versions is redundant.

Signed-off-by: bot69dude <nrithvik19@gmail.com>

---------

Signed-off-by: bot69dude <nrithvik19@gmail.com>
This commit is contained in:
bot69dude
2026-08-04 01:17:52 +00:00
committed by GitHub
parent e65f33a1fb
commit 7447b1d0bd
4 changed files with 66 additions and 13 deletions
+12 -12
View File
@@ -464,9 +464,9 @@ var _ = ginkgo.Describe("Create v1alpha1 ClusterManagementAddOn", ginkgo.Label("
_, err := hub.CreateClusterManagementAddOnV1Alpha1(addonName, addon)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
ginkgo.By("Update v1alpha1 ClusterManagementAddOn status using v1alpha1 client")
ginkgo.By("Update v1alpha1 ClusterManagementAddOn status and verify v1beta1 conversion")
gomega.Eventually(func() error {
addon, err = hub.GetClusterManagementAddOnV1Alpha1(addonName)
addon, err := hub.GetClusterManagementAddOnV1Alpha1(addonName)
if err != nil {
return err
}
@@ -485,28 +485,28 @@ var _ = ginkgo.Describe("Create v1alpha1 ClusterManagementAddOn", ginkgo.Label("
}
_, err = hub.AddonClient.AddonV1alpha1().ClusterManagementAddOns().UpdateStatus(
context.Background(), addon, metav1.UpdateOptions{})
return err
}).Should(gomega.Succeed())
if err != nil {
return err
}
ginkgo.By("Get v1alpha1 ClusterManagementAddOn using v1beta1 client and verify conversion")
gomega.Eventually(func() error {
v1beta1Addon, err := hub.GetClusterManagementAddOnV1Beta1(addonName)
if err != nil {
return err
}
// Verify spec.supportedConfigs → spec.defaultConfigs conversion
if len(v1beta1Addon.Spec.DefaultConfigs) != 1 {
return fmt.Errorf("expected 1 default config but got %v", len(v1beta1Addon.Spec.DefaultConfigs))
return fmt.Errorf("expected 1 default config but got %d", len(v1beta1Addon.Spec.DefaultConfigs))
}
if v1beta1Addon.Spec.DefaultConfigs[0].Name != "test-config" {
return fmt.Errorf("expected test-config but got %v", v1beta1Addon.Spec.DefaultConfigs[0].Name)
return fmt.Errorf("expected test-config but got %s", v1beta1Addon.Spec.DefaultConfigs[0].Name)
}
// Verify status.DefaultConfigReferences conversion
if len(v1beta1Addon.Status.DefaultConfigReferences) != 1 {
return fmt.Errorf("expected 1 default config but got %v", len(v1beta1Addon.Status.DefaultConfigReferences))
return fmt.Errorf("expected 1 default config reference but got %d", len(v1beta1Addon.Status.DefaultConfigReferences))
}
if v1beta1Addon.Status.DefaultConfigReferences[0].DesiredConfig == nil {
return fmt.Errorf("expected desired config in status")
}
if v1beta1Addon.Status.DefaultConfigReferences[0].DesiredConfig.Name != "test-config" {
return fmt.Errorf("expected status-config but got %v", v1beta1Addon.Status.DefaultConfigReferences[0].DesiredConfig.Name)
return fmt.Errorf("expected test-config in status but got %s", v1beta1Addon.Status.DefaultConfigReferences[0].DesiredConfig.Name)
}
return nil
}).Should(gomega.Succeed())
+5 -1
View File
@@ -288,12 +288,16 @@ func (spoke *Spoke) RemoveRegistrationFeature(klusterletName string, feature str
return err
}
// CleanKlusterletRelatedResources needs both hub side and spoke side operations
// CleanKlusterletRelatedResources needs both hub side and spoke side operations.
func CleanKlusterletRelatedResources(
hub *Hub, spoke *Spoke,
klusterletName, managedClusterName string) {
Expect(klusterletName).NotTo(Equal(""))
// Remove addons and manifest works first; leftover ManifestWorks (e.g. pre-delete hooks) block ManagedCluster deletion.
hub.DeleteAllManagedClusterAddOnsInCluster(managedClusterName)
hub.WaitUntilNoManifestWorks(managedClusterName)
// clean the managed clusters at first.
err := hub.ClusterClient.ClusterV1().ManagedClusters().Delete(context.TODO(), managedClusterName, metav1.DeleteOptions{})
if apierrors.IsNotFound(err) {
+31
View File
@@ -5,11 +5,14 @@ import (
"fmt"
"time"
. "github.com/onsi/gomega"
coordv1 "k8s.io/api/coordination/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
addonv1beta1 "open-cluster-management.io/api/addon/v1beta1"
@@ -175,3 +178,31 @@ func (hub *Hub) UpdateManagedClusterAddOnV1Alpha1(addon *addonv1alpha1.ManagedCl
return hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(addon.Namespace).Update(
context.TODO(), addon, metav1.UpdateOptions{})
}
// DeleteAllManagedClusterAddOnsInCluster deletes every ManagedClusterAddOn in the cluster namespace.
// A single API version is enough because both versions share the same storage.
func (hub *Hub) DeleteAllManagedClusterAddOnsInCluster(clusterName string) {
addons, err := hub.AddonClient.AddonV1beta1().ManagedClusterAddOns(clusterName).List(context.TODO(), metav1.ListOptions{})
if err != nil && !apierrors.IsNotFound(err) {
klog.Errorf("failed to list managed cluster addons in %s: %v", clusterName, err)
} else {
for _, addon := range addons.Items {
err := hub.AddonClient.AddonV1beta1().ManagedClusterAddOns(clusterName).Delete(
context.TODO(), addon.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
klog.Errorf("failed to delete managed cluster addon %s/%s: %v", clusterName, addon.Name, err)
}
}
}
Eventually(func() error {
addons, err := hub.AddonClient.AddonV1beta1().ManagedClusterAddOns(clusterName).List(context.TODO(), metav1.ListOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
if len(addons.Items) > 0 {
return fmt.Errorf("managed cluster addons still exist in %s", clusterName)
}
return nil
}).Should(Succeed())
}
+18
View File
@@ -2,6 +2,7 @@ package framework
import (
"context"
"fmt"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -24,3 +25,20 @@ func (hub *Hub) CleanManifestWorks(clusterName, workName string) error {
return nil
}
// WaitUntilNoManifestWorks waits until all ManifestWorks are removed from the managed cluster namespace.
func (hub *Hub) WaitUntilNoManifestWorks(clusterName string) {
Eventually(func() error {
works, err := hub.WorkClient.WorkV1().ManifestWorks(clusterName).List(context.Background(), metav1.ListOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return nil
}
return err
}
if len(works.Items) != 0 {
return fmt.Errorf("expected no manifest works, but got %d", len(works.Items))
}
return nil
}).Should(Succeed())
}