Skip to content

Commit ce256bf

Browse files
authored
Merge pull request #11953 from chrischdi/pr-cp-11948
[release-1.9] 🐛 ensure kubeadm controller always sets all v1beta2 conditions
2 parents a491076 + 97a2a22 commit ce256bf

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,30 @@ func (r *KubeadmConfigReconciler) reconcile(ctx context.Context, scope *Scope, c
317317
Status: metav1.ConditionTrue,
318318
Reason: bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Reason,
319319
})
320+
v1beta2conditions.Set(scope.Config, metav1.Condition{
321+
Type: bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Condition,
322+
Status: metav1.ConditionTrue,
323+
Reason: bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Reason,
324+
})
320325
return ctrl.Result{}, nil
321326
// Status is ready means a config has been generated.
327+
// This also solves the upgrade scenario to a version which includes v1beta2 to ensure v1beta2 conditions are properly set.
322328
case config.Status.Ready:
329+
// Based on existing code paths status.Ready is only true if status.dataSecretName is set
330+
// So we can assume that the DataSecret is available.
331+
conditions.MarkTrue(config, bootstrapv1.DataSecretAvailableCondition)
332+
v1beta2conditions.Set(scope.Config, metav1.Condition{
333+
Type: bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Condition,
334+
Status: metav1.ConditionTrue,
335+
Reason: bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Reason,
336+
})
337+
// Same applies for the CertificatesAvailable, which must have been the case to generate
338+
// the DataSecret.
339+
v1beta2conditions.Set(scope.Config, metav1.Condition{
340+
Type: bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Condition,
341+
Status: metav1.ConditionTrue,
342+
Reason: bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Reason,
343+
})
323344
if config.Spec.JoinConfiguration != nil && config.Spec.JoinConfiguration.Discovery.BootstrapToken != nil {
324345
if !configOwner.HasNodeRefs() {
325346
// If the BootstrapToken has been generated for a join but the config owner has no nodeRefs,

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
"sigs.k8s.io/cluster-api/util"
4646
"sigs.k8s.io/cluster-api/util/certs"
4747
"sigs.k8s.io/cluster-api/util/conditions"
48+
v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
4849
"sigs.k8s.io/cluster-api/util/patch"
4950
"sigs.k8s.io/cluster-api/util/secret"
5051
"sigs.k8s.io/cluster-api/util/test/builder"
@@ -2594,3 +2595,96 @@ func assertHasTrueCondition(g *WithT, myclient client.Client, req ctrl.Request,
25942595
g.Expect(c).ToNot(BeNil())
25952596
g.Expect(c.Status).To(Equal(corev1.ConditionTrue))
25962597
}
2598+
2599+
func TestKubeadmConfigReconciler_Reconcile_v1beta2_conditions(t *testing.T) {
2600+
// Setup work for an initialized cluster
2601+
clusterName := "my-cluster"
2602+
cluster := builder.Cluster(metav1.NamespaceDefault, clusterName).Build()
2603+
conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
2604+
cluster.Status.InfrastructureReady = true
2605+
cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{
2606+
Host: "example.com",
2607+
Port: 6443,
2608+
}
2609+
2610+
machine := builder.Machine(metav1.NamespaceDefault, "my-machine").
2611+
WithVersion("v1.19.1").
2612+
WithClusterName(cluster.Name).
2613+
WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "").Unstructured()).
2614+
Build()
2615+
2616+
kubeadmConfig := newKubeadmConfig(metav1.NamespaceDefault, "kubeadmconfig")
2617+
2618+
tests := []struct {
2619+
name string
2620+
config *bootstrapv1.KubeadmConfig
2621+
machine *clusterv1.Machine
2622+
}{
2623+
{
2624+
name: "conditions should be true again after reconciling",
2625+
config: kubeadmConfig.DeepCopy(),
2626+
machine: machine.DeepCopy(),
2627+
},
2628+
{
2629+
name: "conditions should be true again after status got emptied out",
2630+
config: kubeadmConfig.DeepCopy(),
2631+
machine: func() *clusterv1.Machine {
2632+
m := machine.DeepCopy()
2633+
m.Spec.Bootstrap.DataSecretName = ptr.To("foo")
2634+
return m
2635+
}(),
2636+
},
2637+
{
2638+
name: "conditions should be true after upgrading to v1beta2",
2639+
config: func() *bootstrapv1.KubeadmConfig {
2640+
c := kubeadmConfig.DeepCopy()
2641+
c.Status.Ready = true
2642+
return c
2643+
}(),
2644+
machine: machine.DeepCopy(),
2645+
},
2646+
}
2647+
for _, tt := range tests {
2648+
t.Run(tt.name, func(t *testing.T) {
2649+
g := NewWithT(t)
2650+
cluster := cluster.DeepCopy()
2651+
tt.config.SetOwnerReferences([]metav1.OwnerReference{{
2652+
APIVersion: clusterv1.GroupVersion.String(),
2653+
Kind: "Machine",
2654+
Name: tt.machine.Name,
2655+
}})
2656+
2657+
objects := []client.Object{cluster, tt.machine, tt.config}
2658+
objects = append(objects, createSecrets(t, cluster, tt.config)...)
2659+
2660+
myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
2661+
2662+
r := &KubeadmConfigReconciler{
2663+
Client: myclient,
2664+
SecretCachingClient: myclient,
2665+
ClusterCache: clustercache.NewFakeClusterCache(myclient, client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
2666+
KubeadmInitLock: &myInitLocker{},
2667+
}
2668+
2669+
key := client.ObjectKey{Namespace: tt.config.Namespace, Name: tt.config.Name}
2670+
_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key})
2671+
g.Expect(err).ToNot(HaveOccurred())
2672+
2673+
newConfig := &bootstrapv1.KubeadmConfig{}
2674+
g.Expect(myclient.Get(ctx, key, newConfig)).To(Succeed())
2675+
2676+
for _, conditionType := range []string{bootstrapv1.KubeadmConfigReadyV1Beta2Condition, bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Condition, bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Condition} {
2677+
condition := v1beta2conditions.Get(newConfig, conditionType)
2678+
g.Expect(condition).ToNot(BeNil(), "condition %s is missing", conditionType)
2679+
g.Expect(condition.Status).To(Equal(metav1.ConditionTrue))
2680+
g.Expect(condition.Message).To(BeEmpty())
2681+
}
2682+
for _, conditionType := range []string{clusterv1.PausedV1Beta2Condition} {
2683+
condition := v1beta2conditions.Get(newConfig, conditionType)
2684+
g.Expect(condition).ToNot(BeNil(), "condition %s is missing", conditionType)
2685+
g.Expect(condition.Status).To(Equal(metav1.ConditionFalse))
2686+
g.Expect(condition.Message).To(BeEmpty())
2687+
}
2688+
})
2689+
}
2690+
}

