diff --git a/api/core/v1beta1/conversion.go b/api/core/v1beta1/conversion.go index b8c753e83e6d..4aed53b6732d 100644 --- a/api/core/v1beta1/conversion.go +++ b/api/core/v1beta1/conversion.go @@ -1203,6 +1203,10 @@ func Convert_v1beta1_MachinePoolSpec_To_v1beta2_MachinePoolSpec(in *MachinePoolS return autoConvert_v1beta1_MachinePoolSpec_To_v1beta2_MachinePoolSpec(in, out, s) } +func Convert_v1beta2_MachinePoolSpec_To_v1beta1_MachinePoolSpec(in *clusterv1.MachinePoolSpec, out *MachinePoolSpec, s apimachineryconversion.Scope) error { + return autoConvert_v1beta2_MachinePoolSpec_To_v1beta1_MachinePoolSpec(in, out, s) +} + func Convert_v1beta1_ClusterClassStatusVariableDefinition_To_v1beta2_ClusterClassStatusVariableDefinition(in *ClusterClassStatusVariableDefinition, out *clusterv1.ClusterClassStatusVariableDefinition, s apimachineryconversion.Scope) error { if err := autoConvert_v1beta1_ClusterClassStatusVariableDefinition_To_v1beta2_ClusterClassStatusVariableDefinition(in, out, s); err != nil { return err diff --git a/api/core/v1beta1/conversion_test.go b/api/core/v1beta1/conversion_test.go index 5eaf248d8edd..b7c03423ca7d 100644 --- a/api/core/v1beta1/conversion_test.go +++ b/api/core/v1beta1/conversion_test.go @@ -583,6 +583,7 @@ func spokeObjectReference(in *corev1.ObjectReference, c randfill.Continue) { func MachinePoolFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { return []interface{}{ + hubMachinePoolSpec, hubMachinePoolStatus, hubMachineSpec, spokeMachinePool, @@ -591,6 +592,13 @@ func MachinePoolFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { } } +func hubMachinePoolSpec(in *clusterv1.MachinePoolSpec, c randfill.Continue) { + c.FillNoCustom(in) + + // Readded in v1beta2 with different type than in v1alpha's, no conversion possible + in.Strategy = nil +} + func hubMachinePoolStatus(in *clusterv1.MachinePoolStatus, c randfill.Continue) { c.FillNoCustom(in) // Always create struct with at least one mandatory fields. diff --git a/api/core/v1beta1/machinepool_types.go b/api/core/v1beta1/machinepool_types.go index 174fb6817183..9f1e572b96f7 100644 --- a/api/core/v1beta1/machinepool_types.go +++ b/api/core/v1beta1/machinepool_types.go @@ -108,6 +108,21 @@ type MachinePoolSpec struct { // ANCHOR_END: MachinePoolSpec +// ANCHOR: MachinePoolStrategy + +// MachinePoolStrategy describes how to replace existing machines +// with new ones. +type MachinePoolStrategy struct { + // remediation controls the strategy of remediating unhealthy machines + // as marked by a MachineHealthCheck. This only applies to infrastructure + // providers supporting "MachinePool Machines". For other providers, + // no remediation is done. + // +optional + Remediation *RemediationStrategy `json:"remediation,omitempty"` +} + +// ANCHOR_END: MachinePoolStrategy + // ANCHOR: MachinePoolStatus // MachinePoolStatus defines the observed state of MachinePool. diff --git a/api/core/v1beta1/zz_generated.conversion.go b/api/core/v1beta1/zz_generated.conversion.go index 24a279a41eca..aa7cb1e57709 100644 --- a/api/core/v1beta1/zz_generated.conversion.go +++ b/api/core/v1beta1/zz_generated.conversion.go @@ -475,8 +475,13 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.MachinePoolSpec)(nil), (*MachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_MachinePoolSpec_To_v1beta1_MachinePoolSpec(a.(*v1beta2.MachinePoolSpec), b.(*MachinePoolSpec), scope) + if err := s.AddGeneratedConversionFunc((*MachinePoolStrategy)(nil), (*v1beta2.MachinePoolStrategy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_MachinePoolStrategy_To_v1beta2_MachinePoolStrategy(a.(*MachinePoolStrategy), b.(*v1beta2.MachinePoolStrategy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta2.MachinePoolStrategy)(nil), (*MachinePoolStrategy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_MachinePoolStrategy_To_v1beta1_MachinePoolStrategy(a.(*v1beta2.MachinePoolStrategy), b.(*MachinePoolStrategy), scope) }); err != nil { return err } @@ -950,6 +955,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.MachinePoolSpec)(nil), (*MachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_MachinePoolSpec_To_v1beta1_MachinePoolSpec(a.(*v1beta2.MachinePoolSpec), b.(*MachinePoolSpec), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.MachinePoolStatus)(nil), (*MachinePoolStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_MachinePoolStatus_To_v1beta1_MachinePoolStatus(a.(*v1beta2.MachinePoolStatus), b.(*MachinePoolStatus), scope) }); err != nil { @@ -3123,14 +3133,10 @@ func autoConvert_v1beta2_MachinePoolSpec_To_v1beta1_MachinePoolSpec(in *v1beta2. } out.ProviderIDList = *(*[]string)(unsafe.Pointer(&in.ProviderIDList)) out.FailureDomains = *(*[]string)(unsafe.Pointer(&in.FailureDomains)) + // WARNING: in.Strategy requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta2_MachinePoolSpec_To_v1beta1_MachinePoolSpec is an autogenerated conversion function. -func Convert_v1beta2_MachinePoolSpec_To_v1beta1_MachinePoolSpec(in *v1beta2.MachinePoolSpec, out *MachinePoolSpec, s conversion.Scope) error { - return autoConvert_v1beta2_MachinePoolSpec_To_v1beta1_MachinePoolSpec(in, out, s) -} - func autoConvert_v1beta1_MachinePoolStatus_To_v1beta2_MachinePoolStatus(in *MachinePoolStatus, out *v1beta2.MachinePoolStatus, s conversion.Scope) error { out.NodeRefs = *(*[]corev1.ObjectReference)(unsafe.Pointer(&in.NodeRefs)) if err := v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { @@ -3194,6 +3200,26 @@ func autoConvert_v1beta2_MachinePoolStatus_To_v1beta1_MachinePoolStatus(in *v1be return nil } +func autoConvert_v1beta1_MachinePoolStrategy_To_v1beta2_MachinePoolStrategy(in *MachinePoolStrategy, out *v1beta2.MachinePoolStrategy, s conversion.Scope) error { + out.Remediation = (*v1beta2.RemediationStrategy)(unsafe.Pointer(in.Remediation)) + return nil +} + +// Convert_v1beta1_MachinePoolStrategy_To_v1beta2_MachinePoolStrategy is an autogenerated conversion function. +func Convert_v1beta1_MachinePoolStrategy_To_v1beta2_MachinePoolStrategy(in *MachinePoolStrategy, out *v1beta2.MachinePoolStrategy, s conversion.Scope) error { + return autoConvert_v1beta1_MachinePoolStrategy_To_v1beta2_MachinePoolStrategy(in, out, s) +} + +func autoConvert_v1beta2_MachinePoolStrategy_To_v1beta1_MachinePoolStrategy(in *v1beta2.MachinePoolStrategy, out *MachinePoolStrategy, s conversion.Scope) error { + out.Remediation = (*RemediationStrategy)(unsafe.Pointer(in.Remediation)) + return nil +} + +// Convert_v1beta2_MachinePoolStrategy_To_v1beta1_MachinePoolStrategy is an autogenerated conversion function. +func Convert_v1beta2_MachinePoolStrategy_To_v1beta1_MachinePoolStrategy(in *v1beta2.MachinePoolStrategy, out *MachinePoolStrategy, s conversion.Scope) error { + return autoConvert_v1beta2_MachinePoolStrategy_To_v1beta1_MachinePoolStrategy(in, out, s) +} + func autoConvert_v1beta1_MachinePoolTopology_To_v1beta2_MachinePoolTopology(in *MachinePoolTopology, out *v1beta2.MachinePoolTopology, s conversion.Scope) error { if err := Convert_v1beta1_ObjectMeta_To_v1beta2_ObjectMeta(&in.Metadata, &out.Metadata, s); err != nil { return err diff --git a/api/core/v1beta1/zz_generated.deepcopy.go b/api/core/v1beta1/zz_generated.deepcopy.go index a1b090669cb4..81aaaa8526b7 100644 --- a/api/core/v1beta1/zz_generated.deepcopy.go +++ b/api/core/v1beta1/zz_generated.deepcopy.go @@ -2213,6 +2213,26 @@ func (in *MachinePoolStatus) DeepCopy() *MachinePoolStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachinePoolStrategy) DeepCopyInto(out *MachinePoolStrategy) { + *out = *in + if in.Remediation != nil { + in, out := &in.Remediation, &out.Remediation + *out = new(RemediationStrategy) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachinePoolStrategy. +func (in *MachinePoolStrategy) DeepCopy() *MachinePoolStrategy { + if in == nil { + return nil + } + out := new(MachinePoolStrategy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MachinePoolTopology) DeepCopyInto(out *MachinePoolTopology) { *out = *in diff --git a/api/core/v1beta1/zz_generated.openapi.go b/api/core/v1beta1/zz_generated.openapi.go index 13a78b23719c..d317c52c3de9 100644 --- a/api/core/v1beta1/zz_generated.openapi.go +++ b/api/core/v1beta1/zz_generated.openapi.go @@ -97,6 +97,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "sigs.k8s.io/cluster-api/api/core/v1beta1.MachinePoolList": schema_cluster_api_api_core_v1beta1_MachinePoolList(ref), "sigs.k8s.io/cluster-api/api/core/v1beta1.MachinePoolSpec": schema_cluster_api_api_core_v1beta1_MachinePoolSpec(ref), "sigs.k8s.io/cluster-api/api/core/v1beta1.MachinePoolStatus": schema_cluster_api_api_core_v1beta1_MachinePoolStatus(ref), + "sigs.k8s.io/cluster-api/api/core/v1beta1.MachinePoolStrategy": schema_cluster_api_api_core_v1beta1_MachinePoolStrategy(ref), "sigs.k8s.io/cluster-api/api/core/v1beta1.MachinePoolTopology": schema_cluster_api_api_core_v1beta1_MachinePoolTopology(ref), "sigs.k8s.io/cluster-api/api/core/v1beta1.MachinePoolV1Beta2Status": schema_cluster_api_api_core_v1beta1_MachinePoolV1Beta2Status(ref), "sigs.k8s.io/cluster-api/api/core/v1beta1.MachinePoolVariables": schema_cluster_api_api_core_v1beta1_MachinePoolVariables(ref), @@ -3897,6 +3898,27 @@ func schema_cluster_api_api_core_v1beta1_MachinePoolStatus(ref common.ReferenceC } } +func schema_cluster_api_api_core_v1beta1_MachinePoolStrategy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MachinePoolStrategy describes how to replace existing machines with new ones.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "remediation": { + SchemaProps: spec.SchemaProps{ + Description: "remediation controls the strategy of remediating unhealthy machines as marked by a MachineHealthCheck. This only applies to infrastructure providers supporting \"MachinePool Machines\". For other providers, no remediation is done.", + Ref: ref("sigs.k8s.io/cluster-api/api/core/v1beta1.RemediationStrategy"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/cluster-api/api/core/v1beta1.RemediationStrategy"}, + } +} + func schema_cluster_api_api_core_v1beta1_MachinePoolTopology(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/api/core/v1beta2/machinepool_types.go b/api/core/v1beta2/machinepool_types.go index 0f16263bf194..95eaee5d73a0 100644 --- a/api/core/v1beta2/machinepool_types.go +++ b/api/core/v1beta2/machinepool_types.go @@ -97,10 +97,29 @@ type MachinePoolSpec struct { // +kubebuilder:validation:items:MinLength=1 // +kubebuilder:validation:items:MaxLength=256 FailureDomains []string `json:"failureDomains,omitempty"` + + // strategy defines how to replace existing machines with new ones. + // +optional + Strategy *MachinePoolStrategy `json:"strategy,omitempty"` } // ANCHOR_END: MachinePoolSpec +// ANCHOR: MachinePoolStrategy + +// MachinePoolStrategy describes how to replace existing machines +// with new ones. +type MachinePoolStrategy struct { + // remediation controls the strategy of remediating unhealthy machines + // as marked by a MachineHealthCheck. This only applies to infrastructure + // providers supporting "MachinePool Machines". For other providers, + // no remediation is done. + // +optional + Remediation *RemediationStrategy `json:"remediation,omitempty"` +} + +// ANCHOR_END: MachinePoolStrategy + // ANCHOR: MachinePoolStatus // MachinePoolStatus defines the observed state of MachinePool. diff --git a/api/core/v1beta2/zz_generated.deepcopy.go b/api/core/v1beta2/zz_generated.deepcopy.go index f267696437b3..15f73ee1fad4 100644 --- a/api/core/v1beta2/zz_generated.deepcopy.go +++ b/api/core/v1beta2/zz_generated.deepcopy.go @@ -2384,6 +2384,11 @@ func (in *MachinePoolSpec) DeepCopyInto(out *MachinePoolSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Strategy != nil { + in, out := &in.Strategy, &out.Strategy + *out = new(MachinePoolStrategy) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachinePoolSpec. @@ -2453,6 +2458,26 @@ func (in *MachinePoolStatus) DeepCopy() *MachinePoolStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachinePoolStrategy) DeepCopyInto(out *MachinePoolStrategy) { + *out = *in + if in.Remediation != nil { + in, out := &in.Remediation, &out.Remediation + *out = new(RemediationStrategy) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachinePoolStrategy. +func (in *MachinePoolStrategy) DeepCopy() *MachinePoolStrategy { + if in == nil { + return nil + } + out := new(MachinePoolStrategy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MachinePoolTopology) DeepCopyInto(out *MachinePoolTopology) { *out = *in diff --git a/api/core/v1beta2/zz_generated.openapi.go b/api/core/v1beta2/zz_generated.openapi.go index 7c4a163e59d6..40faf2e21012 100644 --- a/api/core/v1beta2/zz_generated.openapi.go +++ b/api/core/v1beta2/zz_generated.openapi.go @@ -112,6 +112,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolList": schema_cluster_api_api_core_v1beta2_MachinePoolList(ref), "sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolSpec": schema_cluster_api_api_core_v1beta2_MachinePoolSpec(ref), "sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolStatus": schema_cluster_api_api_core_v1beta2_MachinePoolStatus(ref), + "sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolStrategy": schema_cluster_api_api_core_v1beta2_MachinePoolStrategy(ref), "sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolTopology": schema_cluster_api_api_core_v1beta2_MachinePoolTopology(ref), "sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolV1Beta1DeprecatedStatus": schema_cluster_api_api_core_v1beta2_MachinePoolV1Beta1DeprecatedStatus(ref), "sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolVariables": schema_cluster_api_api_core_v1beta2_MachinePoolVariables(ref), @@ -4170,12 +4171,18 @@ func schema_cluster_api_api_core_v1beta2_MachinePoolSpec(ref common.ReferenceCal }, }, }, + "strategy": { + SchemaProps: spec.SchemaProps{ + Description: "strategy defines how to replace existing machines with new ones.", + Ref: ref("sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolStrategy"), + }, + }, }, Required: []string{"clusterName", "template"}, }, }, Dependencies: []string{ - "sigs.k8s.io/cluster-api/api/core/v1beta2.MachineTemplateSpec"}, + "sigs.k8s.io/cluster-api/api/core/v1beta2.MachinePoolStrategy", "sigs.k8s.io/cluster-api/api/core/v1beta2.MachineTemplateSpec"}, } } @@ -4284,6 +4291,27 @@ func schema_cluster_api_api_core_v1beta2_MachinePoolStatus(ref common.ReferenceC } } +func schema_cluster_api_api_core_v1beta2_MachinePoolStrategy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MachinePoolStrategy describes how to replace existing machines with new ones.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "remediation": { + SchemaProps: spec.SchemaProps{ + Description: "remediation controls the strategy of remediating unhealthy machines as marked by a MachineHealthCheck. This only applies to infrastructure providers supporting \"MachinePool Machines\". For other providers, no remediation is done.", + Ref: ref("sigs.k8s.io/cluster-api/api/core/v1beta2.RemediationStrategy"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/cluster-api/api/core/v1beta2.RemediationStrategy"}, + } +} + func schema_cluster_api_api_core_v1beta2_MachinePoolTopology(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/config/crd/bases/cluster.x-k8s.io_machinepools.yaml b/config/crd/bases/cluster.x-k8s.io_machinepools.yaml index 0a9ecf33f7fb..bad46247739f 100644 --- a/config/crd/bases/cluster.x-k8s.io_machinepools.yaml +++ b/config/crd/bases/cluster.x-k8s.io_machinepools.yaml @@ -1694,6 +1694,39 @@ spec: This is a pointer to distinguish between explicit zero and not specified. format: int32 type: integer + strategy: + description: strategy defines how to replace existing machines with + new ones. + properties: + remediation: + description: |- + remediation controls the strategy of remediating unhealthy machines + as marked by a MachineHealthCheck. This only applies to infrastructure + providers supporting "MachinePool Machines". For other providers, + no remediation is done. + properties: + maxInFlight: + anyOf: + - type: integer + - type: string + description: |- + maxInFlight determines how many in flight remediations should happen at the same time. + + Remediation only happens on the MachineSet with the most current revision, while + older MachineSets (usually present during rollout operations) aren't allowed to remediate. + + Note: In general (independent of remediations), unhealthy machines are always + prioritized during scale down operations over healthy ones. + + MaxInFlight can be set to a fixed number or a percentage. + Example: when this is set to 20%, the MachineSet controller deletes at most 20% of + the desired replicas. + + If not set, remediation is limited to all machines (bounded by replicas) + under the active MachineSet's management. + x-kubernetes-int-or-string: true + type: object + type: object template: description: template describes the machines that will be created. properties: diff --git a/exp/internal/controllers/machinepool_controller_phases.go b/exp/internal/controllers/machinepool_controller_phases.go index ed8bab3db85e..f9704303f614 100644 --- a/exp/internal/controllers/machinepool_controller_phases.go +++ b/exp/internal/controllers/machinepool_controller_phases.go @@ -20,14 +20,17 @@ import ( "context" "fmt" "reflect" + "sort" "time" "github.com/pkg/errors" + "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" kerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" "k8s.io/utils/ptr" @@ -44,6 +47,8 @@ import ( "sigs.k8s.io/cluster-api/internal/util/ssa" "sigs.k8s.io/cluster-api/util" "sigs.k8s.io/cluster-api/util/annotations" + "sigs.k8s.io/cluster-api/util/collections" + "sigs.k8s.io/cluster-api/util/conditions" v1beta1conditions "sigs.k8s.io/cluster-api/util/conditions/deprecated/v1beta1" "sigs.k8s.io/cluster-api/util/labels" "sigs.k8s.io/cluster-api/util/labels/format" @@ -315,7 +320,10 @@ func (r *MachinePoolReconciler) reconcileInfrastructure(ctx context.Context, s * // Get the nodeRefsMap from the cluster. s.nodeRefMap, getNodeRefsErr = r.getNodeRefMap(ctx, clusterClient) - err = r.reconcileMachines(ctx, s, infraConfig) + res := ctrl.Result{} + + reconcileMachinesRes, err := r.reconcileMachines(ctx, s, infraConfig) + res = util.LowestNonZeroResult(res, reconcileMachinesRes) if err != nil || getNodeRefsErr != nil { 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))}) @@ -323,7 +331,7 @@ func (r *MachinePoolReconciler) reconcileInfrastructure(ctx context.Context, s * 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 + return res, nil } var providerIDList []string @@ -358,7 +366,7 @@ func (r *MachinePoolReconciler) reconcileInfrastructure(ctx context.Context, s * mp.Status.Deprecated.V1Beta1.UnavailableReplicas = ptr.Deref(mp.Status.Replicas, 0) } - return ctrl.Result{}, nil + return res, nil } // reconcileMachines reconciles Machines associated with a MachinePool. @@ -368,7 +376,7 @@ func (r *MachinePoolReconciler) reconcileInfrastructure(ctx context.Context, s * // infrastructure is created accordingly. // Note: When supported by the cloud provider implementation of the MachinePool, machines will provide a means to interact // with the corresponding infrastructure (e.g. delete a specific machine in case MachineHealthCheck detects it is unhealthy). -func (r *MachinePoolReconciler) reconcileMachines(ctx context.Context, s *scope, infraMachinePool *unstructured.Unstructured) error { +func (r *MachinePoolReconciler) reconcileMachines(ctx context.Context, s *scope, infraMachinePool *unstructured.Unstructured) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) mp := s.machinePool @@ -376,10 +384,10 @@ func (r *MachinePoolReconciler) reconcileMachines(ctx context.Context, s *scope, if err := util.UnstructuredUnmarshalField(infraMachinePool, &infraMachineKind, "status", "infrastructureMachineKind"); err != nil { if errors.Is(err, util.ErrUnstructuredFieldNotFound) { log.V(4).Info("MachinePool Machines not supported, no infraMachineKind found") - return nil + return ctrl.Result{}, nil } - return errors.Wrapf(err, "failed to retrieve infraMachineKind from infrastructure provider for MachinePool %s", klog.KObj(mp)) + return ctrl.Result{}, errors.Wrapf(err, "failed to retrieve infraMachineKind from infrastructure provider for MachinePool %s", klog.KObj(mp)) } infraMachineSelector := metav1.LabelSelector{ @@ -396,7 +404,7 @@ func (r *MachinePoolReconciler) reconcileMachines(ctx context.Context, s *scope, infraMachineList.SetAPIVersion(infraMachinePool.GetAPIVersion()) infraMachineList.SetKind(infraMachineKind + "List") if err := r.Client.List(ctx, &infraMachineList, client.InNamespace(mp.Namespace), client.MatchingLabels(infraMachineSelector.MatchLabels)); err != nil { - return errors.Wrapf(err, "failed to list infra machines for MachinePool %q in namespace %q", mp.Name, mp.Namespace) + return ctrl.Result{}, errors.Wrapf(err, "failed to list infra machines for MachinePool %q in namespace %q", mp.Name, mp.Namespace) } // Add watcher for infraMachine, if there isn't one already; this will allow this controller to reconcile @@ -407,21 +415,26 @@ func (r *MachinePoolReconciler) reconcileMachines(ctx context.Context, s *scope, // Add watcher for infraMachine, if there isn't one already. if err := r.externalTracker.Watch(log, sampleInfraMachine, handler.EnqueueRequestsFromMapFunc(r.infraMachineToMachinePoolMapper), predicates.ResourceIsChanged(r.Client.Scheme(), *r.externalTracker.PredicateLogger)); err != nil { - return err + return ctrl.Result{}, err } // Get the list of machines managed by this controller, and align it with the infra machines managed by // the InfraMachinePool controller. machineList := &clusterv1.MachineList{} if err := r.Client.List(ctx, machineList, client.InNamespace(mp.Namespace), client.MatchingLabels(infraMachineSelector.MatchLabels)); err != nil { - return err + return ctrl.Result{}, err } if err := r.createOrUpdateMachines(ctx, s, machineList.Items, infraMachineList.Items); err != nil { - return errors.Wrapf(err, "failed to create machines for MachinePool %q in namespace %q", mp.Name, mp.Namespace) + return ctrl.Result{}, errors.Wrapf(err, "failed to create machines for MachinePool %q in namespace %q", mp.Name, mp.Namespace) } - return nil + res, err := r.reconcileUnhealthyMachines(ctx, s, machineList.Items) + if err != nil { + return ctrl.Result{}, errors.Wrapf(err, "failed to reconcile unhealthy machines for MachinePool %s", klog.KObj(mp)) + } + + return res, nil } // createOrUpdateMachines creates a MachinePool Machine for each infraMachine if it doesn't already exist and sets the owner reference and infraRef. @@ -615,3 +628,121 @@ func (r *MachinePoolReconciler) getNodeRefMap(ctx context.Context, c client.Clie return nodeRefsMap, nil } + +func (r *MachinePoolReconciler) reconcileUnhealthyMachines(ctx context.Context, s *scope, machines []clusterv1.Machine) (ctrl.Result, error) { + if len(machines) == 0 { + return ctrl.Result{}, nil + } + + log := ctrl.LoggerFrom(ctx) + mp := s.machinePool + + machinesWithHealthCheck := slices.DeleteFunc(slices.Clone(machines), func(machine clusterv1.Machine) bool { + return !conditions.Has(&machine, clusterv1.MachineHealthCheckSucceededCondition) + }) + if len(machinesWithHealthCheck) == 0 { + // This means there is no MachineHealthCheck selecting any machines + // of this machine pool. In this case, do not requeue so often, + // but still check regularly in case a MachineHealthCheck became + // deployed or activated. This long interval shouldn't be a problem + // at cluster creation, since newly-created nodes should anyway + // trigger MachinePool reconciliation as the infrastructure provider + // creates the InfraMachines. + log.V(4).Info("Skipping reconciliation of unhealthy MachinePool machines because there are no health-checked machines") + return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil + } + + unhealthyMachines := slices.DeleteFunc(slices.Clone(machines), func(machine clusterv1.Machine) bool { + return !collections.IsUnhealthyAndOwnerRemediated(&machine) + }) + log.V(4).Info("Reconciling unhealthy MachinePool machines", "unhealthyMachines", len(unhealthyMachines)) + + // Calculate how many in flight machines we should remediate. + // By default, we allow all machines to be remediated at the same time. + maxInFlight := len(unhealthyMachines) + if mp.Spec.Strategy != nil && mp.Spec.Strategy.Remediation != nil { + if mp.Spec.Strategy.Remediation.MaxInFlight != nil { + var err error + replicas := int(ptr.Deref(mp.Spec.Replicas, 1)) + maxInFlight, err = intstr.GetScaledValueFromIntOrPercent(mp.Spec.Strategy.Remediation.MaxInFlight, replicas, true) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to calculate maxInFlight to remediate machines: %v", err) + } + log = log.WithValues("maxInFlight", maxInFlight, "replicas", replicas) + } + } + + machinesToRemediate := make([]*clusterv1.Machine, 0, len(unhealthyMachines)) + inFlight := 0 + for _, m := range unhealthyMachines { + if !m.DeletionTimestamp.IsZero() { + if conditions.IsTrue(&m, clusterv1.MachineOwnerRemediatedCondition) { + // Machine has been remediated by this controller and still in flight. + inFlight++ + } + continue + } + if conditions.IsFalse(&m, clusterv1.MachineOwnerRemediatedCondition) { + machinesToRemediate = append(machinesToRemediate, &m) + } + } + log = log.WithValues("inFlight", inFlight) + + if len(machinesToRemediate) == 0 { + // There's a MachineHealthCheck monitoring the machines, but currently + // no action to be taken. A machine could require remediation at any + // time, so use a short interval until next reconciliation. + return ctrl.Result{RequeueAfter: 30 * time.Second}, nil + } + + if inFlight >= maxInFlight { + log.V(3).Info("Remediation strategy is set, and maximum in flight has been reached", "machinesToBeRemediated", len(machinesToRemediate)) + + // Check soon again whether the already-remediating (= deleting) machines are gone + // so that more machines can be remediated + return ctrl.Result{RequeueAfter: 15 * time.Second}, nil + } + + // Sort the machines from newest to oldest. + // We are trying to remediate machines failing to come up first because + // there is a chance that they are not hosting any workloads (minimize disruption). + sort.SliceStable(machinesToRemediate, func(i, j int) bool { + return machinesToRemediate[i].CreationTimestamp.After(machinesToRemediate[j].CreationTimestamp.Time) + }) + + haveMoreMachinesToRemediate := false + if len(machinesToRemediate) > (maxInFlight - inFlight) { + haveMoreMachinesToRemediate = true + log.V(5).Info("Remediation strategy is set, limiting in flight operations", "machinesToBeRemediated", len(machinesToRemediate)) + machinesToRemediate = machinesToRemediate[:(maxInFlight - inFlight)] + } + + // Remediate unhealthy machines by deleting them + var errs []error + for _, m := range machinesToRemediate { + log.Info("Deleting unhealthy Machine", "Machine", klog.KObj(m)) + patch := client.MergeFrom(m.DeepCopy()) + if err := r.Client.Delete(ctx, m); err != nil { + if apierrors.IsNotFound(err) { + continue + } + errs = append(errs, errors.Wrapf(err, "failed to delete Machine %s", klog.KObj(m))) + continue + } + v1beta1conditions.MarkTrue(m, clusterv1.MachineOwnerRemediatedCondition) + if err := r.Client.Status().Patch(ctx, m, patch); err != nil && !apierrors.IsNotFound(err) { + errs = append(errs, errors.Wrapf(err, "failed to update status of Machine %s", klog.KObj(m))) + } + } + + if len(errs) > 0 { + return ctrl.Result{}, errors.Wrapf(kerrors.NewAggregate(errs), "failed to delete unhealthy Machines") + } + + if haveMoreMachinesToRemediate { + // More machines need remediation, so reconcile again sooner + return ctrl.Result{RequeueAfter: 15 * time.Second}, nil + } + + return ctrl.Result{RequeueAfter: 30 * time.Second}, nil +} diff --git a/exp/internal/controllers/machinepool_controller_phases_test.go b/exp/internal/controllers/machinepool_controller_phases_test.go index 9b177dabf838..9e800f814f9c 100644 --- a/exp/internal/controllers/machinepool_controller_phases_test.go +++ b/exp/internal/controllers/machinepool_controller_phases_test.go @@ -41,8 +41,11 @@ import ( "sigs.k8s.io/cluster-api/controllers/external" externalfake "sigs.k8s.io/cluster-api/controllers/external/fake" "sigs.k8s.io/cluster-api/internal/util/ssa" + "sigs.k8s.io/cluster-api/util/conditions" + v1beta1conditions "sigs.k8s.io/cluster-api/util/conditions/deprecated/v1beta1" "sigs.k8s.io/cluster-api/util/kubeconfig" "sigs.k8s.io/cluster-api/util/labels/format" + "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/cluster-api/util/test/builder" ) @@ -1409,7 +1412,7 @@ func TestReconcileMachinePoolMachines(t *testing.T) { scope := &scope{ machinePool: &machinePool, } - err = r.reconcileMachines(ctx, scope, &unstructured.Unstructured{Object: infraConfig}) + _, err = r.reconcileMachines(ctx, scope, &unstructured.Unstructured{Object: infraConfig}) r.reconcilePhase(&machinePool) g.Expect(err).ToNot(HaveOccurred()) @@ -1480,7 +1483,7 @@ func TestReconcileMachinePoolMachines(t *testing.T) { machinePool: &machinePool, } - err = r.reconcileMachines(ctx, scope, &unstructured.Unstructured{Object: infraConfig}) + _, err = r.reconcileMachines(ctx, scope, &unstructured.Unstructured{Object: infraConfig}) r.reconcilePhase(&machinePool) g.Expect(err).ToNot(HaveOccurred()) @@ -1533,15 +1536,24 @@ func TestReconcileMachinePoolMachines(t *testing.T) { r := &MachinePoolReconciler{ Client: env, ssaCache: ssa.NewCache(testController), + externalTracker: external.ObjectTracker{ + Controller: externalfake.Controller{}, + Cache: &informertest.FakeInformers{}, + Scheme: env.Scheme(), + PredicateLogger: ptr.To(logr.New(log.NullLogSink{})), + }, } scope := &scope{ machinePool: &machinePool, } - err = r.reconcileMachines(ctx, scope, &unstructured.Unstructured{Object: infraConfig}) + res, err := r.reconcileMachines(ctx, scope, &unstructured.Unstructured{Object: infraConfig}) r.reconcilePhase(&machinePool) g.Expect(err).ToNot(HaveOccurred()) + // Regular reconciliation makes no sense if infra provider + // doesn't support MachinePool machines + g.Expect(res.RequeueAfter).To(BeZero()) machineList := &clusterv1.MachineList{} labels := map[string]string{ @@ -1551,6 +1563,136 @@ func TestReconcileMachinePoolMachines(t *testing.T) { g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace), client.MatchingLabels(labels))).To(Succeed()) g.Expect(machineList.Items).To(BeEmpty()) }) + + t.Run("Should delete unhealthy machines", func(*testing.T) { + machinePool := getMachinePool(3, "machinepool-test-4", clusterName, ns.Name) + g.Expect(env.CreateAndWait(ctx, &machinePool)).To(Succeed()) + + infraMachines := getInfraMachines(3, machinePool.Name, clusterName, ns.Name) + for i := range infraMachines { + g.Expect(env.CreateAndWait(ctx, &infraMachines[i])).To(Succeed()) + } + + machines := getMachines(3, machinePool.Name, clusterName, ns.Name) + for i := range machines { + g.Expect(env.CreateAndWait(ctx, &machines[i])).To(Succeed()) + } + + // machines[0] isn't changed here (no conditions = considered healthy). + + // machines[1] is marked as unhealthy by conditions + patchHelper, err := patch.NewHelper(&machines[1], env) + unhealthyMachineName := machines[1].Name + v1beta1conditions.MarkFalse(&machines[1], clusterv1.MachineHealthCheckSucceededV1Beta1Condition, clusterv1.MachineHasFailureV1Beta1Reason, clusterv1.ConditionSeverityWarning, "") + conditions.Set(&machines[1], metav1.Condition{ + Type: clusterv1.MachineHealthCheckSucceededCondition, + Status: metav1.ConditionFalse, + Reason: clusterv1.MachineHasFailureV1Beta1Reason, + }) + v1beta1conditions.MarkFalse(&machines[1], clusterv1.MachineOwnerRemediatedV1Beta1Condition, clusterv1.WaitingForRemediationV1Beta1Reason, clusterv1.ConditionSeverityWarning, "") + conditions.Set(&machines[1], metav1.Condition{ + Type: clusterv1.MachineOwnerRemediatedCondition, + Status: metav1.ConditionFalse, + Reason: clusterv1.WaitingForRemediationV1Beta1Reason, + }) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(patchHelper.Patch(ctx, &machines[1], patch.WithStatusObservedGeneration{}, patch.WithOwnedConditions{Conditions: []string{ + clusterv1.MachineHealthCheckSucceededCondition, + clusterv1.MachineOwnerRemediatedCondition, + }}, patch.WithOwnedV1Beta1Conditions{Conditions: []clusterv1.ConditionType{ + clusterv1.MachineHealthCheckSucceededV1Beta1Condition, + clusterv1.MachineOwnerRemediatedV1Beta1Condition, + }})).To(Succeed()) + + // machines[2] is marked as healthy by conditions + patchHelper, err = patch.NewHelper(&machines[2], env) + v1beta1conditions.MarkTrue(&machines[2], clusterv1.MachineHealthCheckSucceededV1Beta1Condition) + conditions.Set(&machines[2], metav1.Condition{ + Type: clusterv1.MachineHealthCheckSucceededCondition, + Status: metav1.ConditionTrue, + Reason: clusterv1.MachineHealthCheckSucceededReason, + }) + g.Expect(err).ShouldNot(HaveOccurred()) + g.Expect(patchHelper.Patch(ctx, &machines[2], patch.WithStatusObservedGeneration{}, patch.WithOwnedConditions{Conditions: []string{ + clusterv1.MachineHealthCheckSucceededCondition, + clusterv1.MachineOwnerRemediatedCondition, + }}, patch.WithOwnedV1Beta1Conditions{Conditions: []clusterv1.ConditionType{ + clusterv1.MachineHealthCheckSucceededV1Beta1Condition, + clusterv1.MachineOwnerRemediatedV1Beta1Condition, + }})).To(Succeed()) + + infraConfig := map[string]interface{}{ + "kind": builder.GenericInfrastructureMachinePoolKind, + "apiVersion": builder.InfrastructureGroupVersion.String(), + "metadata": map[string]interface{}{ + "name": "infra-config4", + "namespace": ns.Name, + }, + "spec": map[string]interface{}{ + "providerIDList": []interface{}{ + "test://id-1", + }, + }, + "status": map[string]interface{}{ + "ready": true, + "addresses": []interface{}{ + map[string]interface{}{ + "type": "InternalIP", + "address": "10.0.0.1", + }, + map[string]interface{}{ + "type": "InternalIP", + "address": "10.0.0.2", + }, + }, + "infrastructureMachineKind": builder.GenericInfrastructureMachineKind, + }, + } + g.Expect(env.CreateAndWait(ctx, &unstructured.Unstructured{Object: infraConfig})).To(Succeed()) + + r := &MachinePoolReconciler{ + Client: env, + ssaCache: ssa.NewCache("test-controller"), + externalTracker: external.ObjectTracker{ + Controller: externalfake.Controller{}, + Cache: &informertest.FakeInformers{}, + Scheme: env.Scheme(), + PredicateLogger: ptr.To(logr.New(log.NullLogSink{})), + }, + } + scope := &scope{ + machinePool: &machinePool, + } + res, err := r.reconcileMachines(ctx, scope, &unstructured.Unstructured{Object: infraConfig}) + r.reconcilePhase(&machinePool) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(res.RequeueAfter).To(BeNumerically(">=", 0)) + + machineList := &clusterv1.MachineList{} + labels := map[string]string{ + clusterv1.ClusterNameLabel: clusterName, + clusterv1.MachinePoolNameLabel: machinePool.Name, + } + g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace), client.MatchingLabels(labels))).To(Succeed()) + + // The unhealthy machine should have been remediated (= deleted) + g.Expect(machineList.Items).To(HaveLen(2)) + + for i := range machineList.Items { + machine := &machineList.Items[i] + + // Healthy machines should remain + g.Expect(machine.Name).ToNot(Equal(unhealthyMachineName)) + + _, err := external.Get(ctx, r.Client, &corev1.ObjectReference{ + APIVersion: builder.InfrastructureGroupVersion.String(), + Kind: machine.Spec.InfrastructureRef.Kind, + Namespace: machine.Namespace, + Name: machine.Spec.InfrastructureRef.Name, + }) + g.Expect(err).ToNot(HaveOccurred()) + } + }) }) } diff --git a/go.mod b/go.mod index fe1f1f8e965c..d5c22b7f2e0d 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( go.etcd.io/etcd/client/pkg/v3 v3.5.21 go.etcd.io/etcd/client/v3 v3.5.21 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/oauth2 v0.30.0 golang.org/x/text v0.26.0 gomodules.xyz/jsonpatch/v2 v2.5.0 diff --git a/internal/api/core/v1alpha3/conversion.go b/internal/api/core/v1alpha3/conversion.go index 03ce8c882f07..0d495961ae6b 100644 --- a/internal/api/core/v1alpha3/conversion.go +++ b/internal/api/core/v1alpha3/conversion.go @@ -614,6 +614,16 @@ func (dst *MachinePool) ConvertFrom(srcRaw conversion.Hub) error { return utilconversion.MarshalData(src, dst) } +func Convert_v1alpha3_MachineDeploymentStrategy_To_v1beta2_MachinePoolStrategy(in *MachineDeploymentStrategy, out *clusterv1.MachinePoolStrategy, _ apimachineryconversion.Scope) error { + // Subfields differ in v1beta2, no conversion possible + out.Remediation = nil + return nil +} + +func Convert_v1beta2_MachinePoolStrategy_To_v1alpha3_MachineDeploymentStrategy(in *clusterv1.MachinePoolStrategy, out *MachineDeploymentStrategy, _ apimachineryconversion.Scope) error { + return nil +} + func Convert_v1beta2_MachineSetStatus_To_v1alpha3_MachineSetStatus(in *clusterv1.MachineSetStatus, out *MachineSetStatus, _ apimachineryconversion.Scope) error { // Status.Conditions was introduced in v1alpha4, thus requiring a custom conversion function; the values is going to be preserved in an annotation thus allowing roundtrip without loosing informations // V1Beta2 was added in v1beta1. @@ -796,7 +806,10 @@ func Convert_v1alpha3_MachineSetSpec_To_v1beta2_MachineSetSpec(in *MachineSetSpe return autoConvert_v1alpha3_MachineSetSpec_To_v1beta2_MachineSetSpec(in, out, s) } -// Convert_v1alpha3_MachinePoolSpec_To_v1beta2_MachinePoolSpec is an autogenerated conversion function. +func Convert_v1beta2_MachinePoolSpec_To_v1alpha3_MachinePoolSpec(in *clusterv1.MachinePoolSpec, out *MachinePoolSpec, s apimachineryconversion.Scope) error { + return autoConvert_v1beta2_MachinePoolSpec_To_v1alpha3_MachinePoolSpec(in, out, s) +} + func Convert_v1alpha3_MachinePoolSpec_To_v1beta2_MachinePoolSpec(in *MachinePoolSpec, out *clusterv1.MachinePoolSpec, s apimachineryconversion.Scope) error { return autoConvert_v1alpha3_MachinePoolSpec_To_v1beta2_MachinePoolSpec(in, out, s) } diff --git a/internal/api/core/v1alpha3/conversion_test.go b/internal/api/core/v1alpha3/conversion_test.go index b3746f778044..8d5fc3cc694e 100644 --- a/internal/api/core/v1alpha3/conversion_test.go +++ b/internal/api/core/v1alpha3/conversion_test.go @@ -86,9 +86,10 @@ func TestFuzzyConversion(t *testing.T) { })) t.Run("for MachinePool", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ - Hub: &clusterv1.MachinePool{}, - Spoke: &MachinePool{}, - FuzzerFuncs: []fuzzer.FuzzerFuncs{MachinePoolFuzzFuncs}, + Hub: &clusterv1.MachinePool{}, + HubAfterMutation: machinePoolHubAfterMutation, + Spoke: &MachinePool{}, + FuzzerFuncs: []fuzzer.FuzzerFuncs{MachinePoolFuzzFuncs}, })) } @@ -118,6 +119,13 @@ func hubMachineSpec(in *clusterv1.MachineSpec, c randfill.Continue) { } } +func hubMachinePoolSpec(in *clusterv1.MachinePoolSpec, c randfill.Continue) { + c.FillNoCustom(in) + + // Subfields differ in v1beta2, no conversion possible + in.Strategy = nil +} + func hubMachineStatus(in *clusterv1.MachineStatus, c randfill.Continue) { c.FillNoCustom(in) // Drop empty structs with only omit empty fields. @@ -303,6 +311,15 @@ func clusterSpokeAfterMutation(c conversion.Convertible) { cluster.Status.Conditions = tmp } +// machinePoolHubAfterMutation modifies the spoke version of the MachinePool such that it can pass an equality test in the +// spoke-hub-spoke conversion scenario. +func machinePoolHubAfterMutation(c conversion.Hub) { + mp := c.(*clusterv1.MachinePool) + + // Subfields differ in v1beta2, no conversion possible + mp.Spec.Strategy = nil +} + func ClusterFuncs(_ runtimeserializer.CodecFactory) []interface{} { return []interface{}{ hubClusterSpec, @@ -422,6 +439,7 @@ func MachinePoolFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { spokeBootstrap, spokeObjectMeta, spokeMachinePoolSpec, + hubMachinePoolSpec, hubMachinePoolStatus, spokeMachineSpec, } @@ -460,8 +478,7 @@ func spokeMachinePool(in *MachinePool, c randfill.Continue) { func spokeMachinePoolSpec(in *MachinePoolSpec, c randfill.Continue) { c.FillNoCustom(in) - // These fields have been removed in v1beta1 - // data is going to be lost, so we're forcing zero values here. + // Subfields differ in v1beta2, no conversion possible in.Strategy = nil } diff --git a/internal/api/core/v1alpha3/zz_generated.conversion.go b/internal/api/core/v1alpha3/zz_generated.conversion.go index 44cbe543bc73..1c96cf41cace 100644 --- a/internal/api/core/v1alpha3/zz_generated.conversion.go +++ b/internal/api/core/v1alpha3/zz_generated.conversion.go @@ -190,11 +190,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.MachinePoolSpec)(nil), (*MachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_MachinePoolSpec_To_v1alpha3_MachinePoolSpec(a.(*v1beta2.MachinePoolSpec), b.(*MachinePoolSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*MachineRollingUpdateDeployment)(nil), (*v1beta2.MachineRollingUpdateDeployment)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha3_MachineRollingUpdateDeployment_To_v1beta2_MachineRollingUpdateDeployment(a.(*MachineRollingUpdateDeployment), b.(*v1beta2.MachineRollingUpdateDeployment), scope) }); err != nil { @@ -275,6 +270,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*MachineDeploymentStrategy)(nil), (*v1beta2.MachinePoolStrategy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_MachineDeploymentStrategy_To_v1beta2_MachinePoolStrategy(a.(*MachineDeploymentStrategy), b.(*v1beta2.MachinePoolStrategy), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*MachineHealthCheckSpec)(nil), (*v1beta2.MachineHealthCheckSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha3_MachineHealthCheckSpec_To_v1beta2_MachineHealthCheckSpec(a.(*MachineHealthCheckSpec), b.(*v1beta2.MachineHealthCheckSpec), scope) }); err != nil { @@ -355,11 +355,21 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.MachinePoolSpec)(nil), (*MachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_MachinePoolSpec_To_v1alpha3_MachinePoolSpec(a.(*v1beta2.MachinePoolSpec), b.(*MachinePoolSpec), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.MachinePoolStatus)(nil), (*MachinePoolStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_MachinePoolStatus_To_v1alpha3_MachinePoolStatus(a.(*v1beta2.MachinePoolStatus), b.(*MachinePoolStatus), scope) }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.MachinePoolStrategy)(nil), (*MachineDeploymentStrategy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_MachinePoolStrategy_To_v1alpha3_MachineDeploymentStrategy(a.(*v1beta2.MachinePoolStrategy), b.(*MachineDeploymentStrategy), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.MachinePool)(nil), (*MachinePool)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_MachinePool_To_v1alpha3_MachinePool(a.(*v1beta2.MachinePool), b.(*MachinePool), scope) }); err != nil { @@ -1198,7 +1208,15 @@ func autoConvert_v1alpha3_MachinePoolSpec_To_v1beta2_MachinePoolSpec(in *Machine if err := Convert_v1alpha3_MachineTemplateSpec_To_v1beta2_MachineTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } - // WARNING: in.Strategy requires manual conversion: does not exist in peer-type + if in.Strategy != nil { + in, out := &in.Strategy, &out.Strategy + *out = new(v1beta2.MachinePoolStrategy) + if err := Convert_v1alpha3_MachineDeploymentStrategy_To_v1beta2_MachinePoolStrategy(*in, *out, s); err != nil { + return err + } + } else { + out.Strategy = nil + } // WARNING: in.MinReadySeconds requires manual conversion: does not exist in peer-type out.ProviderIDList = *(*[]string)(unsafe.Pointer(&in.ProviderIDList)) out.FailureDomains = *(*[]string)(unsafe.Pointer(&in.FailureDomains)) @@ -1213,14 +1231,18 @@ func autoConvert_v1beta2_MachinePoolSpec_To_v1alpha3_MachinePoolSpec(in *v1beta2 } out.ProviderIDList = *(*[]string)(unsafe.Pointer(&in.ProviderIDList)) out.FailureDomains = *(*[]string)(unsafe.Pointer(&in.FailureDomains)) + if in.Strategy != nil { + in, out := &in.Strategy, &out.Strategy + *out = new(MachineDeploymentStrategy) + if err := Convert_v1beta2_MachinePoolStrategy_To_v1alpha3_MachineDeploymentStrategy(*in, *out, s); err != nil { + return err + } + } else { + out.Strategy = nil + } return nil } -// Convert_v1beta2_MachinePoolSpec_To_v1alpha3_MachinePoolSpec is an autogenerated conversion function. -func Convert_v1beta2_MachinePoolSpec_To_v1alpha3_MachinePoolSpec(in *v1beta2.MachinePoolSpec, out *MachinePoolSpec, s conversion.Scope) error { - return autoConvert_v1beta2_MachinePoolSpec_To_v1alpha3_MachinePoolSpec(in, out, s) -} - func autoConvert_v1alpha3_MachinePoolStatus_To_v1beta2_MachinePoolStatus(in *MachinePoolStatus, out *v1beta2.MachinePoolStatus, s conversion.Scope) error { out.NodeRefs = *(*[]corev1.ObjectReference)(unsafe.Pointer(&in.NodeRefs)) if err := v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { diff --git a/internal/api/core/v1alpha4/conversion.go b/internal/api/core/v1alpha4/conversion.go index 693cefd324b6..5440ec6e31f2 100644 --- a/internal/api/core/v1alpha4/conversion.go +++ b/internal/api/core/v1alpha4/conversion.go @@ -728,6 +728,10 @@ func Convert_v1alpha4_LocalObjectTemplate_To_v1beta2_InfrastructureClass(in *Loc return Convert_v1alpha4_LocalObjectTemplate_To_v1beta2_ClusterClassTemplate(in, &out.ClusterClassTemplate, s) } +func Convert_v1beta2_MachinePoolSpec_To_v1alpha4_MachinePoolSpec(in *clusterv1.MachinePoolSpec, out *MachinePoolSpec, s apimachineryconversion.Scope) error { + return autoConvert_v1beta2_MachinePoolSpec_To_v1alpha4_MachinePoolSpec(in, out, s) +} + func Convert_v1beta2_MachineSpec_To_v1alpha4_MachineSpec(in *clusterv1.MachineSpec, out *MachineSpec, s apimachineryconversion.Scope) error { // spec.nodeDeletionTimeout was added in v1beta1. // ReadinessGates was added in v1beta1. diff --git a/internal/api/core/v1alpha4/conversion_test.go b/internal/api/core/v1alpha4/conversion_test.go index b20cff002cef..8bea23a043e9 100644 --- a/internal/api/core/v1alpha4/conversion_test.go +++ b/internal/api/core/v1alpha4/conversion_test.go @@ -461,11 +461,19 @@ func MachinePoolFuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} { return []interface{}{ hubMachineSpec, spokeMachinePool, + hubMachinePoolSpec, hubMachinePoolStatus, spokeMachineSpec, } } +func hubMachinePoolSpec(in *clusterv1.MachinePoolSpec, c randfill.Continue) { + c.FillNoCustom(in) + + // Subfields differ in v1beta2, no conversion possible + in.Strategy = nil +} + func hubMachinePoolStatus(in *clusterv1.MachinePoolStatus, c randfill.Continue) { c.FillNoCustom(in) // Always create struct with at least one mandatory fields. diff --git a/internal/api/core/v1alpha4/zz_generated.conversion.go b/internal/api/core/v1alpha4/zz_generated.conversion.go index 3a3b470ef101..32290dc6b517 100644 --- a/internal/api/core/v1alpha4/zz_generated.conversion.go +++ b/internal/api/core/v1alpha4/zz_generated.conversion.go @@ -250,11 +250,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta2.MachinePoolSpec)(nil), (*MachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta2_MachinePoolSpec_To_v1alpha4_MachinePoolSpec(a.(*v1beta2.MachinePoolSpec), b.(*MachinePoolSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*MachineRollingUpdateDeployment)(nil), (*v1beta2.MachineRollingUpdateDeployment)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha4_MachineRollingUpdateDeployment_To_v1beta2_MachineRollingUpdateDeployment(a.(*MachineRollingUpdateDeployment), b.(*v1beta2.MachineRollingUpdateDeployment), scope) }); err != nil { @@ -480,6 +475,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1beta2.MachinePoolSpec)(nil), (*MachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta2_MachinePoolSpec_To_v1alpha4_MachinePoolSpec(a.(*v1beta2.MachinePoolSpec), b.(*MachinePoolSpec), scope) + }); err != nil { + return err + } if err := s.AddConversionFunc((*v1beta2.MachinePoolStatus)(nil), (*MachinePoolStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta2_MachinePoolStatus_To_v1alpha4_MachinePoolStatus(a.(*v1beta2.MachinePoolStatus), b.(*MachinePoolStatus), scope) }); err != nil { @@ -1643,14 +1643,10 @@ func autoConvert_v1beta2_MachinePoolSpec_To_v1alpha4_MachinePoolSpec(in *v1beta2 } out.ProviderIDList = *(*[]string)(unsafe.Pointer(&in.ProviderIDList)) out.FailureDomains = *(*[]string)(unsafe.Pointer(&in.FailureDomains)) + // WARNING: in.Strategy requires manual conversion: does not exist in peer-type return nil } -// Convert_v1beta2_MachinePoolSpec_To_v1alpha4_MachinePoolSpec is an autogenerated conversion function. -func Convert_v1beta2_MachinePoolSpec_To_v1alpha4_MachinePoolSpec(in *v1beta2.MachinePoolSpec, out *MachinePoolSpec, s conversion.Scope) error { - return autoConvert_v1beta2_MachinePoolSpec_To_v1alpha4_MachinePoolSpec(in, out, s) -} - func autoConvert_v1alpha4_MachinePoolStatus_To_v1beta2_MachinePoolStatus(in *MachinePoolStatus, out *v1beta2.MachinePoolStatus, s conversion.Scope) error { out.NodeRefs = *(*[]corev1.ObjectReference)(unsafe.Pointer(&in.NodeRefs)) if err := v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil {