Skip to content

Commit 55e2bde

Browse files
authored
Merge pull request #8048 from smartxworks/hw/make-sure-kubernetes-service-create-before-apply-crs
🐛: Make sure the Kubernetes API Server service already created on remote cluster before applying ClusterResourceSets
2 parents 88d47b2 + e329f89 commit 55e2bde

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

exp/addons/internal/controllers/clusterresourceset_controller.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,13 @@ func (r *ClusterResourceSetReconciler) ApplyClusterResourceSet(ctx context.Conte
249249
return err
250250
}
251251

252+
// Ensure that the Kubernetes API Server service has been created in the remote cluster before applying the ClusterResourceSet to avoid service IP conflict.
253+
// This action is required when the remote cluster Kubernetes version is lower than v1.25.
254+
// TODO: Remove this action once CAPI no longer supports Kubernetes versions below v1.25. See: https://github.com/kubernetes-sigs/cluster-api/issues/7804
255+
if err = ensureKubernetesServiceCreated(ctx, remoteClient); err != nil {
256+
return errors.Wrapf(err, "failed to retrieve the Service for Kubernetes API Server of the cluster %s/%s", cluster.Namespace, cluster.Name)
257+
}
258+
252259
// Get ClusterResourceSetBinding object for the cluster.
253260
clusterResourceSetBinding, err := r.getOrCreateClusterResourceSetBinding(ctx, cluster, clusterResourceSet)
254261
if err != nil {

exp/addons/internal/controllers/clusterresourceset_controller_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,100 @@ metadata:
910910
g.Expect(env.Delete(ctx, resourceConfigMapWithMissingNamespace)).To(Succeed())
911911
g.Expect(env.Delete(ctx, missingNs)).To(Succeed())
912912
})
913+
914+
t.Run("Should only create ClusterResourceSetBinding after the remote cluster's Kubernetes API Server Service has been created", func(t *testing.T) {
915+
g := NewWithT(t)
916+
ns := setup(t, g)
917+
defer teardown(t, g, ns)
918+
919+
kubernetesAPIServerService := &corev1.Service{
920+
TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"},
921+
ObjectMeta: metav1.ObjectMeta{
922+
Name: "kubernetes",
923+
Namespace: metav1.NamespaceDefault,
924+
},
925+
}
926+
927+
fakeService := &corev1.Service{
928+
TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"},
929+
ObjectMeta: metav1.ObjectMeta{
930+
Name: "fake",
931+
Namespace: metav1.NamespaceDefault,
932+
},
933+
Spec: corev1.ServiceSpec{
934+
Ports: []corev1.ServicePort{
935+
{
936+
Name: "https",
937+
Port: 443,
938+
},
939+
},
940+
Type: "ClusterIP",
941+
},
942+
}
943+
944+
t.Log("Verifying Kubernetes API Server Service has been created")
945+
g.Expect(env.Get(ctx, client.ObjectKeyFromObject(kubernetesAPIServerService), kubernetesAPIServerService)).To(Succeed())
946+
947+
fakeService.Spec.ClusterIP = kubernetesAPIServerService.Spec.ClusterIP
948+
949+
t.Log("Let Kubernetes API Server Service fail to create by occupying its IP")
950+
g.Eventually(func() error {
951+
err := env.Delete(ctx, kubernetesAPIServerService)
952+
if err != nil {
953+
return err
954+
}
955+
err = env.Create(ctx, fakeService)
956+
if err != nil {
957+
return err
958+
}
959+
return nil
960+
}, timeout).Should(Succeed())
961+
g.Expect(apierrors.IsNotFound(env.Get(ctx, client.ObjectKeyFromObject(kubernetesAPIServerService), kubernetesAPIServerService))).To(BeTrue())
962+
963+
clusterResourceSetInstance := &addonsv1.ClusterResourceSet{
964+
ObjectMeta: metav1.ObjectMeta{
965+
Name: clusterResourceSetName,
966+
Namespace: ns.Name,
967+
},
968+
Spec: addonsv1.ClusterResourceSetSpec{
969+
ClusterSelector: metav1.LabelSelector{
970+
MatchLabels: labels,
971+
},
972+
},
973+
}
974+
// Create the ClusterResourceSet.
975+
g.Expect(env.Create(ctx, clusterResourceSetInstance)).To(Succeed())
976+
977+
testCluster.SetLabels(labels)
978+
g.Expect(env.Update(ctx, testCluster)).To(Succeed())
979+
980+
// ClusterResourceSetBinding for the Cluster is not created because the Kubernetes API Server Service doesn't exist.
981+
clusterResourceSetBindingKey := client.ObjectKey{
982+
Namespace: testCluster.Namespace,
983+
Name: testCluster.Name,
984+
}
985+
g.Consistently(func() bool {
986+
binding := &addonsv1.ClusterResourceSetBinding{}
987+
988+
err := env.Get(ctx, clusterResourceSetBindingKey, binding)
989+
return apierrors.IsNotFound(err)
990+
}, timeout).Should(BeTrue())
991+
992+
t.Log("Make sure Kubernetes API Server Service has been created")
993+
g.Expect(env.Delete(ctx, fakeService)).Should(Succeed())
994+
g.Eventually(func() bool {
995+
err := env.Get(ctx, client.ObjectKeyFromObject(kubernetesAPIServerService), kubernetesAPIServerService)
996+
return err == nil
997+
}, timeout).Should(BeTrue())
998+
999+
// Wait until ClusterResourceSetBinding is created for the Cluster
1000+
g.Eventually(func() bool {
1001+
binding := &addonsv1.ClusterResourceSetBinding{}
1002+
1003+
err := env.Get(ctx, clusterResourceSetBindingKey, binding)
1004+
return err == nil
1005+
}, timeout).Should(BeTrue())
1006+
})
9131007
}
9141008