test/e2e/clusterctl_upgrade.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import (
5353
"sigs.k8s.io/cluster-api/test/framework/bootstrap"
5454
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
5555
"sigs.k8s.io/cluster-api/util"
56+
v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
5657
)
5758

5859
// ClusterctlUpgradeSpecInput is the input for ClusterctlUpgradeSpec.
@@ -698,6 +699,10 @@ func ClusterctlUpgradeSpec(ctx context.Context, inputGetter func() ClusterctlUpg
698699
}
699700
}
700701

702+
Byf("[%d] Verify v1beta2 Available and Ready conditions (if exist) to be true for Cluster and Machines", i)
703+
verifyV1Beta2ConditionsTrue(ctx, managementClusterProxy.GetClient(), workloadCluster.Name, workloadCluster.Namespace,
704+
[]string{clusterv1.AvailableV1Beta2Condition, clusterv1.ReadyV1Beta2Condition})
705+
701706
Byf("[%d] Verify client-side SSA still works", i)
702707
clusterUpdate := &unstructured.Unstructured{}
703708
clusterUpdate.SetGroupVersionKind(clusterv1.GroupVersion.WithKind("Cluster"))
@@ -760,6 +765,38 @@ func ClusterctlUpgradeSpec(ctx context.Context, inputGetter func() ClusterctlUpg
760765
})
761766
}
762767

768+
// verifyV1Beta2ConditionsTrue checks the Cluster and Machines of a Cluster that
769+
// the given v1beta2 condition types are set to true without a message, if they exist.
770+
func verifyV1Beta2ConditionsTrue(ctx context.Context, c client.Client, clusterName, clusterNamespace string, v1beta2conditionTypes []string) {
771+
cluster := framework.GetClusterByName(ctx, framework.GetClusterByNameInput{
772+
Getter: c,
773+
Name: clusterName,
774+
Namespace: clusterNamespace,
775+
})
776+
for _, conditionType := range v1beta2conditionTypes {
777+
if v1beta2conditions.Has(cluster, conditionType) {
778+
condition := v1beta2conditions.Get(cluster, conditionType)
779+
Expect(condition.Status).To(Equal(metav1.ConditionTrue), "The v1beta2 condition %q on the Cluster should be set to true", conditionType)
780+
Expect(condition.Message).To(BeEmpty(), "The v1beta2 condition %q on the Cluster should have an empty message", conditionType)
781+
}
782+
}
783+
784+
machines := framework.GetMachinesByCluster(ctx, framework.GetMachinesByClusterInput{
785+
Lister: c,
786+
ClusterName: clusterName,
787+
Namespace: clusterNamespace,
788+
})
789+
for _, machine := range machines {
790+
for _, conditionType := range v1beta2conditionTypes {
791+
if v1beta2conditions.Has(&machine, conditionType) {
792+
condition := v1beta2conditions.Get(&machine, conditionType)
793+
Expect(condition.Status).To(Equal(metav1.ConditionTrue), "The v1beta2 condition %q on the Machine %q should be set to true", conditionType, machine.Name)
794+
Expect(condition.Message).To(BeEmpty(), "The v1beta2 condition %q on the Machine %q should have an empty message", conditionType, machine.Name)
795+
}
796+
}
797+
}
798+
}
799+
763800
func setupClusterctl(ctx context.Context, clusterctlBinaryURL, clusterctlConfigPath string) (string, string) {
764801
clusterctlBinaryPath := downloadToTmpFile(ctx, clusterctlBinaryURL)
765802

0 commit comments

Comments
 (0)