diff --git a/apis/apps/v1/cluster_types.go b/apis/apps/v1/cluster_types.go index 5074b278783..68b4c0d10ff 100644 --- a/apis/apps/v1/cluster_types.go +++ b/apis/apps/v1/cluster_types.go @@ -438,6 +438,11 @@ type ClusterComponentSpec struct { // +optional PodUpdatePolicy *PodUpdatePolicyType `json:"podUpdatePolicy,omitempty"` + // Provides fine-grained control over the spec update process of all instances. + // + // +optional + InstanceUpdateStrategy *InstanceUpdateStrategy `json:"instanceUpdateStrategy,omitempty"` + // Allows for the customization of configuration values for each instance within a Component. // An instance represent a single replica (Pod and associated K8s resources like PVCs, Services, and ConfigMaps). // While instances typically share a common configuration as defined in the ClusterComponentSpec, diff --git a/apis/apps/v1/component_types.go b/apis/apps/v1/component_types.go index fdf3e5af9a4..ea8df5c6ce9 100644 --- a/apis/apps/v1/component_types.go +++ b/apis/apps/v1/component_types.go @@ -212,6 +212,11 @@ type ComponentSpec struct { // +optional PodUpdatePolicy *PodUpdatePolicyType `json:"podUpdatePolicy,omitempty"` + // Provides fine-grained control over the spec update process of all instances. + // + // +optional + InstanceUpdateStrategy *InstanceUpdateStrategy `json:"instanceUpdateStrategy,omitempty"` + // Specifies the scheduling policy for the Component. // // +optional diff --git a/apis/apps/v1/types.go b/apis/apps/v1/types.go index 0ad03156a19..01190966d94 100644 --- a/apis/apps/v1/types.go +++ b/apis/apps/v1/types.go @@ -18,6 +18,7 @@ package v1 import ( corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) const ( @@ -526,6 +527,59 @@ const ( PreferInPlacePodUpdatePolicyType PodUpdatePolicyType = "PreferInPlace" ) +// InstanceUpdateStrategy defines fine-grained control over the spec update process of all instances. +type InstanceUpdateStrategy struct { + // Indicates the type of the update strategy. + // Default is RollingUpdate. + // + // +optional + Type InstanceUpdateStrategyType `json:"type,omitempty"` + + // Specifies how the rolling update should be applied. + // + // +optional + RollingUpdate *RollingUpdate `json:"rollingUpdate,omitempty"` +} + +// InstanceUpdateStrategyType is a string enumeration type that enumerates +// all possible update strategies for the KubeBlocks controllers. +// +// +enum +// +kubebuilder:validation:Enum={RollingUpdate,OnDelete} +type InstanceUpdateStrategyType string + +const ( + // RollingUpdateStrategyType indicates that update will be + // applied to all Instances with respect to the workload + // ordering constraints. + RollingUpdateStrategyType InstanceUpdateStrategyType = "RollingUpdate" + // OnDeleteStrategyType indicates that ordered rolling restarts are disabled. Instances are recreated + // when they are manually deleted. + OnDeleteStrategyType InstanceUpdateStrategyType = "OnDelete" +) + +// RollingUpdate specifies how the rolling update should be applied. +type RollingUpdate struct { + // Indicates the number of instances that should be updated during a rolling update. + // The remaining instances will remain untouched. This is helpful in defining how many instances + // should participate in the update process. + // Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + // Absolute number is calculated from percentage by rounding up. + // The default value is ComponentSpec.Replicas (i.e., update all instances). + // + // +optional + Replicas *intstr.IntOrString `json:"replicas,omitempty"` + + // The maximum number of instances that can be unavailable during the update. + // Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + // Absolute number is calculated from percentage by rounding up. This can not be 0. + // Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + // it will be counted towards MaxUnavailable. + // + // +optional + MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"` +} + type SchedulingPolicy struct { // If specified, the Pod will be dispatched by specified scheduler. // If not specified, the Pod will be dispatched by default scheduler. diff --git a/apis/apps/v1/zz_generated.deepcopy.go b/apis/apps/v1/zz_generated.deepcopy.go index a8ca3cf4d73..b3ef440bc1c 100644 --- a/apis/apps/v1/zz_generated.deepcopy.go +++ b/apis/apps/v1/zz_generated.deepcopy.go @@ -351,6 +351,11 @@ func (in *ClusterComponentSpec) DeepCopyInto(out *ClusterComponentSpec) { *out = new(PodUpdatePolicyType) **out = **in } + if in.InstanceUpdateStrategy != nil { + in, out := &in.InstanceUpdateStrategy, &out.InstanceUpdateStrategy + *out = new(InstanceUpdateStrategy) + (*in).DeepCopyInto(*out) + } if in.Instances != nil { in, out := &in.Instances, &out.Instances *out = make([]InstanceTemplate, len(*in)) @@ -1537,6 +1542,11 @@ func (in *ComponentSpec) DeepCopyInto(out *ComponentSpec) { *out = new(PodUpdatePolicyType) **out = **in } + if in.InstanceUpdateStrategy != nil { + in, out := &in.InstanceUpdateStrategy, &out.InstanceUpdateStrategy + *out = new(InstanceUpdateStrategy) + (*in).DeepCopyInto(*out) + } if in.SchedulingPolicy != nil { in, out := &in.SchedulingPolicy, &out.SchedulingPolicy *out = new(SchedulingPolicy) @@ -2227,6 +2237,26 @@ func (in *InstanceTemplate) DeepCopy() *InstanceTemplate { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceUpdateStrategy) DeepCopyInto(out *InstanceUpdateStrategy) { + *out = *in + if in.RollingUpdate != nil { + in, out := &in.RollingUpdate, &out.RollingUpdate + *out = new(RollingUpdate) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceUpdateStrategy. +func (in *InstanceUpdateStrategy) DeepCopy() *InstanceUpdateStrategy { + if in == nil { + return nil + } + out := new(InstanceUpdateStrategy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Issuer) DeepCopyInto(out *Issuer) { *out = *in @@ -2534,6 +2564,31 @@ func (in *RoledVar) DeepCopy() *RoledVar { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RollingUpdate) DeepCopyInto(out *RollingUpdate) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(intstr.IntOrString) + **out = **in + } + if in.MaxUnavailable != nil { + in, out := &in.MaxUnavailable, &out.MaxUnavailable + *out = new(intstr.IntOrString) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RollingUpdate. +func (in *RollingUpdate) DeepCopy() *RollingUpdate { + if in == nil { + return nil + } + out := new(RollingUpdate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SchedulingPolicy) DeepCopyInto(out *SchedulingPolicy) { *out = *in diff --git a/apis/workloads/v1/instanceset_types.go b/apis/workloads/v1/instanceset_types.go index b041b4bd71a..e3913468edb 100644 --- a/apis/workloads/v1/instanceset_types.go +++ b/apis/workloads/v1/instanceset_types.go @@ -184,13 +184,20 @@ type InstanceSetSpec struct { // +optional PodUpdatePolicy PodUpdatePolicyType `json:"podUpdatePolicy,omitempty"` - // Indicates the StatefulSetUpdateStrategy that will be - // employed to update Pods in the InstanceSet when a revision is made to - // Template. - // UpdateStrategy.Type will be set to appsv1.OnDeleteStatefulSetStrategyType if MemberUpdateStrategy is not nil + // Provides fine-grained control over the spec update process of all instances. // - // Note: This field will be removed in future version. - UpdateStrategy appsv1.StatefulSetUpdateStrategy `json:"updateStrategy,omitempty"` + // +optional + InstanceUpdateStrategy *InstanceUpdateStrategy `json:"instanceUpdateStrategy,omitempty"` + + // Members(Pods) update strategy. + // + // - serial: update Members one by one that guarantee minimum component unavailable time. + // - parallel: force parallel + // - bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time. + // + // +kubebuilder:validation:Enum={Serial,Parallel,BestEffortParallel} + // +optional + MemberUpdateStrategy *MemberUpdateStrategy `json:"memberUpdateStrategy,omitempty"` // A list of roles defined in the system. Instanceset obtains role through pods' role label `kubeblocks.io/role`. // @@ -207,16 +214,6 @@ type InstanceSetSpec struct { // +optional TemplateVars map[string]string `json:"templateVars,omitempty"` - // Members(Pods) update strategy. - // - // - serial: update Members one by one that guarantee minimum component unavailable time. - // - bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time. - // - parallel: force parallel - // - // +kubebuilder:validation:Enum={Serial,BestEffortParallel,Parallel} - // +optional - MemberUpdateStrategy *MemberUpdateStrategy `json:"memberUpdateStrategy,omitempty"` - // Indicates that the InstanceSet is paused, meaning the reconciliation of this InstanceSet object will be paused. // +optional Paused bool `json:"paused,omitempty"` @@ -308,16 +305,29 @@ type InstanceSetStatus struct { // +kubebuilder:object:generate=false type InstanceTemplate = kbappsv1.InstanceTemplate -type PodUpdatePolicyType string +// PodUpdatePolicyType indicates how pods should be updated +// +// +kubebuilder:object:generate=false +type PodUpdatePolicyType = kbappsv1.PodUpdatePolicyType -const ( - // StrictInPlacePodUpdatePolicyType indicates that only allows in-place upgrades. - // Any attempt to modify other fields will be rejected. - StrictInPlacePodUpdatePolicyType PodUpdatePolicyType = "StrictInPlace" +// InstanceUpdateStrategy defines fine-grained control over the spec update process of all instances. +// +// +kubebuilder:object:generate=false +type InstanceUpdateStrategy = kbappsv1.InstanceUpdateStrategy - // PreferInPlacePodUpdatePolicyType indicates that we will first attempt an in-place upgrade of the Pod. - // If that fails, it will fall back to the ReCreate, where pod will be recreated. - PreferInPlacePodUpdatePolicyType PodUpdatePolicyType = "PreferInPlace" +// RollingUpdate specifies how the rolling update should be applied. +// +// +kubebuilder:object:generate=false +type RollingUpdate = kbappsv1.RollingUpdate + +// MemberUpdateStrategy defines Cluster Component update strategy. +// +enum +type MemberUpdateStrategy string + +const ( + SerialUpdateStrategy MemberUpdateStrategy = "Serial" + ParallelUpdateStrategy MemberUpdateStrategy = "Parallel" + BestEffortParallelUpdateStrategy MemberUpdateStrategy = "BestEffortParallel" ) // ReplicaRole represents a role that can be assigned to a component instance, defining its behavior and responsibilities. @@ -404,16 +414,6 @@ type MembershipReconfiguration struct { Switchover *kbappsv1.Action `json:"switchover,omitempty"` } -// MemberUpdateStrategy defines Cluster Component update strategy. -// +enum -type MemberUpdateStrategy string - -const ( - SerialUpdateStrategy MemberUpdateStrategy = "Serial" - BestEffortParallelUpdateStrategy MemberUpdateStrategy = "BestEffortParallel" - ParallelUpdateStrategy MemberUpdateStrategy = "Parallel" -) - type MemberStatus struct { // Represents the name of the pod. // diff --git a/apis/workloads/v1/zz_generated.deepcopy.go b/apis/workloads/v1/zz_generated.deepcopy.go index 60f81300d75..1d265941b5c 100644 --- a/apis/workloads/v1/zz_generated.deepcopy.go +++ b/apis/workloads/v1/zz_generated.deepcopy.go @@ -154,7 +154,16 @@ func (in *InstanceSetSpec) DeepCopyInto(out *InstanceSetSpec) { *out = new(intstr.IntOrString) **out = **in } - in.UpdateStrategy.DeepCopyInto(&out.UpdateStrategy) + if in.InstanceUpdateStrategy != nil { + in, out := &in.InstanceUpdateStrategy, &out.InstanceUpdateStrategy + *out = new(appsv1.InstanceUpdateStrategy) + (*in).DeepCopyInto(*out) + } + if in.MemberUpdateStrategy != nil { + in, out := &in.MemberUpdateStrategy, &out.MemberUpdateStrategy + *out = new(MemberUpdateStrategy) + **out = **in + } if in.Roles != nil { in, out := &in.Roles, &out.Roles *out = make([]appsv1.ReplicaRole, len(*in)) @@ -172,11 +181,6 @@ func (in *InstanceSetSpec) DeepCopyInto(out *InstanceSetSpec) { (*out)[key] = val } } - if in.MemberUpdateStrategy != nil { - in, out := &in.MemberUpdateStrategy, &out.MemberUpdateStrategy - *out = new(MemberUpdateStrategy) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceSetSpec. diff --git a/apis/workloads/v1alpha1/instanceset_conversion.go b/apis/workloads/v1alpha1/instanceset_conversion.go index 78a14aeb3be..775a05f35aa 100644 --- a/apis/workloads/v1alpha1/instanceset_conversion.go +++ b/apis/workloads/v1alpha1/instanceset_conversion.go @@ -22,7 +22,9 @@ package v1alpha1 import ( "github.com/jinzhu/copier" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/json" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/conversion" workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" @@ -43,6 +45,7 @@ func (r *InstanceSet) ConvertTo(dstRaw conversion.Hub) error { if err := copier.Copy(&dst.Spec, &r.Spec); err != nil { return err } + r.changesToInstanceSet(dst) // status if err := copier.Copy(&dst.Status, &r.Status); err != nil { @@ -67,6 +70,7 @@ func (r *InstanceSet) ConvertFrom(srcRaw conversion.Hub) error { if err := copier.Copy(&r.Spec, &src.Spec); err != nil { return err } + r.changesFromInstanceSet(src) // status if err := copier.Copy(&r.Status, &src.Status); err != nil { @@ -80,22 +84,12 @@ func (r *InstanceSet) ConvertFrom(srcRaw conversion.Hub) error { } func (r *InstanceSet) incrementConvertTo(dstRaw metav1.Object) error { - if r.Spec.RoleProbe == nil && r.Spec.UpdateStrategy == nil { + if r.Spec.RoleProbe == nil && r.Spec.Credential == nil { return nil } - // changed instanceConvert := instanceSetConverter{ - RoleProbe: r.Spec.RoleProbe, - UpdateStrategy: r.Spec.UpdateStrategy, - Credential: r.Spec.Credential, - } - - if r.Spec.UpdateStrategy == nil || r.Spec.UpdateStrategy.MemberUpdateStrategy == nil { - // 1. set default update strategy - updateStrategy := SerialUpdateStrategy - instanceConvert.UpdateStrategy = &InstanceUpdateStrategy{ - MemberUpdateStrategy: &updateStrategy, - } + RoleProbe: r.Spec.RoleProbe, + Credential: r.Spec.Credential, } bytes, err := json.Marshal(instanceConvert) if err != nil { @@ -121,13 +115,73 @@ func (r *InstanceSet) incrementConvertFrom(srcRaw metav1.Object) error { } delete(srcRaw.GetAnnotations(), kbIncrementConverterAK) r.Spec.RoleProbe = instanceConvert.RoleProbe - r.Spec.UpdateStrategy = instanceConvert.UpdateStrategy r.Spec.Credential = instanceConvert.Credential return nil } type instanceSetConverter struct { - RoleProbe *RoleProbe `json:"roleProbe,omitempty"` - UpdateStrategy *InstanceUpdateStrategy `json:"updateStrategy,omitempty"` - Credential *Credential `json:"credential,omitempty"` + RoleProbe *RoleProbe `json:"roleProbe,omitempty"` + Credential *Credential `json:"credential,omitempty"` +} + +func (r *InstanceSet) changesToInstanceSet(its *workloadsv1.InstanceSet) { + // changed: + // spec + // updateStrategy.partition -> instanceUpdateStrategy.rollingUpdate.replicas + // updateStrategy.maxUnavailable -> instanceUpdateStrategy.rollingUpdate.maxUnavailable + // updateStrategy.memberUpdateStrategy -> memberUpdateStrategy + if its.Spec.InstanceUpdateStrategy == nil { + its.Spec.InstanceUpdateStrategy = &workloadsv1.InstanceUpdateStrategy{} + } + initRollingUpdate := func() { + if its.Spec.InstanceUpdateStrategy.RollingUpdate == nil { + its.Spec.InstanceUpdateStrategy.RollingUpdate = &workloadsv1.RollingUpdate{} + } + } + setMemberUpdateStrategy := func(strategy *MemberUpdateStrategy) { + if strategy == nil { + return + } + its.Spec.MemberUpdateStrategy = (*workloadsv1.MemberUpdateStrategy)(strategy) + } + setMemberUpdateStrategy(r.Spec.MemberUpdateStrategy) + if r.Spec.UpdateStrategy != nil { + setMemberUpdateStrategy(r.Spec.UpdateStrategy.MemberUpdateStrategy) + if r.Spec.UpdateStrategy.Partition != nil { + initRollingUpdate() + replicas := intstr.FromInt32(*r.Spec.UpdateStrategy.Partition) + its.Spec.InstanceUpdateStrategy.RollingUpdate.Replicas = &replicas + } + if r.Spec.UpdateStrategy.MaxUnavailable != nil { + initRollingUpdate() + its.Spec.InstanceUpdateStrategy.RollingUpdate.MaxUnavailable = r.Spec.UpdateStrategy.MaxUnavailable + } + } +} + +func (r *InstanceSet) changesFromInstanceSet(its *workloadsv1.InstanceSet) { + // changed: + // spec + // updateStrategy.partition -> instanceUpdateStrategy.rollingUpdate.replicas + // updateStrategy.maxUnavailable -> instanceUpdateStrategy.rollingUpdate.maxUnavailable + // updateStrategy.memberUpdateStrategy -> memberUpdateStrategy + r.Spec.MemberUpdateStrategy = (*MemberUpdateStrategy)(its.Spec.MemberUpdateStrategy) + if its.Spec.InstanceUpdateStrategy == nil { + return + } + if its.Spec.InstanceUpdateStrategy.RollingUpdate == nil { + return + } + if r.Spec.UpdateStrategy == nil { + r.Spec.UpdateStrategy = &InstanceUpdateStrategy{ + MemberUpdateStrategy: r.Spec.MemberUpdateStrategy, + } + } + if its.Spec.InstanceUpdateStrategy.RollingUpdate.Replicas != nil { + partition, _ := intstr.GetScaledValueFromIntOrPercent(its.Spec.InstanceUpdateStrategy.RollingUpdate.Replicas, int(*its.Spec.Replicas), false) + r.Spec.UpdateStrategy.Partition = pointer.Int32(int32(partition)) + } + if its.Spec.InstanceUpdateStrategy.RollingUpdate.MaxUnavailable != nil { + r.Spec.UpdateStrategy.MaxUnavailable = its.Spec.InstanceUpdateStrategy.RollingUpdate.MaxUnavailable + } } diff --git a/config/crd/bases/apps.kubeblocks.io_clusters.yaml b/config/crd/bases/apps.kubeblocks.io_clusters.yaml index 327dba49aed..8a04343dc05 100644 --- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml +++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml @@ -713,6 +713,47 @@ spec: - name type: object type: array + instanceUpdateStrategy: + description: Provides fine-grained control over the spec update + process of all instances. + properties: + rollingUpdate: + description: Specifies how the rolling update should be + applied. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of instances that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. This can not be 0. + Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + it will be counted towards MaxUnavailable. + x-kubernetes-int-or-string: true + replicas: + anyOf: + - type: integer + - type: string + description: |- + Indicates the number of instances that should be updated during a rolling update. + The remaining instances will remain untouched. This is helpful in defining how many instances + should participate in the update process. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. + The default value is ComponentSpec.Replicas (i.e., update all instances). + x-kubernetes-int-or-string: true + type: object + type: + description: |- + Indicates the type of the update strategy. + Default is RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object instances: description: |- Allows for the customization of configuration values for each instance within a Component. @@ -7853,6 +7894,47 @@ spec: - name type: object type: array + instanceUpdateStrategy: + description: Provides fine-grained control over the spec + update process of all instances. + properties: + rollingUpdate: + description: Specifies how the rolling update should + be applied. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of instances that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. This can not be 0. + Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + it will be counted towards MaxUnavailable. + x-kubernetes-int-or-string: true + replicas: + anyOf: + - type: integer + - type: string + description: |- + Indicates the number of instances that should be updated during a rolling update. + The remaining instances will remain untouched. This is helpful in defining how many instances + should participate in the update process. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. + The default value is ComponentSpec.Replicas (i.e., update all instances). + x-kubernetes-int-or-string: true + type: object + type: + description: |- + Indicates the type of the update strategy. + Default is RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object instances: description: |- Allows for the customization of configuration values for each instance within a Component. diff --git a/config/crd/bases/apps.kubeblocks.io_components.yaml b/config/crd/bases/apps.kubeblocks.io_components.yaml index 3c310ba9e94..acc8a6146b5 100644 --- a/config/crd/bases/apps.kubeblocks.io_components.yaml +++ b/config/crd/bases/apps.kubeblocks.io_components.yaml @@ -581,6 +581,46 @@ spec: - name type: object type: array + instanceUpdateStrategy: + description: Provides fine-grained control over the spec update process + of all instances. + properties: + rollingUpdate: + description: Specifies how the rolling update should be applied. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of instances that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. This can not be 0. + Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + it will be counted towards MaxUnavailable. + x-kubernetes-int-or-string: true + replicas: + anyOf: + - type: integer + - type: string + description: |- + Indicates the number of instances that should be updated during a rolling update. + The remaining instances will remain untouched. This is helpful in defining how many instances + should participate in the update process. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. + The default value is ComponentSpec.Replicas (i.e., update all instances). + x-kubernetes-int-or-string: true + type: object + type: + description: |- + Indicates the type of the update strategy. + Default is RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object instances: description: |- Allows for the customization of configuration values for each instance within a Component. diff --git a/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml b/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml index b3a3c00a8c5..547a789fb77 100644 --- a/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml +++ b/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml @@ -94,6 +94,46 @@ spec: type: object type: array type: object + instanceUpdateStrategy: + description: Provides fine-grained control over the spec update process + of all instances. + properties: + rollingUpdate: + description: Specifies how the rolling update should be applied. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of instances that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. This can not be 0. + Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + it will be counted towards MaxUnavailable. + x-kubernetes-int-or-string: true + replicas: + anyOf: + - type: integer + - type: string + description: |- + Indicates the number of instances that should be updated during a rolling update. + The remaining instances will remain untouched. This is helpful in defining how many instances + should participate in the update process. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. + The default value is ComponentSpec.Replicas (i.e., update all instances). + x-kubernetes-int-or-string: true + type: object + type: + description: |- + Indicates the type of the update strategy. + Default is RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object instances: description: |- Overrides values in default Template. @@ -1530,12 +1570,12 @@ spec: - serial: update Members one by one that guarantee minimum component unavailable time. - - bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time. - parallel: force parallel + - bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time. enum: - Serial - - BestEffortParallel - Parallel + - BestEffortParallel type: string membershipReconfiguration: description: Provides actions to do membership dynamic reconfiguration. @@ -9654,48 +9694,6 @@ spec: type: string description: Provides variables which are used to call Actions. type: object - updateStrategy: - description: |- - Indicates the StatefulSetUpdateStrategy that will be - employed to update Pods in the InstanceSet when a revision is made to - Template. - UpdateStrategy.Type will be set to appsv1.OnDeleteStatefulSetStrategyType if MemberUpdateStrategy is not nil - - - Note: This field will be removed in future version. - properties: - rollingUpdate: - description: RollingUpdate is used to communicate parameters when - Type is RollingUpdateStatefulSetStrategyType. - properties: - maxUnavailable: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding up. This can not be 0. - Defaults to 1. This field is alpha-level and is only honored by servers that enable the - MaxUnavailableStatefulSet feature. The field applies to all pods in the range 0 to - Replicas-1. That means if there is any unavailable pod in the range 0 to Replicas-1, it - will be counted towards MaxUnavailable. - x-kubernetes-int-or-string: true - partition: - description: |- - Partition indicates the ordinal at which the StatefulSet should be partitioned - for updates. During a rolling update, all pods from ordinal Replicas-1 to - Partition are updated. All pods from ordinal Partition-1 to 0 remain untouched. - This is helpful in being able to do a canary based deployment. The default value is 0. - format: int32 - type: integer - type: object - type: - description: |- - Type indicates the type of the StatefulSetUpdateStrategy. - Default is RollingUpdate. - type: string - type: object volumeClaimTemplates: description: |- Specifies a list of PersistentVolumeClaim templates that define the storage requirements for each replica. diff --git a/controllers/apps/cluster/transformer_cluster_component.go b/controllers/apps/cluster/transformer_cluster_component.go index 3899488672a..6706e0d3d44 100644 --- a/controllers/apps/cluster/transformer_cluster_component.go +++ b/controllers/apps/cluster/transformer_cluster_component.go @@ -207,6 +207,7 @@ func copyAndMergeComponent(oldCompObj, newCompObj *appsv1.Component) *appsv1.Com compObjCopy.Spec.ServiceAccountName = compProto.Spec.ServiceAccountName compObjCopy.Spec.ParallelPodManagementConcurrency = compProto.Spec.ParallelPodManagementConcurrency compObjCopy.Spec.PodUpdatePolicy = compProto.Spec.PodUpdatePolicy + compObjCopy.Spec.InstanceUpdateStrategy = compProto.Spec.InstanceUpdateStrategy compObjCopy.Spec.SchedulingPolicy = compProto.Spec.SchedulingPolicy compObjCopy.Spec.TLSConfig = compProto.Spec.TLSConfig compObjCopy.Spec.Instances = compProto.Spec.Instances diff --git a/controllers/apps/component/transformer_component_workload.go b/controllers/apps/component/transformer_component_workload.go index f8aea10daf1..9c2090e2c68 100644 --- a/controllers/apps/component/transformer_component_workload.go +++ b/controllers/apps/component/transformer_component_workload.go @@ -35,7 +35,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -410,22 +409,6 @@ func buildPodSpecVolumeMounts(synthesizeComp *component.SynthesizedComponent) { // 1. new an object targetObj by copying from oldObj // 2. merge all fields can be updated from newObj into targetObj func copyAndMergeITS(oldITS, newITS *workloads.InstanceSet) *workloads.InstanceSet { - updateUpdateStrategy := func(itsObj, itsProto *workloads.InstanceSet) { - var objMaxUnavailable *intstr.IntOrString - if itsObj.Spec.UpdateStrategy.RollingUpdate != nil { - objMaxUnavailable = itsObj.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable - } - itsObj.Spec.UpdateStrategy = itsProto.Spec.UpdateStrategy - if objMaxUnavailable == nil && itsObj.Spec.UpdateStrategy.RollingUpdate != nil { - // HACK: This field is alpha-level (since v1.24) and is only honored by servers that enable the - // MaxUnavailableStatefulSet feature. - // When we get a nil MaxUnavailable from k8s, we consider that the field is not supported by the server, - // and set the MaxUnavailable as nil explicitly to avoid the workload been updated unexpectedly. - // Ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#maximum-unavailable-pods - itsObj.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable = nil - } - } - itsObjCopy := oldITS.DeepCopy() itsProto := newITS @@ -450,17 +433,28 @@ func copyAndMergeITS(oldITS, newITS *workloads.InstanceSet) *workloads.InstanceS itsObjCopy.Spec.Roles = itsProto.Spec.Roles itsObjCopy.Spec.MembershipReconfiguration = itsProto.Spec.MembershipReconfiguration itsObjCopy.Spec.TemplateVars = itsProto.Spec.TemplateVars - itsObjCopy.Spec.MemberUpdateStrategy = itsProto.Spec.MemberUpdateStrategy itsObjCopy.Spec.Instances = itsProto.Spec.Instances itsObjCopy.Spec.OfflineInstances = itsProto.Spec.OfflineInstances itsObjCopy.Spec.MinReadySeconds = itsProto.Spec.MinReadySeconds itsObjCopy.Spec.VolumeClaimTemplates = itsProto.Spec.VolumeClaimTemplates itsObjCopy.Spec.ParallelPodManagementConcurrency = itsProto.Spec.ParallelPodManagementConcurrency itsObjCopy.Spec.PodUpdatePolicy = itsProto.Spec.PodUpdatePolicy + itsObjCopy.Spec.InstanceUpdateStrategy = itsProto.Spec.InstanceUpdateStrategy + itsObjCopy.Spec.MemberUpdateStrategy = itsProto.Spec.MemberUpdateStrategy itsObjCopy.Spec.Paused = itsProto.Spec.Paused - if itsProto.Spec.UpdateStrategy.Type != "" || itsProto.Spec.UpdateStrategy.RollingUpdate != nil { - updateUpdateStrategy(itsObjCopy, itsProto) + if itsObjCopy.Spec.InstanceUpdateStrategy != nil && itsObjCopy.Spec.InstanceUpdateStrategy.RollingUpdate != nil { + // use oldITS because itsObjCopy has been overwritten + if oldITS.Spec.InstanceUpdateStrategy != nil && + oldITS.Spec.InstanceUpdateStrategy.RollingUpdate != nil && + oldITS.Spec.InstanceUpdateStrategy.RollingUpdate.MaxUnavailable == nil { + // HACK: This field is alpha-level (since v1.24) and is only honored by servers that enable the + // MaxUnavailableStatefulSet feature. + // When we get a nil MaxUnavailable from k8s, we consider that the field is not supported by the server, + // and set the MaxUnavailable as nil explicitly to avoid the workload been updated unexpectedly. + // Ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#maximum-unavailable-pods + itsObjCopy.Spec.InstanceUpdateStrategy.RollingUpdate.MaxUnavailable = nil + } } intctrlutil.ResolvePodSpecDefaultFields(oldITS.Spec.Template.Spec, &itsObjCopy.Spec.Template.Spec) diff --git a/controllers/apps/componentdefinition_controller_test.go b/controllers/apps/componentdefinition_controller_test.go index 7b3638b5aab..58be0a38fc7 100644 --- a/controllers/apps/componentdefinition_controller_test.go +++ b/controllers/apps/componentdefinition_controller_test.go @@ -29,6 +29,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" @@ -542,8 +543,7 @@ var _ = Describe("ComponentDefinition Controller", func() { Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(componentDefObj), func(cmpd *kbappsv1.ComponentDefinition) { cmpd.Spec.Description = "v0.0.2" cmpd.Spec.Runtime.Containers[0].Image = "image:v0.0.2" - parallel := kbappsv1.ParallelStrategy - cmpd.Spec.UpdateStrategy = ¶llel + cmpd.Spec.UpdateStrategy = ptr.To(kbappsv1.ParallelStrategy) })()).Should(Succeed()) By(fmt.Sprintf("checking the updated object as %s", strings.ToLower(string(kbappsv1.AvailablePhase)))) @@ -570,8 +570,7 @@ var _ = Describe("ComponentDefinition Controller", func() { Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(componentDefObj), func(cmpd *kbappsv1.ComponentDefinition) { cmpd.Spec.Description = "v0.0.2" cmpd.Spec.Runtime.Containers[0].Image = "image:v0.0.2" - parallel := kbappsv1.ParallelStrategy - cmpd.Spec.UpdateStrategy = ¶llel + cmpd.Spec.UpdateStrategy = ptr.To(kbappsv1.ParallelStrategy) })()).Should(Succeed()) By(fmt.Sprintf("checking the updated object as %s", strings.ToLower(string(kbappsv1.UnavailablePhase)))) diff --git a/controllers/parameters/reconfigure_policy.go b/controllers/parameters/reconfigure_policy.go index fc2a2dfffc2..8f63c5b9979 100644 --- a/controllers/parameters/reconfigure_policy.go +++ b/controllers/parameters/reconfigure_policy.go @@ -162,8 +162,8 @@ func (param *reconfigureParams) maxRollingReplicas() int32 { var maxUnavailable *intstr.IntOrString for _, its := range param.InstanceSetUnits { - if its.Spec.UpdateStrategy.RollingUpdate != nil { - maxUnavailable = its.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable + if its.Spec.InstanceUpdateStrategy.RollingUpdate != nil { + maxUnavailable = its.Spec.InstanceUpdateStrategy.RollingUpdate.MaxUnavailable } if maxUnavailable != nil { break diff --git a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml index 327dba49aed..8a04343dc05 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml @@ -713,6 +713,47 @@ spec: - name type: object type: array + instanceUpdateStrategy: + description: Provides fine-grained control over the spec update + process of all instances. + properties: + rollingUpdate: + description: Specifies how the rolling update should be + applied. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of instances that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. This can not be 0. + Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + it will be counted towards MaxUnavailable. + x-kubernetes-int-or-string: true + replicas: + anyOf: + - type: integer + - type: string + description: |- + Indicates the number of instances that should be updated during a rolling update. + The remaining instances will remain untouched. This is helpful in defining how many instances + should participate in the update process. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. + The default value is ComponentSpec.Replicas (i.e., update all instances). + x-kubernetes-int-or-string: true + type: object + type: + description: |- + Indicates the type of the update strategy. + Default is RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object instances: description: |- Allows for the customization of configuration values for each instance within a Component. @@ -7853,6 +7894,47 @@ spec: - name type: object type: array + instanceUpdateStrategy: + description: Provides fine-grained control over the spec + update process of all instances. + properties: + rollingUpdate: + description: Specifies how the rolling update should + be applied. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of instances that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. This can not be 0. + Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + it will be counted towards MaxUnavailable. + x-kubernetes-int-or-string: true + replicas: + anyOf: + - type: integer + - type: string + description: |- + Indicates the number of instances that should be updated during a rolling update. + The remaining instances will remain untouched. This is helpful in defining how many instances + should participate in the update process. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. + The default value is ComponentSpec.Replicas (i.e., update all instances). + x-kubernetes-int-or-string: true + type: object + type: + description: |- + Indicates the type of the update strategy. + Default is RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object instances: description: |- Allows for the customization of configuration values for each instance within a Component. diff --git a/deploy/helm/crds/apps.kubeblocks.io_components.yaml b/deploy/helm/crds/apps.kubeblocks.io_components.yaml index 3c310ba9e94..acc8a6146b5 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_components.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_components.yaml @@ -581,6 +581,46 @@ spec: - name type: object type: array + instanceUpdateStrategy: + description: Provides fine-grained control over the spec update process + of all instances. + properties: + rollingUpdate: + description: Specifies how the rolling update should be applied. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of instances that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. This can not be 0. + Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + it will be counted towards MaxUnavailable. + x-kubernetes-int-or-string: true + replicas: + anyOf: + - type: integer + - type: string + description: |- + Indicates the number of instances that should be updated during a rolling update. + The remaining instances will remain untouched. This is helpful in defining how many instances + should participate in the update process. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. + The default value is ComponentSpec.Replicas (i.e., update all instances). + x-kubernetes-int-or-string: true + type: object + type: + description: |- + Indicates the type of the update strategy. + Default is RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object instances: description: |- Allows for the customization of configuration values for each instance within a Component. diff --git a/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml b/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml index b3a3c00a8c5..547a789fb77 100644 --- a/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml +++ b/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml @@ -94,6 +94,46 @@ spec: type: object type: array type: object + instanceUpdateStrategy: + description: Provides fine-grained control over the spec update process + of all instances. + properties: + rollingUpdate: + description: Specifies how the rolling update should be applied. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of instances that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. This can not be 0. + Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, + it will be counted towards MaxUnavailable. + x-kubernetes-int-or-string: true + replicas: + anyOf: + - type: integer + - type: string + description: |- + Indicates the number of instances that should be updated during a rolling update. + The remaining instances will remain untouched. This is helpful in defining how many instances + should participate in the update process. + Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). + Absolute number is calculated from percentage by rounding up. + The default value is ComponentSpec.Replicas (i.e., update all instances). + x-kubernetes-int-or-string: true + type: object + type: + description: |- + Indicates the type of the update strategy. + Default is RollingUpdate. + enum: + - RollingUpdate + - OnDelete + type: string + type: object instances: description: |- Overrides values in default Template. @@ -1530,12 +1570,12 @@ spec: - serial: update Members one by one that guarantee minimum component unavailable time. - - bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time. - parallel: force parallel + - bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time. enum: - Serial - - BestEffortParallel - Parallel + - BestEffortParallel type: string membershipReconfiguration: description: Provides actions to do membership dynamic reconfiguration. @@ -9654,48 +9694,6 @@ spec: type: string description: Provides variables which are used to call Actions. type: object - updateStrategy: - description: |- - Indicates the StatefulSetUpdateStrategy that will be - employed to update Pods in the InstanceSet when a revision is made to - Template. - UpdateStrategy.Type will be set to appsv1.OnDeleteStatefulSetStrategyType if MemberUpdateStrategy is not nil - - - Note: This field will be removed in future version. - properties: - rollingUpdate: - description: RollingUpdate is used to communicate parameters when - Type is RollingUpdateStatefulSetStrategyType. - properties: - maxUnavailable: - anyOf: - - type: integer - - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding up. This can not be 0. - Defaults to 1. This field is alpha-level and is only honored by servers that enable the - MaxUnavailableStatefulSet feature. The field applies to all pods in the range 0 to - Replicas-1. That means if there is any unavailable pod in the range 0 to Replicas-1, it - will be counted towards MaxUnavailable. - x-kubernetes-int-or-string: true - partition: - description: |- - Partition indicates the ordinal at which the StatefulSet should be partitioned - for updates. During a rolling update, all pods from ordinal Replicas-1 to - Partition are updated. All pods from ordinal Partition-1 to 0 remain untouched. - This is helpful in being able to do a canary based deployment. The default value is 0. - format: int32 - type: integer - type: object - type: - description: |- - Type indicates the type of the StatefulSetUpdateStrategy. - Default is RollingUpdate. - type: string - type: object volumeClaimTemplates: description: |- Specifies a list of PersistentVolumeClaim templates that define the storage requirements for each replica. diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index 8be2cb77475..384912c2aab 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -726,6 +726,20 @@ Default value is “PreferInPlace” +instanceUpdateStrategy
+ + +InstanceUpdateStrategy + + + + +(Optional) +

Provides fine-grained control over the spec update process of all instances.

+ + + + schedulingPolicy
@@ -3168,6 +3182,20 @@ Default value is “PreferInPlace” +instanceUpdateStrategy
+ +
+InstanceUpdateStrategy + + + + +(Optional) +

Provides fine-grained control over the spec update process of all instances.

+ + + + instances
@@ -6477,6 +6505,20 @@ Default value is “PreferInPlace” +instanceUpdateStrategy
+ +
+InstanceUpdateStrategy + + + + +(Optional) +

Provides fine-grained control over the spec update process of all instances.

+ + + + schedulingPolicy
@@ -8191,6 +8233,80 @@ Add new or override existing envs.

+

InstanceUpdateStrategy +

+

+(Appears on:ClusterComponentSpec, ComponentSpec, InstanceSetSpec) +

+
+

InstanceUpdateStrategy defines fine-grained control over the spec update process of all instances.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+type
+ + +InstanceUpdateStrategyType + + +
+(Optional) +

Indicates the type of the update strategy. +Default is RollingUpdate.

+
+rollingUpdate
+ + +RollingUpdate + + +
+(Optional) +

Specifies how the rolling update should be applied.

+
+

InstanceUpdateStrategyType +(string alias)

+

+(Appears on:InstanceUpdateStrategy) +

+
+

InstanceUpdateStrategyType is a string enumeration type that enumerates +all possible update strategies for the KubeBlocks controllers.

+
+ + + + + + + + + + + + +
ValueDescription

"OnDelete"

OnDeleteStrategyType indicates that ordered rolling restarts are disabled. Instances are recreated +when they are manually deleted.

+

"RollingUpdate"

RollingUpdateStrategyType indicates that update will be +applied to all Instances with respect to the workload +ordering constraints.

+

Issuer

@@ -8846,9 +8962,10 @@ string

PodUpdatePolicyType (string alias)

-(Appears on:ClusterComponentSpec, ComponentSpec) +(Appears on:ClusterComponentSpec, ComponentSpec, InstanceSetSpec)

+

PodUpdatePolicyType indicates how pods should be updated

@@ -9310,6 +9427,61 @@ VarOption
+

RollingUpdate +

+

+(Appears on:InstanceUpdateStrategy) +

+
+

RollingUpdate specifies how the rolling update should be applied.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+replicas
+ + +Kubernetes api utils intstr.IntOrString + + +
+(Optional) +

Indicates the number of instances that should be updated during a rolling update. +The remaining instances will remain untouched. This is helpful in defining how many instances +should participate in the update process. +Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). +Absolute number is calculated from percentage by rounding up. +The default value is ComponentSpec.Replicas (i.e., update all instances).

