Skip to content

Commit fa87a5d

Browse files
authored
Merge pull request #11948 from chrischdi/pr-ensure-v1beta2-conditions-are-true
🐛 ensure kubeadm controller always sets all v1beta2 conditions
2 parents 9f6d384 + dd66072 commit fa87a5d

File tree

4 files changed

+172
-0
lines changed

4 files changed

+172
-0
lines changed

bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,30 @@ func (r *KubeadmConfigReconciler) reconcile(ctx context.Context, scope *Scope, c
312312
Status: metav1.ConditionTrue,
313313
Reason: bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Reason,
314314
})
315+
v1beta2conditions.Set(scope.Config, metav1.Condition{
316+
Type: bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Condition,
317+
Status: metav1.ConditionTrue,
318+
Reason: bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Reason,
319+
})
315320
return ctrl.Result{}, nil
316321
// Status is ready means a config has been generated.
322+
// This also solves the upgrade scenario to a version which includes v1beta2 to ensure v1beta2 conditions are properly set.
317323
case config.Status.Ready:
324+
// Based on existing code paths status.Ready is only true if status.dataSecretName is set
325+
// So we can assume that the DataSecret is available.
326+
conditions.MarkTrue(config, bootstrapv1.DataSecretAvailableCondition)
327+
v1beta2conditions.Set(scope.Config, metav1.Condition{
328+
Type: bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Condition,
329+
Status: metav1.ConditionTrue,
330+
Reason: bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Reason,
331+
})
332+
// Same applies for the CertificatesAvailable, which must have been the case to generate
333+
// the DataSecret.
334+
v1beta2conditions.Set(scope.Config, metav1.Condition{
335+
Type: bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Condition,
336+
Status: metav1.ConditionTrue,
337+
Reason: bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Reason,
338+
})
318339
if config.Spec.JoinConfiguration != nil && config.Spec.JoinConfiguration.Discovery.BootstrapToken != nil {
319340
if !configOwner.HasNodeRefs() {
320341
// 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
@@ -46,6 +46,7 @@ import (
4646
"sigs.k8s.io/cluster-api/util"
4747
"sigs.k8s.io/cluster-api/util/certs"
4848
"sigs.k8s.io/cluster-api/util/conditions"
49+
v1beta2conditions "sigs.k8s.io/cluster-api/util/conditions/v1beta2"
4950
"sigs.k8s.io/cluster-api/util/patch"
5051
"sigs.k8s.io/cluster-api/util/secret"
5152
"sigs.k8s.io/cluster-api/util/test/builder"
@@ -2744,3 +2745,96 @@ func assertHasTrueCondition(g *WithT, myclient client.Client, req ctrl.Request,
27442745
g.Expect(c).ToNot(BeNil())
27452746
g.Expect(c.Status).To(Equal(corev1.ConditionTrue))
27462747
}
2748+
2749+
func TestKubeadmConfigReconciler_Reconcile_v1beta2_conditions(t *testing.T) {
2750+
// Setup work for an initialized cluster
2751+
clusterName := "my-cluster"
2752+
cluster := builder.Cluster(metav1.NamespaceDefault, clusterName).Build()
2753+
conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
2754+
cluster.Status.InfrastructureReady = true
2755+
cluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{
2756+
Host: "example.com",
2757+
Port: 6443,
2758+
}
2759+
2760+
machine := builder.Machine(metav1.NamespaceDefault, "my-machine").
2761+
WithVersion("v1.19.1").
2762+
WithClusterName(cluster.Name).
2763+
WithBootstrapTemplate(bootstrapbuilder.KubeadmConfig(metav1.NamespaceDefault, "").Unstructured()).
2764+
Build()
2765+
2766+
kubeadmConfig := newKubeadmConfig(metav1.NamespaceDefault, "kubeadmconfig")
2767+
2768+
tests := []struct {
2769+
name string
2770+
config *bootstrapv1.KubeadmConfig
2771+
machine *clusterv1.Machine
2772+
}{
2773+
{
2774+
name: "conditions should be true again after reconciling",
2775+
config: kubeadmConfig.DeepCopy(),
2776+
machine: machine.DeepCopy(),
2777+
},
2778+
{
2779+
name: "conditions should be true again after status got emptied out",
2780+
config: kubeadmConfig.DeepCopy(),
2781+
machine: func() *clusterv1.Machine {
2782+
m := machine.DeepCopy()
2783+
m.Spec.Bootstrap.DataSecretName = ptr.To("foo")
2784+
return m
2785+
}(),
2786+
},
2787+
{
2788+
name: "conditions should be true after upgrading to v1beta2",
2789+
config: func() *bootstrapv1.KubeadmConfig {
2790+
c := kubeadmConfig.DeepCopy()
2791+
c.Status.Ready = true
2792+
return c
2793+
}(),
2794+
machine: machine.DeepCopy(),
2795+
},
2796+
}
2797+
for _, tt := range tests {
2798+
t.Run(tt.name, func(t *testing.T) {
2799+
g := NewWithT(t)
2800+
cluster := cluster.DeepCopy()
2801+
tt.config.SetOwnerReferences([]metav1.OwnerReference{{
2802+
APIVersion: clusterv1.GroupVersion.String(),
2803+
Kind: "Machine",
2804+
Name: tt.machine.Name,
2805+
}})
2806+
2807+
objects := []client.Object{cluster, tt.machine, tt.config}
2808+
objects = append(objects, createSecrets(t, cluster, tt.config)...)
2809+
2810+
myclient := fake.NewClientBuilder().WithObjects(objects...).WithStatusSubresource(&bootstrapv1.KubeadmConfig{}).Build()
2811+
2812+
r := &KubeadmConfigReconciler{
2813+
Client: myclient,
2814+
SecretCachingClient: myclient,
2815+
ClusterCache: clustercache.NewFakeClusterCache(myclient, client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}),
2816+
KubeadmInitLock: &myInitLocker{},
2817+
}
2818+
2819+
key := client.ObjectKey{Namespace: tt.config.Namespace, Name: tt.config.Name}
2820+
_, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key})
2821+
g.Expect(err).ToNot(HaveOccurred())
2822+
2823+
newConfig := &bootstrapv1.KubeadmConfig{}
2824+
g.Expect(myclient.Get(ctx, key, newConfig)).To(Succeed())
2825+
2826+
for _, conditionType := range []string{bootstrapv1.KubeadmConfigReadyV1Beta2Condition, bootstrapv1.KubeadmConfigCertificatesAvailableV1Beta2Condition, bootstrapv1.KubeadmConfigDataSecretAvailableV1Beta2Condition} {
2827+
condition := v1beta2conditions.Get(newConfig, conditionType)
2828+
g.Expect(condition).ToNot(BeNil(), "condition %s is missing", conditionType)
2829+
g.Expect(condition.Status).To(Equal(metav1.ConditionTrue))
2830+
g.Expect(condition.Message).To(BeEmpty())
2831+
}
2832+
for _, conditionType := range []string{clusterv1.PausedV1Beta2Condition} {
2833+
condition := v1beta2conditions.Get(newConfig, conditionType)
2834+
g.Expect(condition).ToNot(BeNil(), "condition %s is missing", conditionType)
2835+
g.Expect(condition.Status).To(Equal(metav1.ConditionFalse))
2836+
g.Expect(condition.Message).To(BeEmpty())
2837+
}
2838+
})
2839+
}
2840+
}

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.
@@ -705,6 +706,10 @@ func ClusterctlUpgradeSpec(ctx context.Context, inputGetter func() ClusterctlUpg
705706
upgrade.PostUpgrade(managementClusterProxy, workloadCluster.Namespace, workloadCluster.Name)
706707
}
707708

