Skip to content

Commit 94bfff5

Browse files
authored
Merge pull request kubernetes-sigs#10869 from fabriziopandini/make-clusterresourceset-more-predictable
🌱 Make ClusterResourceSet controller more predictable
2 parents ba6678a + e68d8e7 commit 94bfff5

File tree

4 files changed

+51
-25
lines changed

4 files changed

+51
-25
lines changed

exp/addons/internal/controllers/clusterresourceset_controller.go

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

177177
// Return an aggregated error if errors occurred.
178178
if len(errs) > 0 {
179+
// When there are more than one ClusterResourceSet targeting the same cluster,
180+
// there might be conflict when reconciling those ClusterResourceSet in parallel because they all try to
181+
// patch the same ClusterResourceSetBinding Object.
182+
// In case of patching conflicts we don't want to go on exponential backoff, otherwise it might take an
183+
// arbitrary long time to get to stable state due to the backoff delay quickly growing.
184+
// Instead, we are requeueing with an interval to make the system a little bit more predictable (and stabilize tests).
185+
// NOTE: Conflicts happens mostly when ClusterResourceSetBinding is initialized / an entry is added for each
186+
// cluster resource set targeting the same cluster.
187+
for _, err := range errs {
188+
if aggregate, ok := err.(kerrors.Aggregate); ok {
189+
if len(aggregate.Errors()) == 1 && apierrors.IsConflict(aggregate.Errors()[0]) {
190+
log.Info("Conflict in patching a ClusterResourceSetBinding that is updated by more than one ClusterResourceSet, requeueing")
191+
return ctrl.Result{RequeueAfter: 100 * time.Millisecond}, nil
192+
}
193+
}
194+
}
179195
return ctrl.Result{}, kerrors.NewAggregate(errs)
180196
}
181197

exp/addons/internal/controllers/clusterresourceset_controller_test.go

Lines changed: 30 additions & 21 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

@@ -1031,10 +1036,14 @@ metadata:
10311036
defer teardown(t, g, ns)
10321037

10331038
t.Log("Creating ClusterResourceSet instances that have same labels as selector")
1034-
for range 10 {
1039+
for i := range 10 {
1040+
configmapName := fmt.Sprintf("%s-%d", configmapName, i)
1041+
secretName := fmt.Sprintf("%s-%d", secretName, i)
1042+
createConfigMapAndSecret(g, ns.Name, configmapName, secretName)
1043+
10351044
clusterResourceSetInstance := &addonsv1.ClusterResourceSet{
10361045
ObjectMeta: metav1.ObjectMeta{
1037-
Name: fmt.Sprintf("clusterresourceset-%s", util.RandomString(6)),
1046+
Name: fmt.Sprintf("clusterresourceset-%d", i),
10381047
Namespace: ns.Name,
10391048
},
10401049
Spec: addonsv1.ClusterResourceSetSpec{

exp/addons/internal/controllers/suite_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ import (
3838
)
3939

4040
var (
41-
env *envtest.Environment
42-
ctx = ctrl.SetupSignalHandler()
41+
env *envtest.Environment
42+
tracker *remote.ClusterCacheTracker
43+
ctx = ctrl.SetupSignalHandler()
4344
)
4445

4546
func TestMain(m *testing.M) {
@@ -76,7 +77,7 @@ func TestMain(m *testing.M) {
7677
panic(fmt.Sprintf("Failed to start cache for metadata only Secret watches: %v", err))
7778
}
7879

79-
tracker, err := remote.NewClusterCacheTracker(mgr, remote.ClusterCacheTrackerOptions{})
80+
tracker, err = remote.NewClusterCacheTracker(mgr, remote.ClusterCacheTrackerOptions{})
8081
if err != nil {
8182
panic(fmt.Sprintf("Failed to create new cluster cache tracker: %v", err))
8283
}

internal/test/envtest/environment.go

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

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

389389
// Cleanup deletes all the given objects.

0 commit comments

Comments
 (0)