+
+maxUnavailable
+ + +Kubernetes api utils intstr.IntOrString + + +
+(Optional) +

The maximum number of instances that can be unavailable during the update. +Value can be an absolute number (ex: 5) or a percentage of desired instances (ex: 10%). +Absolute number is calculated from percentage by rounding up. This can not be 0. +Defaults to 1. The field applies to all instances. That means if there is any unavailable pod, +it will be counted towards MaxUnavailable.

+

SchedulingPolicy

@@ -29398,7 +29570,7 @@ The default Concurrency is 100%.

podUpdatePolicy
- + PodUpdatePolicyType @@ -29417,19 +29589,35 @@ Default value is “PreferInPlace” -updateStrategy
+instanceUpdateStrategy
- -Kubernetes apps/v1.StatefulSetUpdateStrategy + +InstanceUpdateStrategy -

Indicates the StatefulSetUpdateStrategy that will be -employed to update Pods in the InstanceSet when a revision is made to -Template. -UpdateStrategy.Type will be set to appsv1.OnDeleteStatefulSetStrategyType if MemberUpdateStrategy is not nil

-

Note: This field will be removed in future version.

+(Optional) +

Provides fine-grained control over the spec update process of all instances.

+ + + + +memberUpdateStrategy
+ + +MemberUpdateStrategy + + + + +(Optional) +

