Skip to content

Commit 7f2096e

Browse files
authored
Merge pull request #302 from anmazzotti/set_control_plane_initialized
Check rke2-serving secret to determine controlPlane.Status.Initialized
2 parents 13060bc + e44db86 commit 7f2096e

File tree

3 files changed

+162
-9
lines changed

3 files changed

+162
-9
lines changed

controlplane/internal/controllers/rke2controlplane_controller.go

+27-7
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,16 @@ func (r *RKE2ControlPlaneReconciler) updateStatus(ctx context.Context, rcp *cont
369369
rcp.Status.ReadyReplicas = int32(len(readyMachines))
370370
rcp.Status.UnavailableReplicas = replicas - rcp.Status.ReadyReplicas
371371

372-
if rcp.Status.ReadyReplicas > 0 {
372+
workloadCluster, err := r.getWorkloadCluster(ctx, util.ObjectKey(cluster))
373+
if err != nil {
374+
logger.Error(err, "Failed to get remote client for workload cluster", "cluster key", util.ObjectKey(cluster))
375+
376+
return fmt.Errorf("getting workload cluster: %w", err)
377+
}
378+
379+
status := workloadCluster.ClusterStatus(ctx)
380+
381+
if status.HasRKE2ServingSecret {
373382
rcp.Status.Initialized = true
374383
}
375384

@@ -790,11 +799,11 @@ func (r *RKE2ControlPlaneReconciler) reconcileControlPlaneConditions(
790799
return ctrl.Result{}, nil
791800
}
792801

793-
workloadCluster, err := r.managementCluster.GetWorkloadCluster(ctx, util.ObjectKey(controlPlane.Cluster))
802+
workloadCluster, err := r.getWorkloadCluster(ctx, util.ObjectKey(controlPlane.Cluster))
794803
if err != nil {
795-
logger.Error(err, "Unable to get Workload cluster")
804+
logger.Error(err, "Failed to get remote client for workload cluster", "cluster key", util.ObjectKey(controlPlane.Cluster))
796805

797-
return ctrl.Result{}, errors.Wrap(err, "cannot get remote client to workload cluster")
806+
return ctrl.Result{}, fmt.Errorf("getting workload cluster: %w", err)
798807
}
799808

800809
defer func() {
@@ -842,11 +851,11 @@ func (r *RKE2ControlPlaneReconciler) upgradeControlPlane(
842851
return ctrl.Result{}, nil
843852
}
844853

845-
workloadCluster, err := r.managementCluster.GetWorkloadCluster(ctx, util.ObjectKey(cluster))
854+
workloadCluster, err := r.getWorkloadCluster(ctx, util.ObjectKey(cluster))
846855
if err != nil {
847-
logger.Error(err, "failed to get remote client for workload cluster", "cluster key", util.ObjectKey(cluster))
856+
logger.Error(err, "Failed to get remote client for workload cluster", "cluster key", util.ObjectKey(cluster))
848857

849-
return ctrl.Result{}, err
858+
return ctrl.Result{}, fmt.Errorf("getting workload cluster: %w", err)
850859
}
851860

852861
if err := workloadCluster.InitWorkload(ctx, controlPlane); err != nil {
@@ -892,3 +901,14 @@ func (r *RKE2ControlPlaneReconciler) ClusterToRKE2ControlPlane(ctx context.Conte
892901
return nil
893902
}
894903
}
904+
905+
// getWorkloadCluster gets a cluster object.
906+
// The cluster comes with an etcd client generator to connect to any etcd pod living on a managed machine.
907+
func (r *RKE2ControlPlaneReconciler) getWorkloadCluster(ctx context.Context, clusterKey types.NamespacedName) (rke2.WorkloadCluster, error) {
908+
workloadCluster, err := r.managementCluster.GetWorkloadCluster(ctx, clusterKey)
909+
if err != nil {
910+
return nil, fmt.Errorf("getting remote client for workload cluster: %w", err)
911+
}
912+
913+
return workloadCluster, nil
914+
}

pkg/rke2/workload_cluster.go

+20-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/pkg/errors"
2929
corev1 "k8s.io/api/core/v1"
3030
apierrors "k8s.io/apimachinery/pkg/api/errors"
31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132
kerrors "k8s.io/apimachinery/pkg/util/errors"
3233
"k8s.io/apimachinery/pkg/util/sets"
3334
"k8s.io/client-go/rest"
@@ -51,6 +52,7 @@ const (
5152
etcdDialTimeout = 10 * time.Second
5253
etcdCallTimeout = 15 * time.Second
5354
minimalNodeCount = 2
55+
rke2ServingSecretKey = "rke2-serving" //nolint: gosec
5456
)
5557

5658
// ErrControlPlaneMinNodes is returned when the control plane has fewer than 2 nodes.
@@ -62,7 +64,7 @@ type WorkloadCluster interface {
6264
InitWorkload(ctx context.Context, controlPlane *ControlPlane) error
6365
UpdateNodeMetadata(ctx context.Context, controlPlane *ControlPlane) error
6466

65-
ClusterStatus() ClusterStatus
67+
ClusterStatus(ctx context.Context) ClusterStatus
6668
UpdateAgentConditions(controlPlane *ControlPlane)
6769
UpdateEtcdConditions(controlPlane *ControlPlane)
6870
// Upgrade related tasks.
@@ -196,6 +198,8 @@ type ClusterStatus struct {
196198
Nodes int32
197199
// ReadyNodes are the count of nodes that are reporting ready
198200
ReadyNodes int32
201+
// HasRKE2ServingSecret will be true if the rke2-serving secret has been uploaded, false otherwise.
202+
HasRKE2ServingSecret bool
199203
}
200204

201205
func (w *Workload) getControlPlaneNodes(ctx context.Context) (*corev1.NodeList, error) {
@@ -253,7 +257,7 @@ func (w *Workload) PatchNodes(ctx context.Context, cp *ControlPlane) error {
253257
}
254258

255259
// ClusterStatus returns the status of the cluster.
256-
func (w *Workload) ClusterStatus() ClusterStatus {
260+
func (w *Workload) ClusterStatus(ctx context.Context) ClusterStatus {
257261
status := ClusterStatus{}
258262

259263
// count the control plane nodes
@@ -266,6 +270,20 @@ func (w *Workload) ClusterStatus() ClusterStatus {
266270
}
267271
}
268272

273+
// find the rke2-serving secret
274+
key := ctrlclient.ObjectKey{
275+
Name: rke2ServingSecretKey,
276+
Namespace: metav1.NamespaceSystem,
277+
}
278+
err := w.Client.Get(ctx, key, &corev1.Secret{})
279+
// In case of error we do assume the control plane is not initialized yet.
280+
if err != nil {
281+
logger := log.FromContext(ctx)
282+
logger.Info("Control Plane does not seem to be initialized yet.", "reason", err.Error())
283+
}
284+
285+
status.HasRKE2ServingSecret = err == nil
286+
269287
return status
270288
}
271289

pkg/rke2/workload_cluster_test.go

+115
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package rke2
22

33
import (
4+
"context"
5+
46
. "github.com/onsi/ginkgo/v2"
57
. "github.com/onsi/gomega"
8+
"github.com/pkg/errors"
69
bootstrapv1 "github.com/rancher-sandbox/cluster-api-provider-rke2/bootstrap/api/v1beta1"
710
controlplanev1 "github.com/rancher-sandbox/cluster-api-provider-rke2/controlplane/api/v1beta1"
811
corev1 "k8s.io/api/core/v1"
@@ -12,6 +15,8 @@ import (
1215
"sigs.k8s.io/cluster-api/util/collections"
1316
"sigs.k8s.io/cluster-api/util/conditions"
1417
"sigs.k8s.io/controller-runtime/pkg/client"
18+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
19+
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
1520
)
1621

1722
var _ = Describe("Node metadata propagation", func() {
@@ -394,3 +399,113 @@ var _ = Describe("Cloud-init fields validation", func() {
394399
}})).ToNot(Succeed())
395400
})
396401
})
402+
403+
var _ = Describe("ClusterStatus validation", func() {
404+
var (
405+
err error
406+
ns *corev1.Namespace
407+
node1 *corev1.Node
408+
node2 *corev1.Node
409+
servingSecret *corev1.Secret
410+
connectionErr interceptor.Funcs
411+
)
412+
413+
BeforeEach(func() {
414+
ns, err = testEnv.CreateNamespace(ctx, "ns")
415+
Expect(err).ToNot(HaveOccurred())
416+
417+
node1 = &corev1.Node{
418+
ObjectMeta: metav1.ObjectMeta{
419+
Name: "node1",
420+
Labels: map[string]string{
421+
"node-role.kubernetes.io/master": "true",
422+
},
423+
Annotations: map[string]string{
424+
clusterv1.MachineAnnotation: "node1",
425+
},
426+
},
427+
Status: corev1.NodeStatus{
428+
Conditions: []corev1.NodeCondition{{
429+
Type: corev1.NodeReady,
430+
Status: corev1.ConditionTrue,
431+
}},
432+
}}
433+
434+
node2 = &corev1.Node{
435+
ObjectMeta: metav1.ObjectMeta{
436+
Name: "node2",
437+
Labels: map[string]string{
438+
"node-role.kubernetes.io/master": "true",
439+
},
440+
Annotations: map[string]string{
441+
clusterv1.MachineAnnotation: "node2",
442+
},
443+
},
444+
Status: corev1.NodeStatus{
445+
Conditions: []corev1.NodeCondition{{
446+
Type: corev1.NodeReady,
447+
Status: corev1.ConditionFalse,
448+
}},
449+
}}
450+
451+
servingSecret = &corev1.Secret{
452+
ObjectMeta: metav1.ObjectMeta{
453+
Name: rke2ServingSecretKey,
454+
Namespace: metav1.NamespaceSystem,
455+
},
456+
}
457+
458+
connectionErr = interceptor.Funcs{
459+
Get: func(ctx context.Context, client client.WithWatch, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error {
460+
return errors.New("test connection error")
461+
},
462+
}
463+
})
464+
465+
AfterEach(func() {
466+
testEnv.Cleanup(ctx, ns)
467+
})
468+
469+
It("should set HasRKE2ServingSecret if servingSecret found", func() {
470+
fakeClient := fake.NewClientBuilder().WithObjects([]client.Object{servingSecret}...).Build()
471+
w := &Workload{
472+
Client: fakeClient,
473+
Nodes: map[string]*corev1.Node{
474+
node1.Name: node1,
475+
node2.Name: node2,
476+
},
477+
}
478+
status := w.ClusterStatus(ctx)
479+
Expect(status.Nodes).To(BeEquivalentTo(2), "There are 2 nodes in this cluster")
480+
Expect(status.ReadyNodes).To(BeEquivalentTo(1), "Only 1 node has the NodeReady condition")
481+
Expect(status.HasRKE2ServingSecret).To(BeTrue(), "rke2-serving Secret exists in kube-system namespace")
482+
})
483+
It("should not set HasRKE2ServingSecret if servingSecret not existing", func() {
484+
fakeClient := fake.NewClientBuilder().Build()
485+
w := &Workload{
486+
Client: fakeClient,
487+
Nodes: map[string]*corev1.Node{
488+
node1.Name: node1,
489+
node2.Name: node2,
490+
},
491+
}
492+
status := w.ClusterStatus(ctx)
493+
Expect(status.Nodes).To(BeEquivalentTo(2), "There are 2 nodes in this cluster")
494+
Expect(status.ReadyNodes).To(BeEquivalentTo(1), "Only 1 node has the NodeReady condition")
495+
Expect(status.HasRKE2ServingSecret).To(BeFalse(), "rke2-serving Secret does not exists in kube-system namespace")
496+
})
497+
It("should not set HasRKE2ServingSecret if client returns error", func() {
498+
fakeClient := fake.NewClientBuilder().WithInterceptorFuncs(connectionErr).Build()
499+
w := &Workload{
500+
Client: fakeClient,
501+
Nodes: map[string]*corev1.Node{
502+
node1.Name: node1,
503+
node2.Name: node2,
504+
},
505+
}
506+
status := w.ClusterStatus(ctx)
507+
Expect(status.Nodes).To(BeEquivalentTo(2), "There are 2 nodes in this cluster")
508+
Expect(status.ReadyNodes).To(BeEquivalentTo(1), "Only 1 node has the NodeReady condition")
509+
Expect(status.HasRKE2ServingSecret).To(BeFalse(), "On connection error assume control plane not initialized")
510+
})
511+
})

0 commit comments

Comments
 (0)