709+
Byf("[%d] Verify v1beta2 Available and Ready conditions (if exist) to be true for Cluster and Machines", i)
710+
verifyV1Beta2ConditionsTrue(ctx, managementClusterProxy.GetClient(), workloadCluster.Name, workloadCluster.Namespace,
711+
[]string{clusterv1.AvailableV1Beta2Condition, clusterv1.ReadyV1Beta2Condition})
712+
708713
Byf("[%d] Verify client-side SSA still works", i)
709714
clusterUpdate := &unstructured.Unstructured{}
710715
clusterUpdate.SetGroupVersionKind(clusterv1.GroupVersion.WithKind("Cluster"))
@@ -767,6 +772,38 @@ func ClusterctlUpgradeSpec(ctx context.Context, inputGetter func() ClusterctlUpg
767772
})
768773
}
769774

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

test/infrastructure/docker/internal/controllers/backends/docker/dockermachine_backend.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,26 @@ func (r *MachineBackendReconciler) ReconcileNormal(ctx context.Context, cluster
126126
Status: metav1.ConditionTrue,
127127
Reason: infrav1.DevMachineDockerContainerProvisionedV1Beta2Reason,
128128
})
129+
// In case of upgrades the v1beta2 condition for BootstrapExecSucceeded does not exist.
130+
// In this case recover the information from the existing v1beta1 condition, because we do not know if
131+
// all commands succeeded.
132+
if !v1beta2conditions.Has(dockerMachine, infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition) {
133+
condition := conditions.Get(dockerMachine, infrav1.BootstrapExecSucceededCondition)
134+
if condition == nil || condition.Status == corev1.ConditionTrue {
135+
v1beta2conditions.Set(dockerMachine, metav1.Condition{
136+
Type: infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
137+
Status: metav1.ConditionTrue,
138+
Reason: infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Reason,
139+
})
140+
} else {
141+
v1beta2conditions.Set(dockerMachine, metav1.Condition{
142+
Type: infrav1.DevMachineDockerContainerBootstrapExecSucceededV1Beta2Condition,
143+
Status: metav1.ConditionFalse,
144+
Message: condition.Message,
145+
Reason: infrav1.DevMachineDockerContainerBootstrapExecNotSucceededV1Beta2Reason,
146+
})
147+
}
148+
}
129149

130150
// Setting machine address is required after move, because status.Address field is not retained during move.
131151
if err := setMachineAddress(ctx, dockerMachine, externalMachine); err != nil {

0 commit comments

Comments
 (0)