Members(Pods) update strategy.

+
    +
  • serial: update Members one by one that guarantee minimum component unavailable time.
  • +
  • parallel: force parallel
  • +
  • bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time.
  • +
@@ -29474,25 +29662,6 @@ map[string]string -memberUpdateStrategy
- - -MemberUpdateStrategy - - - - -(Optional) -

Members(Pods) update strategy.

-
    -
  • serial: update Members one by one that guarantee minimum component unavailable time.
  • -
  • bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time.
  • -
  • parallel: force parallel
  • -
- - - - paused
bool @@ -29818,7 +29987,7 @@ The default Concurrency is 100%.

podUpdatePolicy
- + PodUpdatePolicyType @@ -29837,19 +30006,35 @@ Default value is “PreferInPlace” -updateStrategy
+instanceUpdateStrategy
- -Kubernetes apps/v1.StatefulSetUpdateStrategy + +InstanceUpdateStrategy -

Indicates the StatefulSetUpdateStrategy that will be -employed to update Pods in the InstanceSet when a revision is made to -Template. -UpdateStrategy.Type will be set to appsv1.OnDeleteStatefulSetStrategyType if MemberUpdateStrategy is not nil

-

Note: This field will be removed in future version.

+(Optional) +

Provides fine-grained control over the spec update process of all instances.

