Skip to content

Commit 3dedc70

Browse files
committed
Allow control plane provider to set endpoint
Signed-off-by: Vince Prignano <[email protected]>
1 parent 8e8650c commit 3dedc70

File tree

2 files changed

+267
-1
lines changed

2 files changed

+267
-1
lines changed

internal/controllers/cluster/cluster_controller_phases.go

+8
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,14 @@ func (r *Reconciler) reconcileControlPlane(ctx context.Context, cluster *cluster
274274
}
275275
}
276276

277+
// Get and parse Spec.ControlPlaneEndpoint field from the infrastructure provider.
278+
if !cluster.Spec.ControlPlaneEndpoint.IsValid() {
279+
if err := util.UnstructuredUnmarshalField(controlPlaneConfig, &cluster.Spec.ControlPlaneEndpoint, "spec", "controlPlaneEndpoint"); err != nil {
280+
return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve Spec.ControlPlaneEndpoint from control plane provider for Cluster %q in namespace %q",
281+
cluster.Name, cluster.Namespace)
282+
}
283+
}
284+
277285
return ctrl.Result{}, nil
278286
}
279287

internal/controllers/cluster/cluster_controller_phases_test.go

+259-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
3333
capierrors "sigs.k8s.io/cluster-api/errors"
3434
"sigs.k8s.io/cluster-api/internal/test/builder"
35+
"sigs.k8s.io/cluster-api/util/conditions"
3536
)
3637

3738
func TestClusterReconcilePhases(t *testing.T) {
@@ -56,13 +57,30 @@ func TestClusterReconcilePhases(t *testing.T) {
5657
},
5758
},
5859
}
60+
clusterNoEndpoint := &clusterv1.Cluster{
61+
ObjectMeta: metav1.ObjectMeta{
62+
Name: "test-cluster",
63+
Namespace: "test-namespace",
64+
},
65+
Status: clusterv1.ClusterStatus{
66+
InfrastructureReady: true,
67+
},
68+
Spec: clusterv1.ClusterSpec{
69+
InfrastructureRef: &corev1.ObjectReference{
70+
APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
71+
Kind: "GenericInfrastructureMachine",
72+
Name: "test",
73+
},
74+
},
75+
}
5976