9151009
func clusterResourceSetBindingReady(env *envtest.Environment, cluster *clusterv1.Cluster) func() bool {

exp/addons/internal/controllers/clusterresourceset_helpers.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,15 @@ func getClusterNameFromOwnerRef(obj metav1.ObjectMeta) (string, error) {
250250
}
251251
return "", errors.New("failed to find cluster name in ownerRefs: no cluster ownerRef")
252252
}
253+
254+
// ensureKubernetesServiceCreated ensures that the Service for Kubernetes API Server has been created.
255+
func ensureKubernetesServiceCreated(ctx context.Context, client client.Client) error {
256+
err := client.Get(ctx, types.NamespacedName{
257+
Namespace: metav1.NamespaceDefault,
258+
Name: "kubernetes",
259+
}, &corev1.Service{})
260+
if err != nil {
261+
return err
262+
}
263+
return nil
264+
}

exp/addons/internal/controllers/clusterresourceset_helpers_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
"k8s.io/apimachinery/pkg/runtime"
2828
"k8s.io/apimachinery/pkg/types"
29+
"sigs.k8s.io/controller-runtime/pkg/client"
2930
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3031

3132
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -219,3 +220,54 @@ func TestGetConfigMapFromNamespacedName(t *testing.T) {
219220
})
220221
}
221222
}
223+
224+
func TestEnsureKubernetesServiceCreated(t *testing.T) {
225+
g := NewWithT(t)
226+
227+
scheme := runtime.NewScheme()
228+
g.Expect(corev1.AddToScheme(scheme)).To(Succeed())
229+
230+
kubernetesAPIServerService := &corev1.Service{
231+
TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: "v1"},
232+
ObjectMeta: metav1.ObjectMeta{
233+
Name: "kubernetes",
234+
Namespace: metav1.NamespaceDefault,
235+
},
236+
}
237+
238+
tests := []struct {
239+
name string
240+
existingObjs []client.Object
241+
wantErr bool
242+
}{
243+
{
244+
name: "should return nil when Kubernetes API Server Service exists",
245+
existingObjs: []client.Object{kubernetesAPIServerService},
246+
wantErr: false,
247+
},
248+
{
249+
name: "should return error when Kubernetes API Server Service does not exist",
250+
existingObjs: []client.Object{},
251+
wantErr: true,
252+
},
253+
}
254+
255+
for _, tt := range tests {
256+
t.Run(tt.name, func(t *testing.T) {
257+
gs := NewWithT(t)
258+
259+
c := fake.NewClientBuilder().
260+
WithScheme(scheme).
261+
WithObjects(tt.existingObjs...).
262+
Build()
263+
264+
err := ensureKubernetesServiceCreated(context.TODO(), c)
265+
266+
if tt.wantErr {
267+
gs.Expect(err).To(HaveOccurred())
268+
return
269+
}
270+
gs.Expect(err).NotTo(HaveOccurred())
271+
})
272+
}
273+
}

0 commit comments

Comments
 (0)