+ + + + +memberUpdateStrategy
+ + +MemberUpdateStrategy + + + + +(Optional) +

Members(Pods) update strategy.

+
    +
  • serial: update Members one by one that guarantee minimum component unavailable time.
  • +
  • parallel: force parallel
  • +
  • bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time.
  • +
@@ -29894,25 +30079,6 @@ map[string]string -memberUpdateStrategy
- - -MemberUpdateStrategy - - - - -(Optional) -

Members(Pods) update strategy.

-
    -
  • serial: update Members one by one that guarantee minimum component unavailable time.
  • -
  • bestEffortParallel: update Members in parallel that guarantee minimum component un-writable time.
  • -
  • parallel: force parallel
  • -
- - - - paused
bool @@ -30402,30 +30568,6 @@ Action -

PodUpdatePolicyType -(string alias)

-

-(Appears on:InstanceSetSpec) -

-
-
- - - - - - - - - - - - -
ValueDescription

"PreferInPlace"

PreferInPlacePodUpdatePolicyType indicates that we will first attempt an in-place upgrade of the Pod. -If that fails, it will fall back to the ReCreate, where pod will be recreated.

-

"StrictInPlace"

StrictInPlacePodUpdatePolicyType indicates that only allows in-place upgrades. -Any attempt to modify other fields will be rejected.

-

RoleUpdateMechanism (string alias)

