Skip to content
This repository was archived by the owner on Jun 2, 2026. It is now read-only.

Commit 55722b7

Browse files
k8s-ci-robotdlipovetsky
authored andcommitted
🌱 Make ClusterResourceSet controller more predictable
1 parent 06745b9 commit 55722b7

4 files changed

Lines changed: 51 additions & 24 deletions

File tree

exp/addons/internal/controllers/clusterresourceset_controller.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,22 @@ func (r *ClusterResourceSetReconciler) Reconcile(ctx context.Context, req ctrl.R
161161

162162
// Return an aggregated error if errors occurred.
163163
if len(errs) > 0 {
164+
// When there are more than one ClusterResourceSet targeting the same cluster,
165+
// there might be conflict when reconciling those ClusterResourceSet in parallel because they all try to
166+
// patch the same ClusterResourceSetBinding Object.
167+
// In case of patching conflicts we don't want to go on exponential backoff, otherwise it might take an
168+
// arbitrary long time to get to stable state due to the backoff delay quickly growing.
169+
// Instead, we are requeueing with an interval to make the system a little bit more predictable (and stabilize tests).
170+
// NOTE: Conflicts happens mostly when ClusterResourceSetBinding is initialized / an entry is added for each
171+
// cluster resource set targeting the same cluster.
172+
for _, err := range errs {
173+
if aggregate, ok := err.(kerrors.Aggregate); ok {
174+
if len(aggregate.Errors()) == 1 && apierrors.IsConflict(aggregate.Errors()[0]) {
175+
log.Info("Conflict in patching a ClusterResourceSetBinding that is updated by more than one ClusterResourceSet, requeueing")
176+
return ctrl.Result{RequeueAfter: 100 * time.Millisecond}, nil
177+
}
178+
}
179+
}
164180
return ctrl.Result{}, kerrors.NewAggregate(errs)
165181
}
166182

exp/addons/internal/controllers/clusterresourceset_controller_test.go

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,7 @@ func TestClusterResourceSetReconciler(t *testing.T) {
5656
resourceConfigMapsNamespace = "default"
5757
)
5858

59-
setup := func(t *testing.T, g *WithT) *corev1.Namespace {
60-
t.Helper()
61-
62-
clusterResourceSetName = fmt.Sprintf("clusterresourceset-%s", util.RandomString(6))
63-
labels = map[string]string{clusterResourceSetName: "bar"}
64-
65-
ns, err := env.CreateNamespace(ctx, namespacePrefix)
66-
g.Expect(err).ToNot(HaveOccurred())
67-
68-
clusterName = fmt.Sprintf("cluster-%s", util.RandomString(6))
69-
testCluster = &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: ns.Name}}
70-
71-
t.Log("Creating the Cluster")
72-
g.Expect(env.Create(ctx, testCluster)).To(Succeed())
73-
t.Log("Creating the remote Cluster kubeconfig")
74-
g.Expect(env.CreateKubeconfigSecret(ctx, testCluster)).To(Succeed())
75-
59+
createConfigMapAndSecret := func(g Gomega, namespaceName, configmapName, secretName string) {
7660
resourceConfigMap1Content := fmt.Sprintf(`metadata:
7761
name: %s
7862
namespace: %s
@@ -82,7 +66,7 @@ apiVersion: v1`, resourceConfigMap1Name, resourceConfigMapsNamespace)
8266
testConfigmap := &corev1.ConfigMap{
8367
ObjectMeta: metav1.ObjectMeta{
8468
Name: configmapName,
85-
Namespace: ns.Name,
69+
Namespace: namespaceName,
8670
},
8771
Data: map[string]string{
8872
"cm": resourceConfigMap1Content,
@@ -98,7 +82,7 @@ metadata:
9882
testSecret := &corev1.Secret{
9983
ObjectMeta: metav1.ObjectMeta{
10084
Name: secretName,
101-
Namespace: ns.Name,
85+
Namespace: namespaceName,
10286
},
10387
Type: "addons.cluster.x-k8s.io/resource-set",
10488
StringData: map[string]string{
@@ -108,7 +92,28 @@ metadata:
10892
t.Log("Creating a Secret and a ConfigMap with ConfigMap in their data field")
10993
g.Expect(env.Create(ctx, testConfigmap)).To(Succeed())
11094
g.Expect(env.Create(ctx, testSecret)).To(Succeed())
95+
}
11196

97+
setup := func(t *testing.T, g *WithT) *corev1.Namespace {
98+
t.Helper()
99+
100+
clusterResourceSetName = fmt.Sprintf("clusterresourceset-%s", util.RandomString(6))
101+
labels = map[string]string{clusterResourceSetName: "bar"}
102+
103+
ns, err := env.CreateNamespace(ctx, namespacePrefix)
104+
g.Expect(err).ToNot(HaveOccurred())
105+
106+
clusterName = fmt.Sprintf("cluster-%s", util.RandomString(6))
107+
testCluster = &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: ns.Name}}
108+
109+
t.Log("Creating the Cluster")
110+
g.Expect(env.CreateAndWait(ctx, testCluster)).To(Succeed())
111+
t.Log("Creating the remote Cluster kubeconfig")
112+
g.Expect(env.CreateKubeconfigSecret(ctx, testCluster)).To(Succeed())
113+
_, err = tracker.GetClient(ctx, client.ObjectKeyFromObject(testCluster))
114+
g.Expect(err).ToNot(HaveOccurred())
115+
116+
createConfigMapAndSecret(g, ns.Name, configmapName, secretName)
112117
return ns
113118
}
114119

@@ -1013,9 +1018,13 @@ metadata:
10131018

10141019
t.Log("Creating ClusterResourceSet instances that have same labels as selector")
10151020
for i := 0; i < 10; i++ {
1021+
configmapName := fmt.Sprintf("%s-%d", configmapName, i)
1022+
secretName := fmt.Sprintf("%s-%d", secretName, i)
1023+
createConfigMapAndSecret(g, ns.Name, configmapName, secretName)
1024+
10161025
clusterResourceSetInstance := &addonsv1.ClusterResourceSet{
10171026
ObjectMeta: metav1.ObjectMeta{
1018-
Name: fmt.Sprintf("clusterresourceset-%s", util.RandomString(6)),
1027+
Name: fmt.Sprintf("clusterresourceset-%d", i),
10191028
Namespace: ns.Name,
10201029
},
10211030
Spec: addonsv1.ClusterResourceSetSpec{

exp/addons/internal/controllers/suite_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ import (
3434
)
3535

3636
var (
37-
env *envtest.Environment
38-
ctx = ctrl.SetupSignalHandler()
37+
env *envtest.Environment
38+
tracker *remote.ClusterCacheTracker
39+
ctx = ctrl.SetupSignalHandler()
3940
)
4041

4142
func TestMain(m *testing.M) {
@@ -46,7 +47,8 @@ func TestMain(m *testing.M) {
4647
}
4748

4849
setupReconcilers := func(ctx context.Context, mgr ctrl.Manager) {
49-
tracker, err := remote.NewClusterCacheTracker(mgr, remote.ClusterCacheTrackerOptions{})
50+
var err error
51+
tracker, err = remote.NewClusterCacheTracker(mgr, remote.ClusterCacheTrackerOptions{})
5052
if err != nil {
5153
panic(fmt.Sprintf("Failed to create new cluster cache tracker: %v", err))
5254
}

internal/test/envtest/environment.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ func (e *Environment) waitForWebhooks() {
384384

385385
// CreateKubeconfigSecret generates a new Kubeconfig secret from the envtest config.
386386
func (e *Environment) CreateKubeconfigSecret(ctx context.Context, cluster *clusterv1.Cluster) error {
387-
return e.Create(ctx, kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(e.Config, cluster)))
387+
return e.CreateAndWait(ctx, kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(e.Config, cluster)))
388388
}
389389

390390
// Cleanup deletes all the given objects.

0 commit comments

Comments
 (0)