6077
tests := []struct {
6178
name string
6279
cluster *clusterv1.Cluster
6380
infraRef map[string]interface{}
6481
expectErr bool
6582
expectResult ctrl.Result
83+
check func(g *GomegaWithT, in *clusterv1.Cluster)
6684
}{
6785
{
6886
name: "returns no error if infrastructure ref is nil",
@@ -104,7 +122,7 @@ func TestClusterReconcilePhases(t *testing.T) {
104122
expectErr: false,
105123
},
106124
{
107-
name: "returns error if infrastructure has the paused annotation",
125+
name: "returns no error if infrastructure has the paused annotation",
108126
cluster: cluster,
109127
infraRef: map[string]interface{}{
110128
"kind": "GenericInfrastructureMachine",
@@ -119,6 +137,50 @@ func TestClusterReconcilePhases(t *testing.T) {
119137
},
120138
expectErr: false,
121139
},
140+
{
141+
name: "returns an error if the control plane endpoint is not yet set",
142+
cluster: clusterNoEndpoint,
143+
infraRef: map[string]interface{}{
144+
"kind": "GenericInfrastructureMachine",
145+
"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
146+
"metadata": map[string]interface{}{
147+
"name": "test",
148+
"namespace": "test-namespace",
149+
"deletionTimestamp": "sometime",
150+
},
151+
"status": map[string]interface{}{
152+
"ready": true,
153+
},
154+
},
155+
expectErr: true,
156+
},
157+
{
158+
name: "should propagate the control plane endpoint once set",
159+
cluster: clusterNoEndpoint,
160+
infraRef: map[string]interface{}{
161+
"kind": "GenericInfrastructureMachine",
162+
"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
163+
"metadata": map[string]interface{}{
164+
"name": "test",
165+
"namespace": "test-namespace",
166+
"deletionTimestamp": "sometime",
167+
},
168+
"spec": map[string]interface{}{
169+
"controlPlaneEndpoint": map[string]interface{}{
170+
"host": "example.com",
171+
"port": int64(6443),
172+
},
173+
},
174+
"status": map[string]interface{}{
175+
"ready": true,
176+
},
177+
},
178+
expectErr: false,
179+
check: func(g *GomegaWithT, in *clusterv1.Cluster) {
180+
g.Expect(in.Spec.ControlPlaneEndpoint.Host).To(Equal("example.com"))
181+
g.Expect(in.Spec.ControlPlaneEndpoint.Port).To(BeEquivalentTo(6443))
182+
},
183+
},
122184
}
123185

124186
for _, tt := range tests {
@@ -149,6 +211,202 @@ func TestClusterReconcilePhases(t *testing.T) {
149211
} else {
150212
g.Expect(err).ToNot(HaveOccurred())
151213
}
214+
215+
if tt.check != nil {
216+
tt.check(g, tt.cluster)
217+
}
218+
})
219+
}
220+
})
221+
222+
t.Run("reconcile control plane ref", func(t *testing.T) {
223+
cluster := &clusterv1.Cluster{
224+
ObjectMeta: metav1.ObjectMeta{
225+
Name: "test-cluster",
226+
Namespace: "test-namespace",
227+
},
228+
Status: clusterv1.ClusterStatus{
229+
InfrastructureReady: true,
230+
},
231+
Spec: clusterv1.ClusterSpec{
232+
ControlPlaneEndpoint: clusterv1.APIEndpoint{
233+
Host: "1.2.3.4",
234+
Port: 8443,
235+
},
236+
ControlPlaneRef: &corev1.ObjectReference{
237+
APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
238+
Kind: "GenericControlPlane",
239+
Name: "test",
240+
},
241+
},
242+
}
243+
clusterNoEndpoint := &clusterv1.Cluster{
244+
ObjectMeta: metav1.ObjectMeta{
245+
Name: "test-cluster",
246+
Namespace: "test-namespace",
247+
},
248+
Status: clusterv1.ClusterStatus{
249+
InfrastructureReady: true,
250+
},
251+
Spec: clusterv1.ClusterSpec{
252+
ControlPlaneRef: &corev1.ObjectReference{
253+
APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
254+
Kind: "GenericControlPlane",
255+
Name: "test",
256+
},
257+
},
258+
}
259+
260+
tests := []struct {
261+
name string
262+
cluster *clusterv1.Cluster
263+
cpRef map[string]interface{}
264+
expectErr bool
265+
expectResult ctrl.Result
266+
check func(g *GomegaWithT, in *clusterv1.Cluster)
267+
}{
268+
{
269+
name: "returns no error if control plane ref is nil",
270+
cluster: &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "test-cluster", Namespace: "test-namespace"}},
271+
expectErr: false,
272+
},
273+
{
274+
name: "returns error if unable to reconcile control plane ref",
275+
cluster: cluster,
276+
expectErr: false,
277+
expectResult: ctrl.Result{RequeueAfter: 30 * time.Second},
278+
},
279+
{
280+
name: "returns no error if infra config is marked for deletion",
281+
cluster: cluster,
282+
cpRef: map[string]interface{}{
283+
"kind": "GenericControlPlane",
284+
"apiVersion": "controlplane.cluster.x-k8s.io/v1beta1",
285+
"metadata": map[string]interface{}{
286+
"name": "test",
287+
"namespace": "test-namespace",
288+
"deletionTimestamp": "sometime",
289+
},
290+
},
291+
expectErr: false,
292+
},
293+
{
294+
name: "returns no error if infrastructure has the paused annotation",
295+
cluster: cluster,
296+
cpRef: map[string]interface{}{
297+
"kind": "GenericControlPlane",
298+
"apiVersion": "controlplane.cluster.x-k8s.io/v1beta1",
299+
"metadata": map[string]interface{}{
300+
"name": "test",
301+
"namespace": "test-namespace",
302+
"annotations": map[string]interface{}{
303+
"cluster.x-k8s.io/paused": "true",
304+
},
305+
},
306+
},
307+
expectErr: false,
308+
},
309+
{
310+
name: "returns an error if the control plane endpoint is not yet set",
311+
cluster: clusterNoEndpoint,
312+
cpRef: map[string]interface{}{
313+
"kind": "GenericControlPlane",
314+
"apiVersion": "controlplane.cluster.x-k8s.io/v1beta1",
315+
"metadata": map[string]interface{}{
316+
"name": "test",
317+
"namespace": "test-namespace",
318+
"deletionTimestamp": "sometime",
319+
},
320+
"status": map[string]interface{}{
321+
"ready": true,
322+
},
323+
},
324+
expectErr: true,
325+
},
326+
{
327+
name: "should propagate the control plane endpoint if set",
328+
cluster: clusterNoEndpoint,
329+
cpRef: map[string]interface{}{
330+
"kind": "GenericControlPlane",
331+
"apiVersion": "controlplane.cluster.x-k8s.io/v1beta1",
332+
"metadata": map[string]interface{}{
333+
"name": "test",
334+
"namespace": "test-namespace",
335+
"deletionTimestamp": "sometime",
336+
},
337+
"spec": map[string]interface{}{
338+
"controlPlaneEndpoint": map[string]interface{}{
339+
"host": "example.com",
340+
"port": int64(6443),
341+
},
342+
},
343+
"status": map[string]interface{}{
344+
"ready": true,
345+
},
346+
},
347+
expectErr: false,
348+
check: func(g *GomegaWithT, in *clusterv1.Cluster) {
349+
g.Expect(in.Spec.ControlPlaneEndpoint.Host).To(Equal("example.com"))
350+
g.Expect(in.Spec.ControlPlaneEndpoint.Port).To(BeEquivalentTo(6443))
351+
},
352+
},
353+
{
354+
name: "should propagate the initialized and ready conditions",
355+
cluster: clusterNoEndpoint,
356+
cpRef: map[string]interface{}{
357+
"kind": "GenericControlPlane",
358+
"apiVersion": "controlplane.cluster.x-k8s.io/v1beta1",
359+
"metadata": map[string]interface{}{
360+
"name": "test",
361+
"namespace": "test-namespace",
362+
"deletionTimestamp": "sometime",
363+
},
364+
"spec": map[string]interface{}{},
365+
"status": map[string]interface{}{
366+
"ready": true,
367+
"initialized": true,
368+
},
369+
},
370+
expectErr: false,
371+
check: func(g *GomegaWithT, in *clusterv1.Cluster) {
372+
g.Expect(conditions.IsTrue(in, clusterv1.ControlPlaneReadyCondition)).To(BeTrue())
373+
g.Expect(conditions.IsTrue(in, clusterv1.ControlPlaneInitializedCondition)).To(BeTrue())
374+
},
375+
},
376+
}
377+
378+
for _, tt := range tests {
379+
t.Run(tt.name, func(t *testing.T) {
380+
g := NewWithT(t)
381+
382+
var c client.Client
383+
if tt.cpRef != nil {
384+
infraConfig := &unstructured.Unstructured{Object: tt.cpRef}
385+
c = fake.NewClientBuilder().
386+
WithObjects(builder.GenericControlPlaneCRD.DeepCopy(), tt.cluster, infraConfig).
387+
Build()
388+
} else {
389+
c = fake.NewClientBuilder().
390+
WithObjects(builder.GenericControlPlaneCRD.DeepCopy(), tt.cluster).
391+
Build()
392+
}
393+
r := &Reconciler{
394+
Client: c,
395+
UnstructuredCachingClient: c,
396+
recorder: record.NewFakeRecorder(32),
397+
}
398+
399+
res, err := r.reconcileControlPlane(ctx, tt.cluster)
400+
g.Expect(res).To(BeComparableTo(tt.expectResult))
401+
if tt.expectErr {
402+
g.Expect(err).To(HaveOccurred())
403+
} else {
404+
g.Expect(err).ToNot(HaveOccurred())
405+
}
406+
407+
if tt.check != nil {
408+
tt.check(g, tt.cluster)
409+
}
152410
})
153411
}
154412
})

0 commit comments

Comments
 (0)