From 5c133a1d59067e2143472967c74d0a59afea0d8a Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Wed, 16 Apr 2025 14:10:44 +0200 Subject: [PATCH] Add initialization to MachinePool Status # Conflicts: # bootstrap/util/configowner.go --- .../kubeadmconfig_controller_test.go | 2 +- bootstrap/util/configowner.go | 11 +--- bootstrap/util/configowner_test.go | 8 +-- .../bases/cluster.x-k8s.io_machinepools.yaml | 25 ++++++--- exp/api/v1beta1/conversion.go | 15 ++++++ exp/api/v1beta1/conversion_test.go | 7 +++ exp/api/v1beta1/zz_generated.conversion.go | 7 ++- exp/api/v1beta2/machinepool_types.go | 29 ++++++++--- exp/api/v1beta2/zz_generated.deepcopy.go | 20 +++++++ .../machinepool_controller_phases.go | 32 ++++++++---- .../machinepool_controller_phases_test.go | 52 +++++++++++-------- internal/apis/core/exp/v1alpha3/conversion.go | 15 ++++++ .../apis/core/exp/v1alpha3/conversion_test.go | 8 +++ .../exp/v1alpha3/zz_generated.conversion.go | 7 ++- internal/apis/core/exp/v1alpha4/conversion.go | 15 ++++++ .../apis/core/exp/v1alpha4/conversion_test.go | 8 +++ .../exp/v1alpha4/zz_generated.conversion.go | 7 ++- 17 files changed, 197 insertions(+), 71 deletions(-) diff --git a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go index 170410740c8a..807422a5b8a6 100644 --- a/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go +++ b/bootstrap/kubeadm/internal/controllers/kubeadmconfig_controller_test.go @@ -1369,7 +1369,7 @@ func TestBootstrapTokenRotationMachinePool(t *testing.T) { patchHelper, err := patch.NewHelper(workerMachinePool, myclient) g.Expect(err).ShouldNot(HaveOccurred()) - workerMachinePool.Status.InfrastructureReady = true + workerMachinePool.Status.Initialization = &expv1.MachinePoolInitializationStatus{InfrastructureProvisioned: true} g.Expect(patchHelper.Patch(ctx, workerMachinePool, patch.WithStatusObservedGeneration{})).To(Succeed()) result, err = k.Reconcile(ctx, request) diff --git a/bootstrap/util/configowner.go b/bootstrap/util/configowner.go index 9fb5a1017f31..3574dc12a818 100644 --- a/bootstrap/util/configowner.go +++ b/bootstrap/util/configowner.go @@ -40,15 +40,8 @@ type ConfigOwner struct { *unstructured.Unstructured } -// IsInfrastructureReady extracts infrastructure status from the config owner. -func (co ConfigOwner) IsInfrastructureReady() bool { - if co.IsMachinePool() { - infrastructureReady, _, err := unstructured.NestedBool(co.Object, "status", "infrastructureReady") - if err != nil { - return false - } - return infrastructureReady - } +// IsInfrastructureProvisioned extracts infrastructure status from the config owner. +func (co ConfigOwner) IsInfrastructureProvisioned() bool { infrastructureReady, _, err := unstructured.NestedBool(co.Object, "status", "initialization", "infrastructureProvisioned") if err != nil { return false diff --git a/bootstrap/util/configowner_test.go b/bootstrap/util/configowner_test.go index f6fd5d31bdb1..fe9847c4ed16 100644 --- a/bootstrap/util/configowner_test.go +++ b/bootstrap/util/configowner_test.go @@ -82,7 +82,7 @@ func TestGetConfigOwner(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) g.Expect(configOwner).ToNot(BeNil()) g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster")) - g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue()) + g.Expect(configOwner.IsInfrastructureProvisioned()).To(BeTrue()) g.Expect(configOwner.IsControlPlaneMachine()).To(BeTrue()) g.Expect(configOwner.IsMachinePool()).To(BeFalse()) g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6")) @@ -110,7 +110,9 @@ func TestGetConfigOwner(t *testing.T) { }, }, Status: expv1.MachinePoolStatus{ - InfrastructureReady: true, + Initialization: &expv1.MachinePoolInitializationStatus{ + InfrastructureProvisioned: true, + }, }, } @@ -132,7 +134,7 @@ func TestGetConfigOwner(t *testing.T) { g.Expect(err).ToNot(HaveOccurred()) g.Expect(configOwner).ToNot(BeNil()) g.Expect(configOwner.ClusterName()).To(BeEquivalentTo("my-cluster")) - g.Expect(configOwner.IsInfrastructureReady()).To(BeTrue()) + g.Expect(configOwner.IsInfrastructureProvisioned()).To(BeTrue()) g.Expect(configOwner.IsControlPlaneMachine()).To(BeFalse()) g.Expect(configOwner.IsMachinePool()).To(BeTrue()) g.Expect(configOwner.KubernetesVersion()).To(Equal("v1.19.6")) diff --git a/config/crd/bases/cluster.x-k8s.io_machinepools.yaml b/config/crd/bases/cluster.x-k8s.io_machinepools.yaml index 7eef5764aede..053cca1a8ce5 100644 --- a/config/crd/bases/cluster.x-k8s.io_machinepools.yaml +++ b/config/crd/bases/cluster.x-k8s.io_machinepools.yaml @@ -1956,9 +1956,6 @@ spec: Available condition is true. format: int32 type: integer - bootstrapReady: - description: bootstrapReady is the state of the bootstrap provider. - type: boolean conditions: description: |- conditions represents the observations of a MachinePool's current state. @@ -2130,10 +2127,24 @@ spec: type: integer type: object type: object - infrastructureReady: - description: infrastructureReady is the state of the infrastructure - provider. - type: boolean + initialization: + description: |- + initialization provides observations of the MachinePool initialization process. + NOTE: Fields in this struct are part of the Cluster API contract and are used to orchestrate initial MachinePool provisioning. + properties: + bootstrapDataSecretCreated: + description: |- + bootstrapDataSecretCreated is true when the bootstrap provider reports that the MachinePool's boostrap secret is created. + NOTE: this field is part of the Cluster API contract, and it is used to orchestrate provisioning. + The value of this field is never updated after provisioning is completed. + type: boolean + infrastructureProvisioned: + description: |- + infrastructureProvisioned is true when the infrastructure provider reports that MachinePool's infrastructure is fully provisioned. + NOTE: this field is part of the Cluster API contract, and it is used to orchestrate provisioning. + The value of this field is never updated after provisioning is completed. + type: boolean + type: object nodeRefs: description: nodeRefs will point to the corresponding Nodes if it they exist. diff --git a/exp/api/v1beta1/conversion.go b/exp/api/v1beta1/conversion.go index 1876a06a9e4a..6e2609d93abf 100644 --- a/exp/api/v1beta1/conversion.go +++ b/exp/api/v1beta1/conversion.go @@ -64,6 +64,12 @@ func Convert_v1beta2_MachinePoolStatus_To_v1beta1_MachinePoolStatus(in *expv1.Ma out.UnavailableReplicas = in.Deprecated.V1Beta1.UnavailableReplicas } + // Move initialization to old fields + if in.Initialization != nil { + out.BootstrapReady = in.Initialization.BootstrapDataSecretCreated + out.InfrastructureReady = in.Initialization.InfrastructureProvisioned + } + // Move new conditions (v1beta2) and replica counters to the v1beta2 field. if in.Conditions == nil && in.ReadyReplicas == nil && in.AvailableReplicas == nil && in.UpToDateReplicas == nil { return nil @@ -98,6 +104,15 @@ func Convert_v1beta1_MachinePoolStatus_To_v1beta2_MachinePoolStatus(in *MachineP out.UpToDateReplicas = in.V1Beta2.UpToDateReplicas } + // Move BootstrapReady and InfrastructureReady to Initialization + if in.BootstrapReady || in.InfrastructureReady { + if out.Initialization == nil { + out.Initialization = &expv1.MachinePoolInitializationStatus{} + } + out.Initialization.BootstrapDataSecretCreated = in.BootstrapReady + out.Initialization.InfrastructureProvisioned = in.InfrastructureReady + } + // Move legacy conditions (v1beta1), failureReason, failureMessage and replica counters to the deprecated field. if out.Deprecated == nil { out.Deprecated = &expv1.MachinePoolDeprecatedStatus{} diff --git a/exp/api/v1beta1/conversion_test.go b/exp/api/v1beta1/conversion_test.go index 6168cc8ebb60..86f8146d3359 100644 --- a/exp/api/v1beta1/conversion_test.go +++ b/exp/api/v1beta1/conversion_test.go @@ -56,6 +56,13 @@ func hubMachinePoolStatus(in *expv1.MachinePoolStatus, c fuzz.Continue) { if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &expv1.MachinePoolV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &expv1.MachinePoolInitializationStatus{}) { + in.Initialization = nil + } + } } func spokeMachinePoolStatus(in *MachinePoolStatus, c fuzz.Continue) { diff --git a/exp/api/v1beta1/zz_generated.conversion.go b/exp/api/v1beta1/zz_generated.conversion.go index 44f26bddf029..31ab13aa0076 100644 --- a/exp/api/v1beta1/zz_generated.conversion.go +++ b/exp/api/v1beta1/zz_generated.conversion.go @@ -224,8 +224,8 @@ func autoConvert_v1beta1_MachinePoolStatus_To_v1beta2_MachinePoolStatus(in *Mach // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type // WARNING: in.FailureMessage requires manual conversion: does not exist in peer-type out.Phase = in.Phase - out.BootstrapReady = in.BootstrapReady - out.InfrastructureReady = in.InfrastructureReady + // WARNING: in.BootstrapReady requires manual conversion: does not exist in peer-type + // WARNING: in.InfrastructureReady requires manual conversion: does not exist in peer-type out.ObservedGeneration = in.ObservedGeneration if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions @@ -254,6 +254,7 @@ func autoConvert_v1beta2_MachinePoolStatus_To_v1beta1_MachinePoolStatus(in *v1be } else { out.Conditions = nil } + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.NodeRefs = *(*[]corev1.ObjectReference)(unsafe.Pointer(&in.NodeRefs)) out.Replicas = in.Replicas if err := v1.Convert_Pointer_int32_To_int32(&in.ReadyReplicas, &out.ReadyReplicas, s); err != nil { @@ -264,8 +265,6 @@ func autoConvert_v1beta2_MachinePoolStatus_To_v1beta1_MachinePoolStatus(in *v1be } // WARNING: in.UpToDateReplicas requires manual conversion: does not exist in peer-type out.Phase = in.Phase - out.BootstrapReady = in.BootstrapReady - out.InfrastructureReady = in.InfrastructureReady out.ObservedGeneration = in.ObservedGeneration // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type return nil diff --git a/exp/api/v1beta2/machinepool_types.go b/exp/api/v1beta2/machinepool_types.go index a22ba14c6f59..735fba745d34 100644 --- a/exp/api/v1beta2/machinepool_types.go +++ b/exp/api/v1beta2/machinepool_types.go @@ -86,6 +86,11 @@ type MachinePoolStatus struct { // +kubebuilder:validation:MaxItems=32 Conditions []metav1.Condition `json:"conditions,omitempty"` + // initialization provides observations of the MachinePool initialization process. + // NOTE: Fields in this struct are part of the Cluster API contract and are used to orchestrate initial MachinePool provisioning. + // +optional + Initialization *MachinePoolInitializationStatus `json:"initialization,omitempty"` + // nodeRefs will point to the corresponding Nodes if it they exist. // +optional // +kubebuilder:validation:MaxItems=10000 @@ -112,14 +117,6 @@ type MachinePoolStatus struct { // +kubebuilder:validation:Enum=Pending;Provisioning;Provisioned;Running;ScalingUp;ScalingDown;Scaling;Deleting;Failed;Unknown Phase string `json:"phase,omitempty"` - // bootstrapReady is the state of the bootstrap provider. - // +optional - BootstrapReady bool `json:"bootstrapReady"` - - // infrastructureReady is the state of the infrastructure provider. - // +optional - InfrastructureReady bool `json:"infrastructureReady"` - // observedGeneration is the latest generation observed by the controller. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty"` @@ -129,6 +126,22 @@ type MachinePoolStatus struct { Deprecated *MachinePoolDeprecatedStatus `json:"deprecated,omitempty"` } +// MachinePoolInitializationStatus provides observations of the MachinePool initialization process. +// NOTE: Fields in this struct are part of the Cluster API contract and are used to orchestrate initial MachinePool provisioning. +type MachinePoolInitializationStatus struct { + // infrastructureProvisioned is true when the infrastructure provider reports that MachinePool's infrastructure is fully provisioned. + // NOTE: this field is part of the Cluster API contract, and it is used to orchestrate provisioning. + // The value of this field is never updated after provisioning is completed. + // +optional + InfrastructureProvisioned bool `json:"infrastructureProvisioned"` + + // bootstrapDataSecretCreated is true when the bootstrap provider reports that the MachinePool's boostrap secret is created. + // NOTE: this field is part of the Cluster API contract, and it is used to orchestrate provisioning. + // The value of this field is never updated after provisioning is completed. + // +optional + BootstrapDataSecretCreated bool `json:"bootstrapDataSecretCreated"` +} + // MachinePoolDeprecatedStatus groups all the status fields that are deprecated and will be removed in a future version. // See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context. type MachinePoolDeprecatedStatus struct { diff --git a/exp/api/v1beta2/zz_generated.deepcopy.go b/exp/api/v1beta2/zz_generated.deepcopy.go index 96a588d0275b..8ac7af8383ce 100644 --- a/exp/api/v1beta2/zz_generated.deepcopy.go +++ b/exp/api/v1beta2/zz_generated.deepcopy.go @@ -75,6 +75,21 @@ func (in *MachinePoolDeprecatedStatus) DeepCopy() *MachinePoolDeprecatedStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachinePoolInitializationStatus) DeepCopyInto(out *MachinePoolInitializationStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachinePoolInitializationStatus. +func (in *MachinePoolInitializationStatus) DeepCopy() *MachinePoolInitializationStatus { + if in == nil { + return nil + } + out := new(MachinePoolInitializationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MachinePoolList) DeepCopyInto(out *MachinePoolList) { *out = *in @@ -153,6 +168,11 @@ func (in *MachinePoolStatus) DeepCopyInto(out *MachinePoolStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Initialization != nil { + in, out := &in.Initialization, &out.Initialization + *out = new(MachinePoolInitializationStatus) + **out = **in + } if in.NodeRefs != nil { in, out := &in.NodeRefs, &out.NodeRefs *out = make([]corev1.ObjectReference, len(*in)) diff --git a/exp/internal/controllers/machinepool_controller_phases.go b/exp/internal/controllers/machinepool_controller_phases.go index b2365e05ad4c..26279c7ce8a1 100644 --- a/exp/internal/controllers/machinepool_controller_phases.go +++ b/exp/internal/controllers/machinepool_controller_phases.go @@ -60,7 +60,7 @@ func (r *MachinePoolReconciler) reconcilePhase(mp *expv1.MachinePool) { } // Set the phase to "provisioning" if bootstrap is ready and the infrastructure isn't. - if mp.Status.BootstrapReady && !mp.Status.InfrastructureReady { + if mp.Status.Initialization != nil && mp.Status.Initialization.BootstrapDataSecretCreated && !mp.Status.Initialization.InfrastructureProvisioned { mp.Status.SetTypedPhase(expv1.MachinePoolPhaseProvisioning) } @@ -75,12 +75,12 @@ func (r *MachinePoolReconciler) reconcilePhase(mp *expv1.MachinePool) { if mp.Status.Deprecated != nil && mp.Status.Deprecated.V1Beta1 != nil { readyReplicas = mp.Status.Deprecated.V1Beta1.ReadyReplicas } - if mp.Status.InfrastructureReady && mp.Spec.Replicas != nil && *mp.Spec.Replicas == readyReplicas { + if mp.Status.Initialization != nil && mp.Status.Initialization.InfrastructureProvisioned && mp.Spec.Replicas != nil && *mp.Spec.Replicas == readyReplicas { mp.Status.SetTypedPhase(expv1.MachinePoolPhaseRunning) } // Set the appropriate phase in response to the MachinePool replica count being greater than the observed infrastructure replicas. - if mp.Status.InfrastructureReady && mp.Spec.Replicas != nil && *mp.Spec.Replicas > readyReplicas { + if mp.Status.Initialization != nil && mp.Status.Initialization.InfrastructureProvisioned && mp.Spec.Replicas != nil && *mp.Spec.Replicas > readyReplicas { // If we are being managed by an external autoscaler and can't predict scaling direction, set to "Scaling". if annotations.ReplicasManagedByExternalAutoscaler(mp) { mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScaling) @@ -91,7 +91,7 @@ func (r *MachinePoolReconciler) reconcilePhase(mp *expv1.MachinePool) { } // Set the appropriate phase in response to the MachinePool replica count being less than the observed infrastructure replicas. - if mp.Status.InfrastructureReady && mp.Spec.Replicas != nil && *mp.Spec.Replicas < readyReplicas { + if mp.Status.Initialization != nil && mp.Status.Initialization.InfrastructureProvisioned && mp.Spec.Replicas != nil && *mp.Spec.Replicas < readyReplicas { // If we are being managed by an external autoscaler and can't predict scaling direction, set to "Scaling". if annotations.ReplicasManagedByExternalAutoscaler(mp) { mp.Status.SetTypedPhase(expv1.MachinePoolPhaseScaling) @@ -231,7 +231,10 @@ func (r *MachinePoolReconciler) reconcileBootstrap(ctx context.Context, s *scope if !dataSecretCreated { log.Info("Waiting for bootstrap provider to generate data secret and report status.ready", bootstrapConfig.GetKind(), klog.KObj(bootstrapConfig)) - m.Status.BootstrapReady = dataSecretCreated + if m.Status.Initialization == nil { + m.Status.Initialization = &expv1.MachinePoolInitializationStatus{} + } + m.Status.Initialization.BootstrapDataSecretCreated = dataSecretCreated return ctrl.Result{}, nil } @@ -244,13 +247,19 @@ func (r *MachinePoolReconciler) reconcileBootstrap(ctx context.Context, s *scope } m.Spec.Template.Spec.Bootstrap.DataSecretName = secretName - m.Status.BootstrapReady = true + if m.Status.Initialization == nil { + m.Status.Initialization = &expv1.MachinePoolInitializationStatus{} + } + m.Status.Initialization.BootstrapDataSecretCreated = true return ctrl.Result{}, nil } // If dataSecretName is set without a ConfigRef, this means the user brought their own bootstrap data. if m.Spec.Template.Spec.Bootstrap.DataSecretName != nil { - m.Status.BootstrapReady = true + if m.Status.Initialization == nil { + m.Status.Initialization = &expv1.MachinePoolInitializationStatus{} + } + m.Status.Initialization.BootstrapDataSecretCreated = true v1beta1conditions.MarkTrue(m, clusterv1.BootstrapReadyV1Beta1Condition) return ctrl.Result{}, nil } @@ -269,7 +278,7 @@ func (r *MachinePoolReconciler) reconcileInfrastructure(ctx context.Context, s * if err != nil { if apierrors.IsNotFound(errors.Cause(err)) { log.Error(err, "infrastructure reference could not be found") - if mp.Status.InfrastructureReady { + if mp.Status.Initialization != nil && mp.Status.Initialization.InfrastructureProvisioned { // Infra object went missing after the machine pool was up and running log.Error(err, "infrastructure reference has been deleted after being ready, setting failure state") if mp.Status.Deprecated == nil { @@ -297,7 +306,10 @@ func (r *MachinePoolReconciler) reconcileInfrastructure(ctx context.Context, s * return ctrl.Result{}, err } - mp.Status.InfrastructureReady = ready + if mp.Status.Initialization == nil { + mp.Status.Initialization = &expv1.MachinePoolInitializationStatus{} + } + mp.Status.Initialization.InfrastructureProvisioned = ready // Report a summary of current status of the infrastructure object defined for this machine pool. v1beta1conditions.SetMirror(mp, clusterv1.InfrastructureReadyV1Beta1Condition, @@ -320,7 +332,7 @@ func (r *MachinePoolReconciler) reconcileInfrastructure(ctx context.Context, s * return ctrl.Result{}, kerrors.NewAggregate([]error{errors.Wrapf(err, "failed to reconcile Machines for MachinePool %s", klog.KObj(mp)), errors.Wrapf(getNodeRefsErr, "failed to get nodeRefs for MachinePool %s", klog.KObj(mp))}) } - if !mp.Status.InfrastructureReady { + if mp.Status.Initialization == nil && !mp.Status.Initialization.InfrastructureProvisioned { log.Info("Infrastructure provider is not yet ready", infraConfig.GetKind(), klog.KObj(infraConfig)) return ctrl.Result{}, nil } diff --git a/exp/internal/controllers/machinepool_controller_phases_test.go b/exp/internal/controllers/machinepool_controller_phases_test.go index 778d227af235..a98fcbe09e90 100644 --- a/exp/internal/controllers/machinepool_controller_phases_test.go +++ b/exp/internal/controllers/machinepool_controller_phases_test.go @@ -708,7 +708,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { r.reconcilePhase(machinePool) g.Expect(*machinePool.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data-new")) - g.Expect(machinePool.Status.BootstrapReady).To(BeTrue()) + g.Expect(machinePool.Status.Initialization != nil && machinePool.Status.Initialization.BootstrapDataSecretCreated).To(BeTrue()) g.Expect(machinePool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) }) @@ -810,7 +810,7 @@ func TestReconcileMachinePoolPhases(t *testing.T) { // The old secret should still be used, as the new bootstrap config is not marked ready g.Expect(*machinePool.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data")) - g.Expect(machinePool.Status.BootstrapReady).To(BeFalse()) + g.Expect(machinePool.Status.Initialization != nil && machinePool.Status.Initialization.BootstrapDataSecretCreated).To(BeFalse()) // There is no phase defined for "changing to new bootstrap config", so it should still be `Running` the // old configuration @@ -877,7 +877,7 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, expectError: false, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.BootstrapReady).To(BeTrue()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.BootstrapDataSecretCreated).To(BeTrue()) g.Expect(m.Spec.Template.Spec.Bootstrap.DataSecretName).ToNot(BeNil()) g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(ContainSubstring("secret-data")) }, @@ -900,7 +900,7 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, expectError: true, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.BootstrapReady).To(BeFalse()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.BootstrapDataSecretCreated).To(BeFalse()) g.Expect(m.Spec.Template.Spec.Bootstrap.DataSecretName).To(BeNil()) }, }, @@ -919,7 +919,7 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { expectError: false, expectResult: ctrl.Result{}, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.BootstrapReady).To(BeFalse()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.BootstrapDataSecretCreated).To(BeFalse()) }, }, { @@ -936,7 +936,7 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, expectError: true, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.BootstrapReady).To(BeFalse()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.BootstrapDataSecretCreated).To(BeFalse()) }, }, { @@ -991,12 +991,14 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, }, Status: expv1.MachinePoolStatus{ - BootstrapReady: true, + Initialization: &expv1.MachinePoolInitializationStatus{ + BootstrapDataSecretCreated: true, + }, }, }, expectError: false, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.BootstrapReady).To(BeTrue()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.BootstrapDataSecretCreated).To(BeTrue()) g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data")) }, }, @@ -1032,12 +1034,14 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, }, Status: expv1.MachinePoolStatus{ - BootstrapReady: true, + Initialization: &expv1.MachinePoolInitializationStatus{ + BootstrapDataSecretCreated: true, + }, }, }, expectError: false, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.BootstrapReady).To(BeTrue()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.BootstrapDataSecretCreated).To(BeTrue()) g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("data")) }, }, @@ -1079,13 +1083,15 @@ func TestReconcileMachinePoolBootstrap(t *testing.T) { }, }, Status: expv1.MachinePoolStatus{ - BootstrapReady: false, + Initialization: &expv1.MachinePoolInitializationStatus{ + BootstrapDataSecretCreated: false, + }, }, }, expectError: false, expectResult: ctrl.Result{}, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.BootstrapReady).To(BeFalse()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.BootstrapDataSecretCreated).To(BeFalse()) }, }, } @@ -1209,7 +1215,7 @@ func TestReconcileMachinePoolInfrastructure(t *testing.T) { expectError: false, expectChanged: true, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.InfrastructureReady).To(BeTrue()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.InfrastructureProvisioned).To(BeTrue()) }, }, { @@ -1241,9 +1247,11 @@ func TestReconcileMachinePoolInfrastructure(t *testing.T) { }, }, Status: expv1.MachinePoolStatus{ - BootstrapReady: true, - InfrastructureReady: true, - NodeRefs: []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}, + Initialization: &expv1.MachinePoolInitializationStatus{ + InfrastructureProvisioned: true, + BootstrapDataSecretCreated: true, + }, + NodeRefs: []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}, }, }, bootstrapConfig: map[string]interface{}{ @@ -1269,7 +1277,7 @@ func TestReconcileMachinePoolInfrastructure(t *testing.T) { expectError: true, expectRequeueAfter: false, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.InfrastructureReady).To(BeTrue()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.InfrastructureProvisioned).To(BeTrue()) g.Expect(m.Status.Deprecated.V1Beta1.FailureMessage).ToNot(BeNil()) g.Expect(m.Status.Deprecated.V1Beta1.FailureReason).ToNot(BeNil()) g.Expect(m.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseFailed)) @@ -1304,9 +1312,11 @@ func TestReconcileMachinePoolInfrastructure(t *testing.T) { }, }, Status: expv1.MachinePoolStatus{ - BootstrapReady: true, - InfrastructureReady: true, - NodeRefs: []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}, + Initialization: &expv1.MachinePoolInitializationStatus{ + InfrastructureProvisioned: true, + BootstrapDataSecretCreated: true, + }, + NodeRefs: []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}, }, }, bootstrapConfig: map[string]interface{}{ @@ -1351,7 +1361,7 @@ func TestReconcileMachinePoolInfrastructure(t *testing.T) { expectError: false, expectRequeueAfter: false, expected: func(g *WithT, m *expv1.MachinePool) { - g.Expect(m.Status.InfrastructureReady).To(BeTrue()) + g.Expect(m.Status.Initialization != nil && m.Status.Initialization.InfrastructureProvisioned).To(BeTrue()) g.Expect(m.Status.Deprecated.V1Beta1.ReadyReplicas).To(Equal(int32(0))) g.Expect(m.Status.Deprecated.V1Beta1.AvailableReplicas).To(Equal(int32(0))) g.Expect(m.Status.Deprecated.V1Beta1.UnavailableReplicas).To(Equal(int32(0))) diff --git a/internal/apis/core/exp/v1alpha3/conversion.go b/internal/apis/core/exp/v1alpha3/conversion.go index bc593537acd1..0896318c12c8 100644 --- a/internal/apis/core/exp/v1alpha3/conversion.go +++ b/internal/apis/core/exp/v1alpha3/conversion.go @@ -84,6 +84,15 @@ func (src *MachinePool) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Deprecated.V1Beta1.AvailableReplicas = src.Status.AvailableReplicas dst.Status.Deprecated.V1Beta1.UnavailableReplicas = src.Status.UnavailableReplicas + // Move BootstrapReady and InfrastructureReady to Initialization + if src.Status.BootstrapReady || src.Status.InfrastructureReady { + if dst.Status.Initialization == nil { + dst.Status.Initialization = &expv1.MachinePoolInitializationStatus{} + } + dst.Status.Initialization.BootstrapDataSecretCreated = src.Status.BootstrapReady + dst.Status.Initialization.InfrastructureProvisioned = src.Status.InfrastructureReady + } + // Manually restore data. restored := &expv1.MachinePool{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { @@ -130,6 +139,12 @@ func (dst *MachinePool) ConvertFrom(srcRaw conversion.Hub) error { } } + // Move initialization to old fields + if src.Status.Initialization != nil { + dst.Status.BootstrapReady = src.Status.Initialization.BootstrapDataSecretCreated + dst.Status.InfrastructureReady = src.Status.Initialization.InfrastructureProvisioned + } + return utilconversion.MarshalData(src, dst) } diff --git a/internal/apis/core/exp/v1alpha3/conversion_test.go b/internal/apis/core/exp/v1alpha3/conversion_test.go index 95517ef82883..5d1aab70323a 100644 --- a/internal/apis/core/exp/v1alpha3/conversion_test.go +++ b/internal/apis/core/exp/v1alpha3/conversion_test.go @@ -19,6 +19,7 @@ limitations under the License. package v1alpha3 import ( + "reflect" "testing" fuzz "github.com/google/gofuzz" @@ -58,6 +59,13 @@ func hubMachinePoolStatus(in *expv1.MachinePoolStatus, c fuzz.Continue) { if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &expv1.MachinePoolV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &expv1.MachinePoolInitializationStatus{}) { + in.Initialization = nil + } + } } func spokeBootstrap(in *clusterv1alpha3.Bootstrap, c fuzz.Continue) { diff --git a/internal/apis/core/exp/v1alpha3/zz_generated.conversion.go b/internal/apis/core/exp/v1alpha3/zz_generated.conversion.go index 168c51c6e6a5..c9f4bf10f863 100644 --- a/internal/apis/core/exp/v1alpha3/zz_generated.conversion.go +++ b/internal/apis/core/exp/v1alpha3/zz_generated.conversion.go @@ -210,8 +210,8 @@ func autoConvert_v1alpha3_MachinePoolStatus_To_v1beta2_MachinePoolStatus(in *Mac // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type // WARNING: in.FailureMessage requires manual conversion: does not exist in peer-type out.Phase = in.Phase - out.BootstrapReady = in.BootstrapReady - out.InfrastructureReady = in.InfrastructureReady + // WARNING: in.BootstrapReady requires manual conversion: does not exist in peer-type + // WARNING: in.InfrastructureReady requires manual conversion: does not exist in peer-type out.ObservedGeneration = in.ObservedGeneration if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions @@ -239,6 +239,7 @@ func autoConvert_v1beta2_MachinePoolStatus_To_v1alpha3_MachinePoolStatus(in *v1b } else { out.Conditions = nil } + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.NodeRefs = *(*[]corev1.ObjectReference)(unsafe.Pointer(&in.NodeRefs)) out.Replicas = in.Replicas if err := v1.Convert_Pointer_int32_To_int32(&in.ReadyReplicas, &out.ReadyReplicas, s); err != nil { @@ -249,8 +250,6 @@ func autoConvert_v1beta2_MachinePoolStatus_To_v1alpha3_MachinePoolStatus(in *v1b } // WARNING: in.UpToDateReplicas requires manual conversion: does not exist in peer-type out.Phase = in.Phase - out.BootstrapReady = in.BootstrapReady - out.InfrastructureReady = in.InfrastructureReady out.ObservedGeneration = in.ObservedGeneration // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type return nil diff --git a/internal/apis/core/exp/v1alpha4/conversion.go b/internal/apis/core/exp/v1alpha4/conversion.go index 629c798d00a9..cf80399f863b 100644 --- a/internal/apis/core/exp/v1alpha4/conversion.go +++ b/internal/apis/core/exp/v1alpha4/conversion.go @@ -50,6 +50,15 @@ func (src *MachinePool) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Deprecated.V1Beta1.AvailableReplicas = src.Status.AvailableReplicas dst.Status.Deprecated.V1Beta1.UnavailableReplicas = src.Status.UnavailableReplicas + // Move BootstrapReady and InfrastructureReady to Initialization + if src.Status.BootstrapReady || src.Status.InfrastructureReady { + if dst.Status.Initialization == nil { + dst.Status.Initialization = &expv1.MachinePoolInitializationStatus{} + } + dst.Status.Initialization.BootstrapDataSecretCreated = src.Status.BootstrapReady + dst.Status.Initialization.InfrastructureProvisioned = src.Status.InfrastructureReady + } + // Manually restore data. restored := &expv1.MachinePool{} if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok { @@ -96,6 +105,12 @@ func (dst *MachinePool) ConvertFrom(srcRaw conversion.Hub) error { } } + // Move initialization to old fields + if src.Status.Initialization != nil { + dst.Status.BootstrapReady = src.Status.Initialization.BootstrapDataSecretCreated + dst.Status.InfrastructureReady = src.Status.Initialization.InfrastructureProvisioned + } + // Preserve Hub data on down-conversion except for metadata return utilconversion.MarshalData(src, dst) } diff --git a/internal/apis/core/exp/v1alpha4/conversion_test.go b/internal/apis/core/exp/v1alpha4/conversion_test.go index edc7dc28990b..7a67734b0967 100644 --- a/internal/apis/core/exp/v1alpha4/conversion_test.go +++ b/internal/apis/core/exp/v1alpha4/conversion_test.go @@ -19,6 +19,7 @@ limitations under the License. package v1alpha4 import ( + "reflect" "testing" fuzz "github.com/google/gofuzz" @@ -54,4 +55,11 @@ func hubMachinePoolStatus(in *expv1.MachinePoolStatus, c fuzz.Continue) { if in.Deprecated.V1Beta1 == nil { in.Deprecated.V1Beta1 = &expv1.MachinePoolV1Beta1DeprecatedStatus{} } + + // Drop empty structs with only omit empty fields. + if in.Initialization != nil { + if reflect.DeepEqual(in.Initialization, &expv1.MachinePoolInitializationStatus{}) { + in.Initialization = nil + } + } } diff --git a/internal/apis/core/exp/v1alpha4/zz_generated.conversion.go b/internal/apis/core/exp/v1alpha4/zz_generated.conversion.go index 15a0f0f73df8..d8ae37421c6d 100644 --- a/internal/apis/core/exp/v1alpha4/zz_generated.conversion.go +++ b/internal/apis/core/exp/v1alpha4/zz_generated.conversion.go @@ -224,8 +224,8 @@ func autoConvert_v1alpha4_MachinePoolStatus_To_v1beta2_MachinePoolStatus(in *Mac // WARNING: in.FailureReason requires manual conversion: does not exist in peer-type // WARNING: in.FailureMessage requires manual conversion: does not exist in peer-type out.Phase = in.Phase - out.BootstrapReady = in.BootstrapReady - out.InfrastructureReady = in.InfrastructureReady + // WARNING: in.BootstrapReady requires manual conversion: does not exist in peer-type + // WARNING: in.InfrastructureReady requires manual conversion: does not exist in peer-type out.ObservedGeneration = in.ObservedGeneration if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions @@ -253,6 +253,7 @@ func autoConvert_v1beta2_MachinePoolStatus_To_v1alpha4_MachinePoolStatus(in *v1b } else { out.Conditions = nil } + // WARNING: in.Initialization requires manual conversion: does not exist in peer-type out.NodeRefs = *(*[]corev1.ObjectReference)(unsafe.Pointer(&in.NodeRefs)) out.Replicas = in.Replicas if err := v1.Convert_Pointer_int32_To_int32(&in.ReadyReplicas, &out.ReadyReplicas, s); err != nil { @@ -263,8 +264,6 @@ func autoConvert_v1beta2_MachinePoolStatus_To_v1alpha4_MachinePoolStatus(in *v1b } // WARNING: in.UpToDateReplicas requires manual conversion: does not exist in peer-type out.Phase = in.Phase - out.BootstrapReady = in.BootstrapReady - out.InfrastructureReady = in.InfrastructureReady out.ObservedGeneration = in.ObservedGeneration // WARNING: in.Deprecated requires manual conversion: does not exist in peer-type return nil