diff --git a/pkg/controller/builder/builder_component.go b/pkg/controller/builder/builder_component.go index 0dca6ce9b92..5b2d8b817a5 100644 --- a/pkg/controller/builder/builder_component.go +++ b/pkg/controller/builder/builder_component.go @@ -96,6 +96,11 @@ func (builder *ComponentBuilder) SetPodUpdatePolicy(policy *appsv1.PodUpdatePoli return builder } +func (builder *ComponentBuilder) SetInstanceUpdateStrategy(strategy *appsv1.InstanceUpdateStrategy) *ComponentBuilder { + builder.get().Spec.InstanceUpdateStrategy = strategy + return builder +} + func (builder *ComponentBuilder) SetResources(resources corev1.ResourceRequirements) *ComponentBuilder { builder.get().Spec.Resources = resources return builder diff --git a/pkg/controller/builder/builder_instance_set.go b/pkg/controller/builder/builder_instance_set.go index b7a98d1fde9..50cef82dbee 100644 --- a/pkg/controller/builder/builder_instance_set.go +++ b/pkg/controller/builder/builder_instance_set.go @@ -20,7 +20,7 @@ along with this program. If not, see . package builder import ( - apps "k8s.io/api/apps/v1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -104,7 +104,7 @@ func (builder *InstanceSetBuilder) SetVolumeClaimTemplates(templates ...corev1.P return builder } -func (builder *InstanceSetBuilder) SetPodManagementPolicy(policy apps.PodManagementPolicyType) *InstanceSetBuilder { +func (builder *InstanceSetBuilder) SetPodManagementPolicy(policy appsv1.PodManagementPolicyType) *InstanceSetBuilder { builder.get().Spec.PodManagementPolicy = policy return builder } @@ -113,19 +113,18 @@ func (builder *InstanceSetBuilder) SetParallelPodManagementConcurrency(parallelP builder.get().Spec.ParallelPodManagementConcurrency = parallelPodManagementConcurrency return builder } - func (builder *InstanceSetBuilder) SetPodUpdatePolicy(policy workloads.PodUpdatePolicyType) *InstanceSetBuilder { builder.get().Spec.PodUpdatePolicy = policy return builder } -func (builder *InstanceSetBuilder) SetUpdateStrategy(strategy apps.StatefulSetUpdateStrategy) *InstanceSetBuilder { - builder.get().Spec.UpdateStrategy = strategy +func (builder *InstanceSetBuilder) SetInstanceUpdateStrategy(strategy *workloads.InstanceUpdateStrategy) *InstanceSetBuilder { + builder.get().Spec.InstanceUpdateStrategy = strategy return builder } -func (builder *InstanceSetBuilder) SetUpdateStrategyType(strategyType apps.StatefulSetUpdateStrategyType) *InstanceSetBuilder { - builder.get().Spec.UpdateStrategy.Type = strategyType +func (builder *InstanceSetBuilder) SetMemberUpdateStrategy(strategy *workloads.MemberUpdateStrategy) *InstanceSetBuilder { + builder.get().Spec.MemberUpdateStrategy = strategy return builder } @@ -154,14 +153,6 @@ func (builder *InstanceSetBuilder) SetTemplateVars(templateVars map[string]any) return builder } -func (builder *InstanceSetBuilder) SetMemberUpdateStrategy(strategy *workloads.MemberUpdateStrategy) *InstanceSetBuilder { - builder.get().Spec.MemberUpdateStrategy = strategy - if strategy != nil { - builder.SetUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType) - } - return builder -} - func (builder *InstanceSetBuilder) SetPaused(paused bool) *InstanceSetBuilder { builder.get().Spec.Paused = paused return builder diff --git a/pkg/controller/builder/builder_instance_set_test.go b/pkg/controller/builder/builder_instance_set_test.go index ade51a20727..49b1ab609f5 100644 --- a/pkg/controller/builder/builder_instance_set_test.go +++ b/pkg/controller/builder/builder_instance_set_test.go @@ -23,12 +23,13 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - apps "k8s.io/api/apps/v1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" ) @@ -44,8 +45,8 @@ var _ = Describe("instance_set builder", func() { replicas = int32(5) minReadySeconds = int32(11) port = int32(12345) - policy = apps.OrderedReadyPodManagement - podUpdatePolicy = workloads.PreferInPlacePodUpdatePolicyType + policy = appsv1.OrderedReadyPodManagement + podUpdatePolicy = kbappsv1.PreferInPlacePodUpdatePolicyType ) parallelPodManagementConcurrency := &intstr.IntOrString{Type: intstr.String, StrVal: "100%"} selectors := map[string]string{selectorKey4: selectorValue4} @@ -106,15 +107,13 @@ var _ = Describe("instance_set builder", func() { }, }, } - partition, maxUnavailable := int32(3), intstr.FromInt(2) - strategy := apps.StatefulSetUpdateStrategy{ - Type: apps.RollingUpdateStatefulSetStrategyType, - RollingUpdate: &apps.RollingUpdateStatefulSetStrategy{ - Partition: &partition, + updateReplicas, maxUnavailable := intstr.FromInt32(3), intstr.FromInt32(2) + strategy := workloads.InstanceUpdateStrategy{ + RollingUpdate: &workloads.RollingUpdate{ + Replicas: &updateReplicas, MaxUnavailable: &maxUnavailable, }, } - strategyType := apps.OnDeleteStatefulSetStrategyType memberUpdateStrategy := workloads.BestEffortParallelUpdateStrategy paused := true instances := []workloads.InstanceTemplate{ @@ -141,8 +140,7 @@ var _ = Describe("instance_set builder", func() { SetPodManagementPolicy(policy). SetParallelPodManagementConcurrency(parallelPodManagementConcurrency). SetPodUpdatePolicy(podUpdatePolicy). - SetUpdateStrategy(strategy). - SetUpdateStrategyType(strategyType). + SetInstanceUpdateStrategy(&strategy). SetMemberUpdateStrategy(&memberUpdateStrategy). SetPaused(paused). SetInstances(instances). @@ -169,12 +167,12 @@ var _ = Describe("instance_set builder", func() { Expect(its.Spec.PodManagementPolicy).Should(Equal(policy)) Expect(its.Spec.ParallelPodManagementConcurrency).Should(Equal(parallelPodManagementConcurrency)) Expect(its.Spec.PodUpdatePolicy).Should(Equal(podUpdatePolicy)) - Expect(its.Spec.UpdateStrategy.Type).Should(Equal(strategyType)) - Expect(its.Spec.UpdateStrategy.RollingUpdate).ShouldNot(BeNil()) - Expect(its.Spec.UpdateStrategy.RollingUpdate.Partition).ShouldNot(BeNil()) - Expect(*its.Spec.UpdateStrategy.RollingUpdate.Partition).Should(Equal(partition)) - Expect(its.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable).ShouldNot(BeNil()) - Expect(its.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable).ShouldNot(Equal(maxUnavailable)) + Expect(its.Spec.InstanceUpdateStrategy).ShouldNot(BeNil()) + Expect(its.Spec.InstanceUpdateStrategy.RollingUpdate).ShouldNot(BeNil()) + Expect(its.Spec.InstanceUpdateStrategy.RollingUpdate.Replicas).ShouldNot(BeNil()) + Expect(*its.Spec.InstanceUpdateStrategy.RollingUpdate.Replicas).Should(Equal(updateReplicas)) + Expect(its.Spec.InstanceUpdateStrategy.RollingUpdate.MaxUnavailable).ShouldNot(BeNil()) + Expect(*its.Spec.InstanceUpdateStrategy.RollingUpdate.MaxUnavailable).Should(Equal(maxUnavailable)) Expect(its.Spec.MemberUpdateStrategy).ShouldNot(BeNil()) Expect(*its.Spec.MemberUpdateStrategy).Should(Equal(memberUpdateStrategy)) Expect(its.Spec.Paused).Should(Equal(paused)) diff --git a/pkg/controller/component/component.go b/pkg/controller/component/component.go index ab4b34d44d9..62affe6c57f 100644 --- a/pkg/controller/component/component.go +++ b/pkg/controller/component/component.go @@ -79,6 +79,7 @@ func BuildComponent(cluster *appsv1.Cluster, compSpec *appsv1.ClusterComponentSp SetServiceAccountName(compSpec.ServiceAccountName). SetParallelPodManagementConcurrency(compSpec.ParallelPodManagementConcurrency). SetPodUpdatePolicy(compSpec.PodUpdatePolicy). + SetInstanceUpdateStrategy(compSpec.InstanceUpdateStrategy). SetVolumeClaimTemplates(compSpec.VolumeClaimTemplates). SetVolumes(compSpec.Volumes). SetServices(compSpec.Services). diff --git a/pkg/controller/component/convertor.go b/pkg/controller/component/convertor.go deleted file mode 100644 index 77527010644..00000000000 --- a/pkg/controller/component/convertor.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright (C) 2022-2025 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package component - -import ( - "reflect" - "strings" -) - -// convertor is the interface for converting a value. -type convertor interface { - convert(...any) (any, error) -} - -// covertObject converts the fields of an object with the given convertors. -func covertObject(convertors map[string]convertor, obj any, args ...any) error { - tp := typeofObject(obj) - for i := 0; i < tp.NumField(); i++ { - fieldName := tp.Field(i).Name - c, ok := convertors[strings.ToLower(fieldName)] - if !ok || c == nil { - continue // leave the origin (default) value - } - - val, err := c.convert(args...) - if err != nil { - return err - } - - fieldValue := reflect.ValueOf(obj).Elem().FieldByName(fieldName) - switch { - case reflect.TypeOf(val) == nil || reflect.ValueOf(val).IsZero(): - fieldValue.Set(reflect.Zero(fieldValue.Type())) - case fieldValue.IsValid() && fieldValue.Type().AssignableTo(reflect.TypeOf(val)): - fieldValue.Set(reflect.ValueOf(val)) - default: - panic("not assignable") - } - } - return nil -} - -// typeofObject returns the typeof an object. -func typeofObject(obj any) reflect.Type { - val := reflect.ValueOf(obj) - if val.Kind() == reflect.Ptr { - return reflect.TypeOf(obj).Elem() - } - if val.Kind() != reflect.Struct { - panic("not a struct") - } - return reflect.TypeOf(obj) -} diff --git a/pkg/controller/component/convertor_test.go b/pkg/controller/component/convertor_test.go deleted file mode 100644 index 741399ca193..00000000000 --- a/pkg/controller/component/convertor_test.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright (C) 2022-2025 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package component - -import ( - . "github.com/onsi/ginkgo/v2" -) - -var _ = Describe("Test Convertor", func() { - // TODO: add test cases -}) diff --git a/pkg/controller/component/its_convertor.go b/pkg/controller/component/its_convertor.go deleted file mode 100644 index f0548ea5b8c..00000000000 --- a/pkg/controller/component/its_convertor.go +++ /dev/null @@ -1,135 +0,0 @@ -/* -Copyright (C) 2022-2025 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package component - -import ( - "errors" - - appsv1 "k8s.io/api/apps/v1" - - kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" -) - -// BuildWorkloadFrom builds a new Component object based on SynthesizedComponent. -func BuildWorkloadFrom(synthesizeComp *SynthesizedComponent, protoITS *workloads.InstanceSet) (*workloads.InstanceSet, error) { - if synthesizeComp == nil { - return nil, nil - } - if protoITS == nil { - protoITS = &workloads.InstanceSet{} - } - convertors := map[string]convertor{ - "roles": &itsRolesConvertor{}, - "memberupdatestrategy": &itsMemberUpdateStrategyConvertor{}, - "podmanagementpolicy": &itsPodManagementPolicyConvertor{}, - "updatestrategy": &itsUpdateStrategyConvertor{}, - } - if err := covertObject(convertors, &protoITS.Spec, synthesizeComp); err != nil { - return nil, err - } - return protoITS, nil -} - -// itsRolesConvertor is an implementation of the convertor interface, used to convert the given object into InstanceSet.Spec.Roles. -type itsRolesConvertor struct{} - -// itsRolesConvertor converts the ComponentDefinition.Spec.Roles into InstanceSet.Spec.Roles. -func (c *itsRolesConvertor) convert(args ...any) (any, error) { - synthesizeComp, err := parseITSConvertorArgs(args...) - if err != nil { - return nil, err - } - return synthesizeComp.Roles, nil -} - -// itsMemberUpdateStrategyConvertor is an implementation of the convertor interface, used to convert the given object into InstanceSet.Spec.MemberUpdateStrategy. -type itsMemberUpdateStrategyConvertor struct{} - -func (c *itsMemberUpdateStrategyConvertor) convert(args ...any) (any, error) { - synthesizeComp, err := parseITSConvertorArgs(args...) - if err != nil { - return nil, err - } - return getMemberUpdateStrategy(synthesizeComp), nil -} - -// itsPodManagementPolicyConvertor is an implementation of the convertor interface, used to convert the given object into InstanceSet.Spec.PodManagementPolicy. -type itsPodManagementPolicyConvertor struct{} - -func (c *itsPodManagementPolicyConvertor) convert(args ...any) (any, error) { - synthesizedComp, err := parseITSConvertorArgs(args...) - if err != nil { - return nil, err - } - if synthesizedComp.PodManagementPolicy != nil { - return *synthesizedComp.PodManagementPolicy, nil - } - memberUpdateStrategy := getMemberUpdateStrategy(synthesizedComp) - if memberUpdateStrategy == nil || *memberUpdateStrategy == workloads.SerialUpdateStrategy { - return appsv1.OrderedReadyPodManagement, nil - } - return appsv1.ParallelPodManagement, nil -} - -// itsUpdateStrategyConvertor is an implementation of the convertor interface, used to convert the given object into InstanceSet.Spec.Instances. -type itsUpdateStrategyConvertor struct{} - -func (c *itsUpdateStrategyConvertor) convert(args ...any) (any, error) { - synthesizedComp, err := parseITSConvertorArgs(args...) - if err != nil { - return nil, err - } - if getMemberUpdateStrategy(synthesizedComp) != nil { - // appsv1.OnDeleteStatefulSetStrategyType is the default value if member update strategy is set. - return appsv1.StatefulSetUpdateStrategy{}, nil - } - return nil, nil -} - -// parseITSConvertorArgs parses the args of ITS convertor. -func parseITSConvertorArgs(args ...any) (*SynthesizedComponent, error) { - synthesizeComp, ok := args[0].(*SynthesizedComponent) - if !ok { - return nil, errors.New("args[0] not a SynthesizedComponent object") - } - return synthesizeComp, nil -} - -func getMemberUpdateStrategy(synthesizedComp *SynthesizedComponent) *workloads.MemberUpdateStrategy { - if synthesizedComp.UpdateStrategy == nil { - return nil - } - var ( - serial = workloads.SerialUpdateStrategy - parallelUpdate = workloads.ParallelUpdateStrategy - bestEffortParallelUpdate = workloads.BestEffortParallelUpdateStrategy - ) - switch *synthesizedComp.UpdateStrategy { - case kbappsv1.SerialStrategy: - return &serial - case kbappsv1.ParallelStrategy: - return ¶llelUpdate - case kbappsv1.BestEffortParallelStrategy: - return &bestEffortParallelUpdate - default: - return nil - } -} diff --git a/pkg/controller/component/synthesize_component.go b/pkg/controller/component/synthesize_component.go index c57edfea3dd..b2f91419558 100644 --- a/pkg/controller/component/synthesize_component.go +++ b/pkg/controller/component/synthesize_component.go @@ -97,7 +97,6 @@ func BuildSynthesizedComponent(ctx context.Context, cli client.Reader, ConfigTemplates: compDefObj.Spec.Configs, ScriptTemplates: compDefObj.Spec.Scripts, Roles: compDefObj.Spec.Roles, - UpdateStrategy: compDefObj.Spec.UpdateStrategy, MinReadySeconds: compDefObj.Spec.MinReadySeconds, PolicyRules: compDefObj.Spec.PolicyRules, LifecycleActions: compDefObj.Spec.LifecycleActions, @@ -113,6 +112,8 @@ func BuildSynthesizedComponent(ctx context.Context, cli client.Reader, PodManagementPolicy: compDef.Spec.PodManagementPolicy, ParallelPodManagementConcurrency: comp.Spec.ParallelPodManagementConcurrency, PodUpdatePolicy: comp.Spec.PodUpdatePolicy, + UpdateStrategy: compDef.Spec.UpdateStrategy, + InstanceUpdateStrategy: comp.Spec.InstanceUpdateStrategy, } if err = mergeUserDefinedEnv(synthesizeComp, comp); err != nil { diff --git a/pkg/controller/component/type.go b/pkg/controller/component/type.go index 4476a486b04..ba474bc1cb2 100644 --- a/pkg/controller/component/type.go +++ b/pkg/controller/component/type.go @@ -64,10 +64,11 @@ type SynthesizedComponent struct { Instances []kbappsv1.InstanceTemplate `json:"instances,omitempty"` OfflineInstances []string `json:"offlineInstances,omitempty"` Roles []kbappsv1.ReplicaRole `json:"roles,omitempty"` - UpdateStrategy *kbappsv1.UpdateStrategy `json:"updateStrategy,omitempty"` PodManagementPolicy *appsv1.PodManagementPolicyType `json:"podManagementPolicy,omitempty"` ParallelPodManagementConcurrency *intstr.IntOrString `json:"parallelPodManagementConcurrency,omitempty"` PodUpdatePolicy *kbappsv1.PodUpdatePolicyType `json:"podUpdatePolicy,omitempty"` + UpdateStrategy *kbappsv1.UpdateStrategy `json:"updateStrategy,omitempty"` + InstanceUpdateStrategy *kbappsv1.InstanceUpdateStrategy `json:"instanceUpdateStrategy,omitempty"` PolicyRules []rbacv1.PolicyRule `json:"policyRules,omitempty"` LifecycleActions *kbappsv1.ComponentLifecycleActions `json:"lifecycleActions,omitempty"` SystemAccounts []kbappsv1.SystemAccount `json:"systemAccounts,omitempty"` diff --git a/pkg/controller/factory/builder.go b/pkg/controller/factory/builder.go index ece4d757e53..1f7a57f2be5 100644 --- a/pkg/controller/factory/builder.go +++ b/pkg/controller/factory/builder.go @@ -24,12 +24,13 @@ import ( "path/filepath" "strconv" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/klog/v2" - appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/common" @@ -43,7 +44,7 @@ import ( ) // BuildInstanceSet builds an InstanceSet object from SynthesizedComponent. -func BuildInstanceSet(synthesizedComp *component.SynthesizedComponent, componentDef *appsv1.ComponentDefinition) (*workloads.InstanceSet, error) { +func BuildInstanceSet(synthesizedComp *component.SynthesizedComponent, componentDef *kbappsv1.ComponentDefinition) (*workloads.InstanceSet, error) { var ( compDefName = synthesizedComp.CompDefName namespace = synthesizedComp.Namespace @@ -51,18 +52,6 @@ func BuildInstanceSet(synthesizedComp *component.SynthesizedComponent, component compName = synthesizedComp.Name ) - podBuilder := builder.NewPodBuilder("", ""). - // priority: static < dynamic < built-in - AddLabelsInMap(synthesizedComp.StaticLabels). - AddLabelsInMap(synthesizedComp.DynamicLabels). - AddLabelsInMap(constant.GetCompLabels(clusterName, compName, synthesizedComp.Labels)). - AddAnnotationsInMap(synthesizedComp.StaticAnnotations). - AddAnnotationsInMap(synthesizedComp.DynamicAnnotations) - template := corev1.PodTemplateSpec{ - ObjectMeta: podBuilder.GetObject().ObjectMeta, - Spec: *synthesizedComp.PodSpec.DeepCopy(), - } - itsName := constant.GenerateWorkloadNamePattern(clusterName, compName) itsBuilder := builder.NewInstanceSetBuilder(namespace, itsName). // priority: static < dynamic < built-in @@ -77,38 +66,28 @@ func BuildInstanceSet(synthesizedComp *component.SynthesizedComponent, component }). AddAnnotationsInMap(synthesizedComp.StaticAnnotations). AddAnnotationsInMap(getMonitorAnnotations(synthesizedComp, componentDef)). - SetTemplate(template). + SetTemplate(getTemplate(synthesizedComp)). AddMatchLabelsInMap(constant.GetCompLabels(clusterName, compName)). SetReplicas(synthesizedComp.Replicas). + SetVolumeClaimTemplates(getVolumeClaimTemplates(synthesizedComp)...). SetMinReadySeconds(synthesizedComp.MinReadySeconds). SetInstances(synthesizedComp.Instances). SetOfflineInstances(synthesizedComp.OfflineInstances). + SetRoles(synthesizedComp.Roles). + SetPodManagementPolicy(getPodManagementPolicy(synthesizedComp)). SetParallelPodManagementConcurrency(getParallelPodManagementConcurrency(synthesizedComp)). SetPodUpdatePolicy(getPodUpdatePolicy(synthesizedComp)). + SetInstanceUpdateStrategy(getInstanceUpdateStrategy(synthesizedComp)). + SetMemberUpdateStrategy(getMemberUpdateStrategy(synthesizedComp)). SetLifecycleActions(synthesizedComp.LifecycleActions). SetTemplateVars(synthesizedComp.TemplateVars) - var vcts []corev1.PersistentVolumeClaim - for _, vct := range synthesizedComp.VolumeClaimTemplates { - // Priority: static < dynamic < built-in - intctrlutil.MergeMetadataMapInplace(synthesizedComp.StaticLabels, &vct.ObjectMeta.Labels) - intctrlutil.MergeMetadataMapInplace(synthesizedComp.StaticAnnotations, &vct.ObjectMeta.Annotations) - intctrlutil.MergeMetadataMapInplace(synthesizedComp.DynamicLabels, &vct.ObjectMeta.Labels) - intctrlutil.MergeMetadataMapInplace(synthesizedComp.DynamicAnnotations, &vct.ObjectMeta.Annotations) - vcts = append(vcts, vctToPVC(vct)) - } - itsBuilder.SetVolumeClaimTemplates(vcts...) - if common.IsCompactMode(synthesizedComp.Annotations) { itsBuilder.AddAnnotations(constant.FeatureReconciliationInCompactModeAnnotationKey, synthesizedComp.Annotations[constant.FeatureReconciliationInCompactModeAnnotationKey]) } - // convert componentDef attributes to workload attributes. including service, credential, roles, roleProbe, membershipReconfiguration, memberUpdateStrategy, etc. - itsObj, err := component.BuildWorkloadFrom(synthesizedComp, itsBuilder.GetObject()) - if err != nil { - return nil, err - } + itsObj := itsBuilder.GetObject() // update its.spec.volumeClaimTemplates[].metadata.labels // TODO(xingran): synthesizedComp.VolumeTypes has been removed, and the following code needs to be refactored. @@ -124,6 +103,47 @@ func BuildInstanceSet(synthesizedComp *component.SynthesizedComponent, component return itsObj, nil } +func getTemplate(synthesizedComp *component.SynthesizedComponent) corev1.PodTemplateSpec { + podBuilder := builder.NewPodBuilder("", ""). + // priority: static < dynamic < built-in + AddLabelsInMap(synthesizedComp.StaticLabels). + AddLabelsInMap(synthesizedComp.DynamicLabels). + AddLabelsInMap(constant.GetCompLabels(synthesizedComp.ClusterName, synthesizedComp.Name, synthesizedComp.Labels)). + AddAnnotationsInMap(synthesizedComp.StaticAnnotations). + AddAnnotationsInMap(synthesizedComp.DynamicAnnotations) + return corev1.PodTemplateSpec{ + ObjectMeta: podBuilder.GetObject().ObjectMeta, + Spec: *synthesizedComp.PodSpec.DeepCopy(), + } +} + +func getVolumeClaimTemplates(synthesizedComp *component.SynthesizedComponent) []corev1.PersistentVolumeClaim { + pvc := func(vct corev1.PersistentVolumeClaimTemplate) corev1.PersistentVolumeClaim { + return corev1.PersistentVolumeClaim{ + ObjectMeta: vct.ObjectMeta, + Spec: vct.Spec, + } + } + + var vcts []corev1.PersistentVolumeClaim + for _, vct := range synthesizedComp.VolumeClaimTemplates { + // priority: static < dynamic < built-in + intctrlutil.MergeMetadataMapInplace(synthesizedComp.StaticLabels, &vct.ObjectMeta.Labels) + intctrlutil.MergeMetadataMapInplace(synthesizedComp.StaticAnnotations, &vct.ObjectMeta.Annotations) + intctrlutil.MergeMetadataMapInplace(synthesizedComp.DynamicLabels, &vct.ObjectMeta.Labels) + intctrlutil.MergeMetadataMapInplace(synthesizedComp.DynamicAnnotations, &vct.ObjectMeta.Annotations) + vcts = append(vcts, pvc(vct)) + } + return vcts +} + +func getPodManagementPolicy(synthesizedComp *component.SynthesizedComponent) appsv1.PodManagementPolicyType { + if synthesizedComp.PodManagementPolicy != nil { + return *synthesizedComp.PodManagementPolicy + } + return appsv1.OrderedReadyPodManagement // default value +} + func getParallelPodManagementConcurrency(synthesizedComp *component.SynthesizedComponent) *intstr.IntOrString { if synthesizedComp.ParallelPodManagementConcurrency != nil { return synthesizedComp.ParallelPodManagementConcurrency @@ -133,20 +153,25 @@ func getParallelPodManagementConcurrency(synthesizedComp *component.SynthesizedC func getPodUpdatePolicy(synthesizedComp *component.SynthesizedComponent) workloads.PodUpdatePolicyType { if synthesizedComp.PodUpdatePolicy != nil { - return workloads.PodUpdatePolicyType(*synthesizedComp.PodUpdatePolicy) + return *synthesizedComp.PodUpdatePolicy } - return workloads.PreferInPlacePodUpdatePolicyType // default value + return kbappsv1.PreferInPlacePodUpdatePolicyType // default value } -func vctToPVC(vct corev1.PersistentVolumeClaimTemplate) corev1.PersistentVolumeClaim { - return corev1.PersistentVolumeClaim{ - ObjectMeta: vct.ObjectMeta, - Spec: vct.Spec, +func getInstanceUpdateStrategy(synthesizedComp *component.SynthesizedComponent) *workloads.InstanceUpdateStrategy { + // TODO: on-delete if the member update strategy is not null? + return synthesizedComp.InstanceUpdateStrategy +} + +func getMemberUpdateStrategy(synthesizedComp *component.SynthesizedComponent) *workloads.MemberUpdateStrategy { + if synthesizedComp.UpdateStrategy != nil { + return (*workloads.MemberUpdateStrategy)(synthesizedComp.UpdateStrategy) } + return nil } // getMonitorAnnotations returns the annotations for the monitor. -func getMonitorAnnotations(synthesizedComp *component.SynthesizedComponent, componentDef *appsv1.ComponentDefinition) map[string]string { +func getMonitorAnnotations(synthesizedComp *component.SynthesizedComponent, componentDef *kbappsv1.ComponentDefinition) map[string]string { if synthesizedComp.DisableExporter == nil || *synthesizedComp.DisableExporter || componentDef == nil { return nil } @@ -258,11 +283,11 @@ func GetRestoreSystemAccountPassword(annotations map[string]string, componentNam // TODO: add dynamicLabels and dynamicAnnotations by @zhangtao -func BuildConfigMapWithTemplate(cluster *appsv1.Cluster, +func BuildConfigMapWithTemplate(cluster *kbappsv1.Cluster, synthesizedComp *component.SynthesizedComponent, configs map[string]string, cmName string, - configTemplateSpec appsv1.ComponentTemplateSpec) *corev1.ConfigMap { + configTemplateSpec kbappsv1.ComponentTemplateSpec) *corev1.ConfigMap { return builder.NewConfigMapBuilder(cluster.Namespace, cmName). AddLabelsInMap(synthesizedComp.StaticLabels). AddLabelsInMap(constant.GetCompLabels(cluster.Name, synthesizedComp.Name)). @@ -366,7 +391,7 @@ func BuildRoleBinding(synthesizedComp *component.SynthesizedComponent, name stri GetObject() } -func BuildRole(synthesizedComp *component.SynthesizedComponent, cmpd *appsv1.ComponentDefinition) *rbacv1.Role { +func BuildRole(synthesizedComp *component.SynthesizedComponent, cmpd *kbappsv1.ComponentDefinition) *rbacv1.Role { rules := cmpd.Spec.PolicyRules if len(rules) == 0 { return nil diff --git a/pkg/controller/factory/builder_test.go b/pkg/controller/factory/builder_test.go index c08d3449107..f505303706f 100644 --- a/pkg/controller/factory/builder_test.go +++ b/pkg/controller/factory/builder_test.go @@ -124,7 +124,8 @@ var _ = Describe("builder", func() { // test roles Expect(its.Spec.Roles).Should(HaveLen(len(compDef.Spec.Roles))) - // test member update strategy + // test update strategy + Expect(its.Spec.InstanceUpdateStrategy).Should(BeNil()) Expect(its.Spec.MemberUpdateStrategy).ShouldNot(BeNil()) Expect(*its.Spec.MemberUpdateStrategy).Should(BeEquivalentTo(workloads.BestEffortParallelUpdateStrategy)) }) diff --git a/pkg/controller/instanceset/reconciler_update.go b/pkg/controller/instanceset/reconciler_update.go index 558b28708c1..243f47a3a8b 100644 --- a/pkg/controller/instanceset/reconciler_update.go +++ b/pkg/controller/instanceset/reconciler_update.go @@ -24,7 +24,6 @@ import ( "fmt" "time" - apps "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -97,14 +96,13 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder } // 3. do update - // do nothing if UpdateStrategyType is 'OnDelete' - if its.Spec.UpdateStrategy.Type == apps.OnDeleteStatefulSetStrategyType { - // TODO: how to handle the OnDelete type? + // do nothing if update strategy type is 'OnDelete' + if its.Spec.InstanceUpdateStrategy != nil && its.Spec.InstanceUpdateStrategy.Type == kbappsv1.OnDeleteStrategyType { return kubebuilderx.Continue, nil } // handle 'RollingUpdate' - partition, maxUnavailable, err := parsePartitionNMaxUnavailable(its.Spec.UpdateStrategy.RollingUpdate, len(oldPodList)) + replicas, maxUnavailable, err := parseReplicasNMaxUnavailable(its.Spec.InstanceUpdateStrategy, len(oldPodList)) if err != nil { return kubebuilderx.Continue, err } @@ -119,8 +117,7 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder // if it's a roleful InstanceSet, we use updateCount to represent Pods can be updated according to the spec.memberUpdateStrategy. updateCount := len(oldPodList) if len(its.Spec.Roles) > 0 { - itsForPlan := getInstanceSetForUpdatePlan(its) - plan := NewUpdatePlan(*itsForPlan, oldPodList, IsPodUpdated) + plan := NewUpdatePlan(*its, oldPodList, IsPodUpdated) podsToBeUpdated, err := plan.Execute() if err != nil { return kubebuilderx.Continue, err @@ -138,7 +135,7 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder if updatingPods >= updateCount || updatingPods >= unavailable { break } - if updatedPods >= partition { + if updatedPods >= replicas { break } @@ -165,9 +162,9 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder if err != nil { return kubebuilderx.Continue, err } - if its.Spec.PodUpdatePolicy == workloads.StrictInPlacePodUpdatePolicyType && updatePolicy == RecreatePolicy { + if its.Spec.PodUpdatePolicy == kbappsv1.StrictInPlacePodUpdatePolicyType && updatePolicy == RecreatePolicy { message := fmt.Sprintf("InstanceSet %s/%s blocks on update as the PodUpdatePolicy is %s and the pod %s can not inplace update", - its.Namespace, its.Name, workloads.StrictInPlacePodUpdatePolicyType, pod.Name) + its.Namespace, its.Name, kbappsv1.StrictInPlacePodUpdatePolicyType, pod.Name) if tree != nil && tree.EventRecorder != nil { tree.EventRecorder.Eventf(its, corev1.EventTypeWarning, EventReasonStrictInPlace, message) } @@ -268,39 +265,33 @@ func buildBlockedCondition(its *workloads.InstanceSet, message string) *metav1.C } } -func getInstanceSetForUpdatePlan(its *workloads.InstanceSet) *workloads.InstanceSet { - if its.Spec.MemberUpdateStrategy != nil { - return its - } - itsForPlan := its.DeepCopy() - updateStrategy := workloads.SerialUpdateStrategy - if its.Spec.PodManagementPolicy == apps.ParallelPodManagement { - updateStrategy = workloads.ParallelUpdateStrategy - } - itsForPlan.Spec.MemberUpdateStrategy = &updateStrategy - return itsForPlan -} - -func parsePartitionNMaxUnavailable(rollingUpdate *apps.RollingUpdateStatefulSetStrategy, replicas int) (int, int, error) { - partition := replicas +func parseReplicasNMaxUnavailable(updateStrategy *workloads.InstanceUpdateStrategy, totalReplicas int) (int, int, error) { + replicas := totalReplicas maxUnavailable := 1 + if updateStrategy == nil { + return replicas, maxUnavailable, nil + } + rollingUpdate := updateStrategy.RollingUpdate if rollingUpdate == nil { - return partition, maxUnavailable, nil + return replicas, maxUnavailable, nil } - if rollingUpdate.Partition != nil { - partition = int(*rollingUpdate.Partition) + var err error + if rollingUpdate.Replicas != nil { + replicas, err = intstr.GetScaledValueFromIntOrPercent(rollingUpdate.Replicas, totalReplicas, false) + if err != nil { + return replicas, maxUnavailable, err + } } if rollingUpdate.MaxUnavailable != nil { - maxUnavailableNum, err := intstr.GetScaledValueFromIntOrPercent(intstr.ValueOrDefault(rollingUpdate.MaxUnavailable, intstr.FromInt32(1)), replicas, false) + maxUnavailable, err = intstr.GetScaledValueFromIntOrPercent(intstr.ValueOrDefault(rollingUpdate.MaxUnavailable, intstr.FromInt32(1)), totalReplicas, false) if err != nil { return 0, 0, err } // maxUnavailable might be zero for small percentage with round down. // So we have to enforce it not to be less than 1. - if maxUnavailableNum < 1 { - maxUnavailableNum = 1 + if maxUnavailable < 1 { + maxUnavailable = 1 } - maxUnavailable = maxUnavailableNum } - return partition, maxUnavailable, nil + return replicas, maxUnavailable, nil } diff --git a/pkg/controller/instanceset/reconciler_update_test.go b/pkg/controller/instanceset/reconciler_update_test.go index 9a2c3b1a607..4cfd6da8cdb 100644 --- a/pkg/controller/instanceset/reconciler_update_test.go +++ b/pkg/controller/instanceset/reconciler_update_test.go @@ -32,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/controller/builder" "github.com/apecloud/kubeblocks/pkg/controller/kubebuilderx" @@ -159,11 +160,11 @@ var _ = Describe("update reconciler test", func() { Expect(err).Should(BeNil()) root, ok := partitionTree.GetRoot().(*workloads.InstanceSet) Expect(ok).Should(BeTrue()) - partition := int32(3) + updateReplicas := intstr.FromInt32(3) maxUnavailable := intstr.FromInt32(2) - root.Spec.UpdateStrategy = appsv1.StatefulSetUpdateStrategy{ - RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{ - Partition: &partition, + root.Spec.InstanceUpdateStrategy = &workloads.InstanceUpdateStrategy{ + RollingUpdate: &workloads.RollingUpdate{ + Replicas: &updateReplicas, MaxUnavailable: &maxUnavailable, }, } @@ -179,9 +180,9 @@ var _ = Describe("update reconciler test", func() { Expect(err).Should(BeNil()) root, ok = partitionTree.GetRoot().(*workloads.InstanceSet) Expect(ok).Should(BeTrue()) - root.Spec.UpdateStrategy = appsv1.StatefulSetUpdateStrategy{ - RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{ - Partition: &partition, + root.Spec.InstanceUpdateStrategy = &workloads.InstanceUpdateStrategy{ + RollingUpdate: &workloads.RollingUpdate{ + Replicas: &updateReplicas, MaxUnavailable: &maxUnavailable, }, } @@ -203,7 +204,9 @@ var _ = Describe("update reconciler test", func() { Expect(err).Should(BeNil()) root, ok = onDeleteTree.GetRoot().(*workloads.InstanceSet) Expect(ok).Should(BeTrue()) - root.Spec.UpdateStrategy.Type = appsv1.OnDeleteStatefulSetStrategyType + root.Spec.InstanceUpdateStrategy = &workloads.InstanceUpdateStrategy{ + Type: kbappsv1.OnDeleteStrategyType, + } res, err = reconciler.Reconcile(onDeleteTree) Expect(err).Should(BeNil()) Expect(res).Should(Equal(kubebuilderx.Continue)) @@ -216,7 +219,7 @@ var _ = Describe("update reconciler test", func() { Expect(err).Should(BeNil()) root, ok = preferInPlaceTree.GetRoot().(*workloads.InstanceSet) Expect(ok).Should(BeTrue()) - root.Spec.PodUpdatePolicy = workloads.PreferInPlacePodUpdatePolicyType + root.Spec.PodUpdatePolicy = kbappsv1.PreferInPlacePodUpdatePolicyType // try to add env to instanceHello to trigger the recreation root.Spec.Instances[0].Env = []corev1.EnvVar{ { @@ -234,7 +237,7 @@ var _ = Describe("update reconciler test", func() { Expect(err).Should(BeNil()) root, ok = strictInPlaceTree.GetRoot().(*workloads.InstanceSet) Expect(ok).Should(BeTrue()) - root.Spec.PodUpdatePolicy = workloads.StrictInPlacePodUpdatePolicyType + root.Spec.PodUpdatePolicy = kbappsv1.StrictInPlacePodUpdatePolicyType // try to add env to instanceHello to trigger the recreation root.Spec.Instances[0].Env = []corev1.EnvVar{ { diff --git a/pkg/controller/instanceset/update_plan.go b/pkg/controller/instanceset/update_plan.go index 11f8bbd470a..100cd49c2b5 100644 --- a/pkg/controller/instanceset/update_plan.go +++ b/pkg/controller/instanceset/update_plan.go @@ -98,7 +98,8 @@ func (p *realUpdatePlan) planWalkFunc(vertex graph.Vertex) error { // This change may lead to false alarms, as when all replicas are temporarily unavailable for some reason, // the system will update them without waiting for their roles to be elected and probed. This cloud // potentially hide some uncertain risks. - serialUpdate := p.its.Spec.MemberUpdateStrategy != nil && *p.its.Spec.MemberUpdateStrategy == workloads.SerialUpdateStrategy + memberUpdateStrategy := getMemberUpdateStrategy(&p.its) + serialUpdate := memberUpdateStrategy == workloads.SerialUpdateStrategy hasRoleProbed := len(p.its.Status.MembersStatus) > 0 if !serialUpdate || hasRoleProbed { return ErrWait @@ -116,21 +117,19 @@ func (p *realUpdatePlan) defaultIsPodUpdatedFunc(its *workloads.InstanceSet, pod return intctrlutil.GetPodRevision(pod) == its.Status.UpdateRevision, nil } -// build builds the update plan based on updateStrategy +// build builds the update plan based on memberUpdateStrategy func (p *realUpdatePlan) build() { // make a root vertex with nil Obj root := &model.ObjectVertex{} p.dag.AddVertex(root) - if p.its.Spec.MemberUpdateStrategy == nil { - return - } + memberUpdateStrategy := getMemberUpdateStrategy(&p.its) rolePriorityMap := ComposeRolePriorityMap(p.its.Spec.Roles) SortPods(p.pods, rolePriorityMap, false) - // generate plan by MemberUpdateStrategy - switch *p.its.Spec.MemberUpdateStrategy { + // generate plan by memberUpdateStrategy + switch memberUpdateStrategy { case workloads.SerialUpdateStrategy: p.buildSerialUpdatePlan() case workloads.ParallelUpdateStrategy: diff --git a/pkg/controller/instanceset/update_plan_test.go b/pkg/controller/instanceset/update_plan_test.go index a599a3745a8..097efdbf879 100644 --- a/pkg/controller/instanceset/update_plan_test.go +++ b/pkg/controller/instanceset/update_plan_test.go @@ -26,6 +26,7 @@ import ( apps "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/ptr" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/controller/builder" @@ -121,8 +122,7 @@ var _ = Describe("update plan test.", func() { It("should work well in a serial plan", func() { By("build a serial plan") - strategy := workloads.SerialUpdateStrategy - its.Spec.MemberUpdateStrategy = &strategy + its.Spec.MemberUpdateStrategy = ptr.To(workloads.SerialUpdateStrategy) expectedPlan := [][]*corev1.Pod{ {pod4}, {pod2}, @@ -137,8 +137,7 @@ var _ = Describe("update plan test.", func() { It("should work well in a serial plan when pod has no role", func() { By("build a serial plan") - strategy := workloads.SerialUpdateStrategy - its.Spec.MemberUpdateStrategy = &strategy + its.Spec.MemberUpdateStrategy = ptr.To(workloads.SerialUpdateStrategy) expectedPlan := [][]*corev1.Pod{ {pod4}, {pod2}, @@ -153,8 +152,7 @@ var _ = Describe("update plan test.", func() { It("should work well in a parallel plan", func() { By("build a parallel plan") - strategy := workloads.ParallelUpdateStrategy - its.Spec.MemberUpdateStrategy = &strategy + its.Spec.MemberUpdateStrategy = ptr.To(workloads.ParallelUpdateStrategy) expectedPlan := [][]*corev1.Pod{ {pod0, pod1, pod2, pod3, pod4, pod5, pod6}, } @@ -163,8 +161,7 @@ var _ = Describe("update plan test.", func() { It("should work well in a best effort parallel", func() { By("build a best effort parallel plan") - strategy := workloads.BestEffortParallelUpdateStrategy - its.Spec.MemberUpdateStrategy = &strategy + its.Spec.MemberUpdateStrategy = ptr.To(workloads.BestEffortParallelUpdateStrategy) expectedPlan := [][]*corev1.Pod{ {pod2, pod3, pod4, pod6, pod1}, {pod0}, @@ -175,8 +172,7 @@ var _ = Describe("update plan test.", func() { It("should work well with role-less and heterogeneous pods", func() { By("build a serial plan with role-less and heterogeneous pods") - strategy := workloads.SerialUpdateStrategy - its.Spec.MemberUpdateStrategy = &strategy + its.Spec.MemberUpdateStrategy = ptr.To(workloads.SerialUpdateStrategy) its.Spec.Roles = nil for _, pod := range []*corev1.Pod{pod0, pod1, pod2, pod3, pod4, pod5, pod6} { labels := pod.Labels diff --git a/pkg/controller/instanceset/utils.go b/pkg/controller/instanceset/utils.go index ca71494610a..dddca5fbfb0 100644 --- a/pkg/controller/instanceset/utils.go +++ b/pkg/controller/instanceset/utils.go @@ -183,3 +183,11 @@ func CalculateConcurrencyReplicas(concurrency *intstr.IntOrString, replicas int) pValue = integer.IntMax(integer.IntMin(pValue, replicas), 1) return pValue, nil } + +func getMemberUpdateStrategy(its *workloads.InstanceSet) workloads.MemberUpdateStrategy { + updateStrategy := workloads.SerialUpdateStrategy + if its.Spec.MemberUpdateStrategy != nil { + updateStrategy = *its.Spec.MemberUpdateStrategy + } + return updateStrategy +} diff --git a/pkg/testutil/apps/constant.go b/pkg/testutil/apps/constant.go index e33421c7f77..e3366e64d13 100644 --- a/pkg/testutil/apps/constant.go +++ b/pkg/testutil/apps/constant.go @@ -25,6 +25,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" ) @@ -210,7 +211,7 @@ var ( }, }, }, - UpdateStrategy: &[]appsv1.UpdateStrategy{appsv1.BestEffortParallelStrategy}[0], + UpdateStrategy: ptr.To(appsv1.BestEffortParallelStrategy), Roles: []appsv1.ReplicaRole{ { Name: "leader", diff --git a/pkg/testutil/apps/instance_set_factoy.go b/pkg/testutil/apps/instance_set_factoy.go index b78b8198645..7ba83fc3e09 100644 --- a/pkg/testutil/apps/instance_set_factoy.go +++ b/pkg/testutil/apps/instance_set_factoy.go @@ -20,10 +20,10 @@ along with this program. If not, see . package apps import ( - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" ) @@ -60,8 +60,8 @@ func NewInstanceSetFactory(namespace, name string, clusterName string, component }, }, }, - UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ - Type: appsv1.OnDeleteStatefulSetStrategyType, + InstanceUpdateStrategy: &workloads.InstanceUpdateStrategy{ + Type: kbappsv1.OnDeleteStrategyType, }, }, }, f)