diff --git a/apis/apps/v1/deprecated.go b/apis/apps/v1/deprecated.go index e4be8f591a7..116d59510d3 100644 --- a/apis/apps/v1/deprecated.go +++ b/apis/apps/v1/deprecated.go @@ -23,7 +23,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" viper "github.com/apecloud/kubeblocks/pkg/viperx" ) @@ -217,7 +216,6 @@ func (t *InstanceTemplate) GetReplicas() int32 { return defaultInstanceTemplateReplicas } -// GetOrdinals TODO(free6om): Remove after resolving the circular dependencies between apps and workloads. -func (t *InstanceTemplate) GetOrdinals() workloads.Ordinals { - return workloads.Ordinals{} +func (t *InstanceTemplate) GetOrdinals() Ordinals { + return t.Ordinals } diff --git a/apis/apps/v1/types.go b/apis/apps/v1/types.go index 0cc99cfc120..5cf93cd6f2e 100644 --- a/apis/apps/v1/types.go +++ b/apis/apps/v1/types.go @@ -613,6 +613,15 @@ type InstanceTemplate struct { // +optional Replicas *int32 `json:"replicas,omitempty"` + // Specifies the desired Ordinals of this InstanceTemplate. + // The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + // + // For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + // then the instance names generated under this InstanceTemplate would be + // $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + // $(cluster.name)-$(component.name)-$(template.name)-7 + Ordinals Ordinals `json:"ordinals,omitempty"` + // Specifies a map of key-value pairs to be merged into the Pod's existing annotations. // Existing keys will have their values overwritten, while new keys will be added to the annotations. // @@ -661,3 +670,16 @@ type InstanceTemplate struct { // +optional VolumeClaimTemplates []ClusterComponentVolumeClaimTemplate `json:"volumeClaimTemplates,omitempty"` } + +// Range represents a range with a start and an end value. +// It is used to define a continuous segment. +type Range struct { + Start int32 `json:"start"` + End int32 `json:"end"` +} + +// Ordinals represents a combination of continuous segments and individual values. +type Ordinals struct { + Ranges []Range `json:"ranges,omitempty"` + Discrete []int32 `json:"discrete,omitempty"` +} diff --git a/apis/apps/v1/zz_generated.deepcopy.go b/apis/apps/v1/zz_generated.deepcopy.go index 1e9b6d2c9df..4524d020a5e 100644 --- a/apis/apps/v1/zz_generated.deepcopy.go +++ b/apis/apps/v1/zz_generated.deepcopy.go @@ -2108,6 +2108,7 @@ func (in *InstanceTemplate) DeepCopyInto(out *InstanceTemplate) { *out = new(int32) **out = **in } + in.Ordinals.DeepCopyInto(&out.Ordinals) if in.Annotations != nil { in, out := &in.Annotations, &out.Annotations *out = make(map[string]string, len(*in)) @@ -2292,6 +2293,31 @@ func (in *NamedVar) DeepCopy() *NamedVar { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Ordinals) DeepCopyInto(out *Ordinals) { + *out = *in + if in.Ranges != nil { + in, out := &in.Ranges, &out.Ranges + *out = make([]Range, len(*in)) + copy(*out, *in) + } + if in.Discrete != nil { + in, out := &in.Discrete, &out.Discrete + *out = make([]int32, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ordinals. +func (in *Ordinals) DeepCopy() *Ordinals { + if in == nil { + return nil + } + out := new(Ordinals) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PasswordConfig) DeepCopyInto(out *PasswordConfig) { *out = *in @@ -2369,6 +2395,21 @@ func (in *ProvisionSecretRef) DeepCopy() *ProvisionSecretRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Range) DeepCopyInto(out *Range) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Range. +func (in *Range) DeepCopy() *Range { + if in == nil { + return nil + } + out := new(Range) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplicaRole) DeepCopyInto(out *ReplicaRole) { *out = *in diff --git a/apis/workloads/v1/instanceset_types.go b/apis/workloads/v1/instanceset_types.go index 79895a8d7d9..baee0b346f8 100644 --- a/apis/workloads/v1/instanceset_types.go +++ b/apis/workloads/v1/instanceset_types.go @@ -24,6 +24,8 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" ) // +genclient @@ -83,7 +85,7 @@ type InstanceSetSpec struct { // For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, // then the instance names generated under the default template would be // $(cluster.name)-$(component.name)-0、$(cluster.name)-$(component.name)-1 and $(cluster.name)-$(component.name)-7 - DefaultTemplateOrdinals Ordinals `json:"defaultTemplateOrdinals,omitempty"` + DefaultTemplateOrdinals kbappsv1.Ordinals `json:"defaultTemplateOrdinals,omitempty"` // Defines the minimum number of seconds a newly created pod should be ready // without any of its container crashing to be considered available. @@ -306,150 +308,10 @@ type InstanceSetStatus struct { TemplatesStatus []InstanceTemplateStatus `json:"templatesStatus,omitempty"` } -// Range represents a range with a start and an end value. -// It is used to define a continuous segment. -type Range struct { - Start int32 `json:"start"` - End int32 `json:"end"` -} - -// Ordinals represents a combination of continuous segments and individual values. -type Ordinals struct { - Ranges []Range `json:"ranges,omitempty"` - Discrete []int32 `json:"discrete,omitempty"` -} - -// InstanceTemplate allows customization of individual replica configurations within a Component, -// without altering the base component template defined in ClusterComponentSpec. -// It enables the application of distinct settings to specific instances (replicas), -// providing flexibility while maintaining a common configuration baseline. -type InstanceTemplate struct { - // Name specifies the unique name of the instance Pod created using this InstanceTemplate. - // This name is constructed by concatenating the component's name, the template's name, and the instance's ordinal - // using the pattern: $(cluster.name)-$(component.name)-$(template.name)-$(ordinal). Ordinals start from 0. - // The specified name overrides any default naming conventions or patterns. - // - // +kubebuilder:validation:MaxLength=54 - // +kubebuilder:validation:Pattern:=`^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$` - // +kubebuilder:validation:Required - Name string `json:"name"` - - // Specifies the number of instances (Pods) to create from this InstanceTemplate. - // This field allows setting how many replicated instances of the component, - // with the specific overrides in the InstanceTemplate, are created. - // The default value is 1. A value of 0 disables instance creation. - // - // +kubebuilder:default=1 - // +kubebuilder:validation:Minimum=0 - // +optional - Replicas *int32 `json:"replicas,omitempty"` - - // Specifies the desired Ordinals of this InstanceTemplate. - // The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. - // - // For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, - // then the instance names generated under this InstanceTemplate would be - // $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and - // $(cluster.name)-$(component.name)-$(template.name)-7 - Ordinals Ordinals `json:"ordinals,omitempty"` - - // Specifies a map of key-value pairs to be merged into the Pod's existing annotations. - // Existing keys will have their values overwritten, while new keys will be added to the annotations. - // - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Specifies a map of key-value pairs that will be merged into the Pod's existing labels. - // Values for existing keys will be overwritten, and new keys will be added. - // - // +optional - Labels map[string]string `json:"labels,omitempty"` - - // Specifies an override for the first container's image in the pod. - // - // +optional - Image *string `json:"image,omitempty"` - - // Specifies the scheduling policy for the Component. - // - // +optional - SchedulingPolicy *SchedulingPolicy `json:"schedulingPolicy,omitempty"` - - // Specifies an override for the resource requirements of the first container in the Pod. - // This field allows for customizing resource allocation (CPU, memory, etc.) for the container. - // - // +optional - Resources *corev1.ResourceRequirements `json:"resources,omitempty"` - - // Defines Env to override. - // Add new or override existing envs. - // +optional - Env []corev1.EnvVar `json:"env,omitempty"` - - // Defines Volumes to override. - // Add new or override existing volumes. - // +optional - Volumes []corev1.Volume `json:"volumes,omitempty"` - - // Defines VolumeMounts to override. - // Add new or override existing volume mounts of the first container in the pod. - // +optional - VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` - - // Defines VolumeClaimTemplates to override. - // Add new or override existing volume claim templates. - // +optional - VolumeClaimTemplates []corev1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"` -} - -// SchedulingPolicy the scheduling policy. -// Deprecated: Unify with apps/v1alpha1.SchedulingPolicy -type SchedulingPolicy struct { - // If specified, the Pod will be dispatched by specified scheduler. - // If not specified, the Pod will be dispatched by default scheduler. - // - // +optional - SchedulerName string `json:"schedulerName,omitempty"` - - // NodeSelector is a selector which must be true for the Pod to fit on a node. - // Selector which must match a node's labels for the Pod to be scheduled on that node. - // More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ - // - // +optional - // +mapType=atomic - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - - // NodeName is a request to schedule this Pod onto a specific node. If it is non-empty, - // the scheduler simply schedules this Pod onto that node, assuming that it fits resource - // requirements. - // - // +optional - NodeName string `json:"nodeName,omitempty"` - - // Specifies a group of affinity scheduling rules of the Cluster, including NodeAffinity, PodAffinity, and PodAntiAffinity. - // - // +optional - Affinity *corev1.Affinity `json:"affinity,omitempty"` - - // Allows Pods to be scheduled onto nodes with matching taints. - // Each toleration in the array allows the Pod to tolerate node taints based on - // specified `key`, `value`, `effect`, and `operator`. - // - // - The `key`, `value`, and `effect` identify the taint that the toleration matches. - // - The `operator` determines how the toleration matches the taint. - // - // Pods with matching tolerations are allowed to be scheduled on tainted nodes, typically reserved for specific purposes. - // - // +optional - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - - // TopologySpreadConstraints describes how a group of Pods ought to spread across topology - // domains. Scheduler will schedule Pods in a way which abides by the constraints. - // All topologySpreadConstraints are ANDed. - // - // +optional - TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` -} +// InstanceTemplate allows customization of individual replica configurations in a Component. +// +// +kubebuilder:object:generate=false +type InstanceTemplate = kbappsv1.InstanceTemplate type PodUpdatePolicyType string @@ -564,6 +426,11 @@ type MembershipReconfiguration struct { // // +optional PromoteAction *Action `json:"promoteAction,omitempty"` + + // Defines the procedure for a controlled transition of a role to a new replica. + // + // +optional + Switchover *kbappsv1.Action `json:"switchover,omitempty"` } // MemberUpdateStrategy defines Cluster Component update strategy. @@ -687,19 +554,63 @@ const ( ReasonInstanceUpdateRestricted = "InstanceUpdateRestricted" ) -const defaultInstanceTemplateReplicas = 1 +// IsInstancesReady gives Instance level 'ready' state when all instances are available +func (r *InstanceSet) IsInstancesReady() bool { + if r == nil { + return false + } + // check whether the cluster has been initialized + if r.Status.ReadyInitReplicas != r.Status.InitReplicas { + return false + } + // check whether latest spec has been sent to the underlying workload + if r.Status.ObservedGeneration != r.Generation { + return false + } + // check whether the underlying workload is ready + if r.Spec.Replicas == nil { + return false + } + replicas := *r.Spec.Replicas + if r.Status.Replicas != replicas || + r.Status.ReadyReplicas != replicas || + r.Status.UpdatedReplicas != replicas { + return false + } + // check availableReplicas only if minReadySeconds is set + if r.Spec.MinReadySeconds > 0 && r.Status.AvailableReplicas != replicas { + return false + } -func (t *InstanceTemplate) GetName() string { - return t.Name + return true } -func (t *InstanceTemplate) GetReplicas() int32 { - if t.Replicas != nil { - return *t.Replicas +// IsInstanceSetReady gives InstanceSet level 'ready' state: +// 1. all instances are available +// 2. and all members have role set (if they are role-ful) +func (r *InstanceSet) IsInstanceSetReady() bool { + instancesReady := r.IsInstancesReady() + if !instancesReady { + return false } - return defaultInstanceTemplateReplicas -} -func (t *InstanceTemplate) GetOrdinals() Ordinals { - return t.Ordinals + // check whether role probe has done + if len(r.Spec.Roles) == 0 { + return true + } + membersStatus := r.Status.MembersStatus + if len(membersStatus) != int(*r.Spec.Replicas) { + return false + } + if r.Status.ReadyWithoutPrimary { + return true + } + hasLeader := false + for _, status := range membersStatus { + if status.ReplicaRole != nil && status.ReplicaRole.IsLeader { + hasLeader = true + break + } + } + return hasLeader } diff --git a/apis/workloads/v1/zz_generated.deepcopy.go b/apis/workloads/v1/zz_generated.deepcopy.go index 5c605826880..8c3d581537d 100644 --- a/apis/workloads/v1/zz_generated.deepcopy.go +++ b/apis/workloads/v1/zz_generated.deepcopy.go @@ -24,6 +24,7 @@ along with this program. If not, see . package v1 import ( + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -168,7 +169,7 @@ func (in *InstanceSetSpec) DeepCopyInto(out *InstanceSetSpec) { in.Template.DeepCopyInto(&out.Template) if in.Instances != nil { in, out := &in.Instances, &out.Instances - *out = make([]InstanceTemplate, len(*in)) + *out = make([]appsv1.InstanceTemplate, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -271,84 +272,6 @@ func (in *InstanceSetStatus) DeepCopy() *InstanceSetStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *InstanceTemplate) DeepCopyInto(out *InstanceTemplate) { - *out = *in - if in.Replicas != nil { - in, out := &in.Replicas, &out.Replicas - *out = new(int32) - **out = **in - } - in.Ordinals.DeepCopyInto(&out.Ordinals) - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Image != nil { - in, out := &in.Image, &out.Image - *out = new(string) - **out = **in - } - if in.SchedulingPolicy != nil { - in, out := &in.SchedulingPolicy, &out.SchedulingPolicy - *out = new(SchedulingPolicy) - (*in).DeepCopyInto(*out) - } - if in.Resources != nil { - in, out := &in.Resources, &out.Resources - *out = new(corev1.ResourceRequirements) - (*in).DeepCopyInto(*out) - } - if in.Env != nil { - in, out := &in.Env, &out.Env - *out = make([]corev1.EnvVar, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Volumes != nil { - in, out := &in.Volumes, &out.Volumes - *out = make([]corev1.Volume, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.VolumeMounts != nil { - in, out := &in.VolumeMounts, &out.VolumeMounts - *out = make([]corev1.VolumeMount, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.VolumeClaimTemplates != nil { - in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates - *out = make([]corev1.PersistentVolumeClaim, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceTemplate. -func (in *InstanceTemplate) DeepCopy() *InstanceTemplate { - if in == nil { - return nil - } - out := new(InstanceTemplate) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InstanceTemplateStatus) DeepCopyInto(out *InstanceTemplateStatus) { *out = *in @@ -412,6 +335,11 @@ func (in *MembershipReconfiguration) DeepCopyInto(out *MembershipReconfiguration *out = new(Action) (*in).DeepCopyInto(*out) } + if in.Switchover != nil { + in, out := &in.Switchover, &out.Switchover + *out = new(appsv1.Action) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MembershipReconfiguration. @@ -424,46 +352,6 @@ func (in *MembershipReconfiguration) DeepCopy() *MembershipReconfiguration { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Ordinals) DeepCopyInto(out *Ordinals) { - *out = *in - if in.Ranges != nil { - in, out := &in.Ranges, &out.Ranges - *out = make([]Range, len(*in)) - copy(*out, *in) - } - if in.Discrete != nil { - in, out := &in.Discrete, &out.Discrete - *out = make([]int32, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ordinals. -func (in *Ordinals) DeepCopy() *Ordinals { - if in == nil { - return nil - } - out := new(Ordinals) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Range) DeepCopyInto(out *Range) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Range. -func (in *Range) DeepCopy() *Range { - if in == nil { - return nil - } - out := new(Range) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplicaRole) DeepCopyInto(out *ReplicaRole) { *out = *in @@ -478,44 +366,3 @@ func (in *ReplicaRole) DeepCopy() *ReplicaRole { 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 - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Affinity != nil { - in, out := &in.Affinity, &out.Affinity - *out = new(corev1.Affinity) - (*in).DeepCopyInto(*out) - } - if in.Tolerations != nil { - in, out := &in.Tolerations, &out.Tolerations - *out = make([]corev1.Toleration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.TopologySpreadConstraints != nil { - in, out := &in.TopologySpreadConstraints, &out.TopologySpreadConstraints - *out = make([]corev1.TopologySpreadConstraint, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulingPolicy. -func (in *SchedulingPolicy) DeepCopy() *SchedulingPolicy { - if in == nil { - return nil - } - out := new(SchedulingPolicy) - in.DeepCopyInto(out) - return out -} diff --git a/config/crd/bases/apps.kubeblocks.io_clusters.yaml b/config/crd/bases/apps.kubeblocks.io_clusters.yaml index 2d4cc34c940..017f1ef8d9a 100644 --- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml +++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml @@ -573,6 +573,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- @@ -9216,6 +9250,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- diff --git a/config/crd/bases/apps.kubeblocks.io_components.yaml b/config/crd/bases/apps.kubeblocks.io_components.yaml index 9da4fd1e563..4b2c768c021 100644 --- a/config/crd/bases/apps.kubeblocks.io_components.yaml +++ b/config/crd/bases/apps.kubeblocks.io_components.yaml @@ -455,6 +455,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- diff --git a/config/crd/bases/operations.kubeblocks.io_opsrequests.yaml b/config/crd/bases/operations.kubeblocks.io_opsrequests.yaml index 4dd1102ce19..2637b8516e3 100644 --- a/config/crd/bases/operations.kubeblocks.io_opsrequests.yaml +++ b/config/crd/bases/operations.kubeblocks.io_opsrequests.yaml @@ -793,6 +793,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- @@ -5317,6 +5351,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- diff --git a/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml b/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml index a8e587f9cda..1c5f705f244 100644 --- a/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml +++ b/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml @@ -331,11 +331,8 @@ spec: The sum of replicas across all InstanceTemplates should not exceed the total number of Replicas specified for the InstanceSet. Any remaining replicas will be generated using the default template and will follow the default naming rules. items: - description: |- - InstanceTemplate allows customization of individual replica configurations within a Component, - without altering the base component template defined in ClusterComponentSpec. - It enables the application of distinct settings to specific instances (replicas), - providing flexibility while maintaining a common configuration baseline. + description: InstanceTemplate allows customization of individual + replica configurations in a Component. properties: annotations: additionalProperties: @@ -462,7 +459,7 @@ spec: type: array image: description: Specifies an override for the first container's - image in the pod. + image in the Pod. type: string labels: additionalProperties: @@ -474,7 +471,7 @@ spec: name: description: |- Name specifies the unique name of the instance Pod created using this InstanceTemplate. - This name is constructed by concatenating the component's name, the template's name, and the instance's ordinal + This name is constructed by concatenating the Component's name, the template's name, and the instance's ordinal using the pattern: $(cluster.name)-$(component.name)-$(template.name)-$(ordinal). Ordinals start from 0. The specified name overrides any default naming conventions or patterns. maxLength: 54 @@ -518,7 +515,7 @@ spec: default: 1 description: |- Specifies the number of instances (Pods) to create from this InstanceTemplate. - This field allows setting how many replicated instances of the component, + This field allows setting how many replicated instances of the Component, with the specific overrides in the InstanceTemplate, are created. The default value is 1. A value of 0 disables instance creation. format: int32 @@ -1722,461 +1719,108 @@ spec: A constraint is considered "Unsatisfiable" for an incoming pod if and only if every possible node assignment for that pod would violate "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - type: object - volumeClaimTemplates: - description: |- - Defines VolumeClaimTemplates to override. - Add new or override existing volume claim templates. - items: - description: PersistentVolumeClaim is a user's request for - and claim to a persistent volume - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - description: |- - Standard object's metadata. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - description: |- - spec defines the desired characteristics of a volume requested by a pod author. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims - properties: - accessModes: - description: |- - accessModes contains the desired access modes the volume should have. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 - items: - type: string - type: array - dataSource: - description: |- - dataSource field can be used to specify either: - * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller can support the specified data source, - it will create a new volume based on the contents of the specified data source. - When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, - and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef will not be copied to dataSource. - properties: - apiGroup: - description: |- - APIGroup is the group for the resource being referenced. - If APIGroup is not specified, the specified Kind must be in the core API group. - For any other third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: |- - dataSourceRef specifies the object from which to populate the volume with data, if a non-empty - volume is desired. This may be any object from a non-empty API group (non - core object) or a PersistentVolumeClaim object. - When this field is specified, volume binding will only succeed if the type of - the specified object matches some installed volume populator or dynamic - provisioner. - This field will replace the functionality of the dataSource field and as such - if both fields are non-empty, they must have the same value. For backwards - compatibility, when namespace isn't specified in dataSourceRef, - both fields (dataSource and dataSourceRef) will be set to the same - value automatically if one of them is empty and the other is non-empty. - When namespace is specified in dataSourceRef, - dataSource isn't set to the same value and must be empty. - There are three important differences between dataSource and dataSourceRef: - * While dataSource only allows two specific types of objects, dataSourceRef - allows any non-core object, as well as PersistentVolumeClaim objects. - * While dataSource ignores disallowed values (dropping them), dataSourceRef - preserves all values, and generates an error if a disallowed value is - specified. - * While dataSource only allows local objects, dataSourceRef allows objects - in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. - (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. - properties: - apiGroup: - description: |- - APIGroup is the group for the resource being referenced. - If APIGroup is not specified, the specified Kind must be in the core API group. - For any other third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - namespace: - description: |- - Namespace is the namespace of resource being referenced - Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. - type: string - required: - - kind - - name - type: object - resources: - description: |- - resources represents the minimum resources the volume should have. - If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements - that are lower than previous value but must still be higher than capacity recorded in the - status field of the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - selector: - description: selector is a label query over volumes - to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - storageClassName: - description: |- - storageClassName is the name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 - type: string - volumeAttributesClassName: - description: |- - volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. - If specified, the CSI driver will create or update the volume with the attributes defined - in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, - it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass - will be applied to the claim but it's not allowed to reset this field to empty string once it is set. - If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass - will be set by the persistentvolume controller if it exists. - If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be - set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource - exists. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass - (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. - type: string - volumeMode: - description: |- - volumeMode defines what type of volume is required by the claim. - Value of Filesystem is implied when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference to - the PersistentVolume backing this claim. - type: string - type: object - status: - description: |- - status represents the current information/status of a persistent volume claim. - Read-only. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims - properties: - accessModes: - description: |- - accessModes contains the actual access modes the volume backing the PVC has. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: |- - When a controller receives persistentvolume claim update with ClaimResourceStatus for a resource - that it does not recognizes, then it should ignore that update and let other controllers - handle it. - type: string - description: "allocatedResourceStatuses stores status - of resource being resized for the given PVC.\nKey - names follow standard Kubernetes label syntax. Valid - values are either:\n\t* Un-prefixed keys:\n\t\t- - storage - the capacity of the volume.\n\t* Custom - resources must use implementation-defined prefixed - names such as \"example.com/my-custom-resource\"\nApart - from above values - keys that are unprefixed or - have kubernetes.io prefix are considered\nreserved - and hence may not be used.\n\n\nClaimResourceStatus - can be in any of following states:\n\t- ControllerResizeInProgress:\n\t\tState - set when resize controller starts resizing the volume - in control-plane.\n\t- ControllerResizeFailed:\n\t\tState - set when resize has failed in resize controller - with a terminal error.\n\t- NodeResizePending:\n\t\tState - set when resize controller has finished resizing - the volume but further resizing of\n\t\tvolume is - needed on the node.\n\t- NodeResizeInProgress:\n\t\tState - set when kubelet starts resizing the volume.\n\t- - NodeResizeFailed:\n\t\tState set when resizing has - failed in kubelet with a terminal error. Transient - errors don't set\n\t\tNodeResizeFailed.\nFor example: - if expanding a PVC for more capacity - this field - can be one of the following states:\n\t- pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\"\n - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\"\n - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\"\n - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\"\n - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\"\nWhen this field is not set, - it means that no resize operation is in progress - for the given PVC.\n\n\nA controller that receives - PVC update with previously unknown resourceName - or ClaimResourceStatus\nshould ignore the update - for the purpose it was designed. For example - a - controller that\nonly is responsible for resizing - capacity of the volume, should ignore PVC updates - that change other valid\nresources associated with - PVC.\n\n\nThis is an alpha field and requires enabling - RecoverVolumeExpansionFailure feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks the resources - allocated to a PVC including its capacity.\nKey - names follow standard Kubernetes label syntax. Valid - values are either:\n\t* Un-prefixed keys:\n\t\t- - storage - the capacity of the volume.\n\t* Custom - resources must use implementation-defined prefixed - names such as \"example.com/my-custom-resource\"\nApart - from above values - keys that are unprefixed or - have kubernetes.io prefix are considered\nreserved - and hence may not be used.\n\n\nCapacity reported - here may be larger than the actual capacity when - a volume expansion operation\nis requested.\nFor - storage quota, the larger value from allocatedResources - and PVC.spec.resources is used.\nIf allocatedResources - is not set, PVC.spec.resources alone is used for - quota calculation.\nIf a volume expansion capacity - request is lowered, allocatedResources is only\nlowered - if there are no expansion operations in progress - and if the actual volume capacity\nis equal or lower - than the requested capacity.\n\n\nA controller that - receives PVC update with previously unknown resourceName\nshould - ignore the update for the purpose it was designed. - For example - a controller that\nonly is responsible - for resizing capacity of the volume, should ignore - PVC updates that change other valid\nresources associated - with PVC.\n\n\nThis is an alpha field and requires - enabling RecoverVolumeExpansionFailure feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual resources - of the underlying volume. - type: object - conditions: - description: |- - conditions is the current Condition of persistent volume claim. If underlying persistent volume is being - resized then the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contains - details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time we probed - the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the time - the condition transitioned from one status - to another. - format: date-time - type: string - message: - description: message is the human-readable message - indicating details about last transition. - type: string - reason: - description: |- - reason is a unique, this should be a short, machine understandable string that gives the reason - for condition's last transition. If it reports "ResizeStarted" that means the underlying - persistent volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - currentVolumeAttributesClassName: - description: |- - currentVolumeAttributesClassName is the current name of the VolumeAttributesClass the PVC is using. - When unset, there is no VolumeAttributeClass applied to this PersistentVolumeClaim - This is an alpha field and requires enabling VolumeAttributesClass feature. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. type: string - modifyVolumeStatus: + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + volumeClaimTemplates: + description: |- + Defines VolumeClaimTemplates to override. + Add new or override existing volume claim templates. + items: + properties: + name: + description: |- + Refers to the name of a volumeMount defined in either: + + + - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` (deprecated) + + + The value of `name` must match the `name` field of a volumeMount specified in the corresponding `volumeMounts` array. + type: string + spec: + description: |- + Defines the desired characteristics of a PersistentVolumeClaim that will be created for the volume + with the mount name specified in the `name` field. + + + When a Pod is created for this ClusterComponent, a new PVC will be created based on the specification + defined in the `spec` field. The PVC will be associated with the volume mount specified by the `name` field. + properties: + accessModes: description: |- - ModifyVolumeStatus represents the status object of ControllerModifyVolume operation. - When this is unset, there is no ModifyVolume operation being attempted. - This is an alpha field and requires enabling VolumeAttributesClass feature. + Contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1. + items: + type: string + type: array + x-kubernetes-preserve-unknown-fields: true + resources: + description: |- + Represents the minimum resources the volume should have. + If the RecoverVolumeExpansionFailure feature is enabled, users are allowed to specify resource requirements that + are lower than the previous value but must still be higher than the capacity recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources. properties: - status: - description: "status is the status of the ControllerModifyVolume - operation. It can be in any of following states:\n - - Pending\n Pending indicates that the PersistentVolumeClaim - cannot be modified due to unmet requirements, - such as\n the specified VolumeAttributesClass - not existing.\n - InProgress\n InProgress - indicates that the volume is being modified.\n - - Infeasible\n Infeasible indicates that the - request has been rejected as invalid by the - CSI driver. To\n\t resolve the error, a valid - VolumeAttributesClass needs to be specified.\nNote: - New statuses can be added in the future. Consumers - should check for unknown statuses and fail appropriately." - type: string - targetVolumeAttributesClassName: - description: targetVolumeAttributesClassName is - the name of the VolumeAttributesClass the PVC - currently being reconciled - type: string - required: - - status + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object type: object - phase: - description: phase represents the current phase of - PersistentVolumeClaim. + x-kubernetes-preserve-unknown-fields: true + storageClassName: + description: |- + The name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1. + type: string + volumeMode: + description: Defines what type of volume is required + by the claim, either Block or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: description: |- Defines VolumeMounts to override. - Add new or override existing volume mounts of the first container in the pod. + Add new or override existing volume mounts of the first container in the Pod. items: description: VolumeMount describes a mounting of a Volume within a container. @@ -4061,6 +3705,275 @@ spec: required: - command type: object + switchover: + description: Defines the procedure for a controlled transition + of a role to a new replica. + properties: + exec: + description: |- + Defines the command to run. + + + This field cannot be updated. + properties: + args: + description: Args represents the arguments that are passed + to the `command` for execution. + items: + type: string + type: array + command: + description: |- + Specifies the command to be executed inside the container. + The working directory for this command is the container's root directory('/'). + Commands are executed directly without a shell environment, meaning shell-specific syntax ('|', etc.) is not supported. + If the shell is required, it must be explicitly invoked in the command. + + + A successful execution is indicated by an exit status of 0; any non-zero status signifies a failure. + items: + type: string + type: array + container: + description: |- + Specifies the name of the container within the same pod whose resources will be shared with the action. + This allows the action to utilize the specified container's resources without executing within it. + + + The name must match one of the containers defined in `componentDefinition.spec.runtime`. + + + The resources that can be shared are included: + + + - volume mounts + + + This field cannot be updated. + type: string + env: + description: |- + Represents a list of environment variables that will be injected into the container. + These variables enable the container to adapt its behavior based on the environment it's running in. + + + This field cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: |- + Specifies the container image to be used for running the Action. + + + When specified, a dedicated container will be created using this image to execute the Action. + All actions with same image will share the same container. + + + This field cannot be updated. + type: string + matchingKey: + description: |- + Used in conjunction with the `targetPodSelector` field to refine the selection of target pod(s) for Action execution. + The impact of this field depends on the `targetPodSelector` value: + + + - When `targetPodSelector` is set to `Any` or `All`, this field will be ignored. + - When `targetPodSelector` is set to `Role`, only those replicas whose role matches the `matchingKey` + will be selected for the Action. + + + This field cannot be updated. + type: string + targetPodSelector: + description: |- + Defines the criteria used to select the target Pod(s) for executing the Action. + This is useful when there is no default target replica identified. + It allows for precise control over which Pod(s) the Action should run in. + + + If not specified, the Action will be executed in the pod where the Action is triggered, such as the pod + to be removed or added; or a random pod if the Action is triggered at the component level, such as + post-provision or pre-terminate of the component. + + + This field cannot be updated. + enum: + - Any + - All + - Role + - Ordinal + type: string + type: object + preCondition: + description: |- + Specifies the state that the cluster must reach before the Action is executed. + Currently, this is only applicable to the `postProvision` action. + + + The conditions are as follows: + + + - `Immediately`: Executed right after the Component object is created. + The readiness of the Component and its resources is not guaranteed at this stage. + - `RuntimeReady`: The Action is triggered after the Component object has been created and all associated + runtime resources (e.g. Pods) are in a ready state. + - `ComponentReady`: The Action is triggered after the Component itself is in a ready state. + This process does not affect the readiness state of the Component or the Cluster. + - `ClusterReady`: The Action is executed after the Cluster is in a ready state. + This execution does not alter the Component or the Cluster's state of readiness. + + + This field cannot be updated. + type: string + retryPolicy: + description: |- + Defines the strategy to be taken when retrying the Action after a failure. + + + It specifies the conditions under which the Action should be retried and the limits to apply, + such as the maximum number of retries and backoff strategy. + + + This field cannot be updated. + properties: + maxRetries: + default: 0 + description: |- + Defines the maximum number of retry attempts that should be made for a given Action. + This value is set to 0 by default, indicating that no retries will be made. + type: integer + retryInterval: + default: 0 + description: |- + Indicates the duration of time to wait between each retry attempt. + This value is set to 0 by default, indicating that there will be no delay between retry attempts. + format: int64 + type: integer + type: object + timeoutSeconds: + default: 0 + description: |- + Specifies the maximum duration in seconds that the Action is allowed to run. + + + If the Action does not complete within this time frame, it will be terminated. + + + This field cannot be updated. + format: int32 + type: integer + type: object switchoverAction: description: |- Specifies the environment variables that can be used in all following Actions: diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go index 3408711a905..1598da82963 100644 --- a/controllers/apps/transformer_component_account_provision.go +++ b/controllers/apps/transformer_component_account_provision.go @@ -32,8 +32,8 @@ import ( "github.com/apecloud/kubeblocks/pkg/common" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" - "github.com/apecloud/kubeblocks/pkg/controller/component/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/graph" + "github.com/apecloud/kubeblocks/pkg/controller/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/model" ) @@ -182,7 +182,8 @@ func (t *componentAccountProvisionTransformer) lifecycleAction(transCtx *compone if err != nil { return nil, err } - lfa, err := lifecycle.New(transCtx.SynthesizeComponent, nil, pods...) + lfa, err := lifecycle.New(synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name, + synthesizedComp.LifecycleActions, synthesizedComp.TemplateVars, nil, pods...) if err != nil { return nil, err } diff --git a/controllers/apps/transformer_component_post_provision.go b/controllers/apps/transformer_component_post_provision.go index e9df8fe293b..f8ebb050815 100644 --- a/controllers/apps/transformer_component_post_provision.go +++ b/controllers/apps/transformer_component_post_provision.go @@ -24,8 +24,8 @@ import ( "time" "github.com/apecloud/kubeblocks/pkg/controller/component" - "github.com/apecloud/kubeblocks/pkg/controller/component/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/graph" + "github.com/apecloud/kubeblocks/pkg/controller/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/model" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -96,7 +96,8 @@ func (t *componentPostProvisionTransformer) lifecycleAction4Component(transCtx * // TODO: (good-first-issue) we should handle the case that the component has no pods return nil, fmt.Errorf("has no pods to running the post-provision action") } - return lifecycle.New(transCtx.SynthesizeComponent, nil, pods...) + return lifecycle.New(synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name, + synthesizedComp.LifecycleActions, synthesizedComp.TemplateVars, nil, pods...) } func checkPostProvisionDone(transCtx *componentTransformContext) bool { diff --git a/controllers/apps/transformer_component_pre_terminate.go b/controllers/apps/transformer_component_pre_terminate.go index c4a73df773c..26f8769eecf 100644 --- a/controllers/apps/transformer_component_pre_terminate.go +++ b/controllers/apps/transformer_component_pre_terminate.go @@ -29,8 +29,8 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" - "github.com/apecloud/kubeblocks/pkg/controller/component/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/graph" + "github.com/apecloud/kubeblocks/pkg/controller/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/model" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -137,7 +137,8 @@ func (t *componentPreTerminateTransformer) lifecycleAction4Component(transCtx *c // TODO: (good-first-issue) we should handle the case that the component has no pods return nil, fmt.Errorf("has no pods to running the pre-terminate action") } - return lifecycle.New(synthesizedComp, nil, pods...) + return lifecycle.New(synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name, + synthesizedComp.LifecycleActions, synthesizedComp.TemplateVars, nil, pods...) } func (t *componentPreTerminateTransformer) synthesizedComponent(transCtx *componentTransformContext, compDef *appsv1.ComponentDefinition) (*component.SynthesizedComponent, error) { diff --git a/controllers/apps/transformer_component_service.go b/controllers/apps/transformer_component_service.go index 9f104f4d0aa..ba2ae5368b9 100644 --- a/controllers/apps/transformer_component_service.go +++ b/controllers/apps/transformer_component_service.go @@ -354,5 +354,5 @@ func generatePodNamesByITS(its *workloads.InstanceSet) ([]string, error) { for i := range its.Spec.Instances { templates = append(templates, &its.Spec.Instances[i]) } - return instanceset.GenerateAllInstanceNames(its.Name, *its.Spec.Replicas, templates, its.Spec.OfflineInstances, workloads.Ordinals{}) + return instanceset.GenerateAllInstanceNames(its.Name, *its.Spec.Replicas, templates, its.Spec.OfflineInstances, appsv1.Ordinals{}) } diff --git a/controllers/apps/transformer_component_status.go b/controllers/apps/transformer_component_status.go index b799498a214..e049518b618 100644 --- a/controllers/apps/transformer_component_status.go +++ b/controllers/apps/transformer_component_status.go @@ -39,7 +39,6 @@ import ( "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/graph" - "github.com/apecloud/kubeblocks/pkg/controller/instanceset" "github.com/apecloud/kubeblocks/pkg/controller/model" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -230,7 +229,7 @@ func (t *componentStatusTransformer) isInstanceSetRunning() bool { if !t.isWorkloadUpdated() { return false } - return instanceset.IsInstanceSetReady(t.runningITS) + return t.runningITS.IsInstanceSetReady() } // isAllConfigSynced checks if all configTemplates are synced. diff --git a/controllers/apps/transformer_component_workload.go b/controllers/apps/transformer_component_workload.go index d4172c52ea3..50d8b1c6881 100644 --- a/controllers/apps/transformer_component_workload.go +++ b/controllers/apps/transformer_component_workload.go @@ -44,10 +44,10 @@ import ( workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" - "github.com/apecloud/kubeblocks/pkg/controller/component/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/configuration" "github.com/apecloud/kubeblocks/pkg/controller/factory" "github.com/apecloud/kubeblocks/pkg/controller/graph" + "github.com/apecloud/kubeblocks/pkg/controller/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/model" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -542,7 +542,8 @@ func (r *componentWorkloadOps) expandVolume() error { for i := range r.runningITS.Spec.Instances { runningInsSpec := r.runningITS.Spec.DeepCopy() runningInsTPL := runningInsSpec.Instances[i] - intctrlutil.MergeList(&runningInsTPL.VolumeClaimTemplates, &runningInsSpec.VolumeClaimTemplates, + vcts := intctrlutil.ToCoreV1PVCs(runningInsTPL.VolumeClaimTemplates) + intctrlutil.MergeList(&vcts, &runningInsSpec.VolumeClaimTemplates, func(item corev1.PersistentVolumeClaim) func(corev1.PersistentVolumeClaim) bool { return func(claim corev1.PersistentVolumeClaim) bool { return claim.Name == item.Name @@ -710,7 +711,9 @@ func (r *componentWorkloadOps) leaveMemberForPod(pod *corev1.Pod, pods []*corev1 return nil } - lfa, err := lifecycle.New(r.synthesizeComp, pod, pods...) + synthesizedComp := r.synthesizeComp + lfa, err := lifecycle.New(synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name, + synthesizedComp.LifecycleActions, synthesizedComp.TemplateVars, pod, pods...) if err != nil { return err } @@ -880,7 +883,9 @@ func (r *componentWorkloadOps) joinMember4ScaleOut() error { } func (r *componentWorkloadOps) joinMemberForPod(pod *corev1.Pod, pods []*corev1.Pod) error { - lfa, err := lifecycle.New(r.synthesizeComp, pod, pods...) + synthesizedComp := r.synthesizeComp + lfa, err := lifecycle.New(synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name, + synthesizedComp.LifecycleActions, synthesizedComp.TemplateVars, pod, pods...) if err != nil { return err } diff --git a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml index 2d4cc34c940..017f1ef8d9a 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml @@ -573,6 +573,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- @@ -9216,6 +9250,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- diff --git a/deploy/helm/crds/apps.kubeblocks.io_components.yaml b/deploy/helm/crds/apps.kubeblocks.io_components.yaml index 9da4fd1e563..4b2c768c021 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_components.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_components.yaml @@ -455,6 +455,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- diff --git a/deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml b/deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml index 4dd1102ce19..2637b8516e3 100755 --- a/deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml +++ b/deploy/helm/crds/operations.kubeblocks.io_opsrequests.yaml @@ -793,6 +793,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- @@ -5317,6 +5351,40 @@ spec: maxLength: 54 pattern: ^[a-z0-9]([a-z0-9\.\-]*[a-z0-9])?$ type: string + ordinals: + description: |- + Specifies the desired Ordinals of this InstanceTemplate. + The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate. + + + For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, + then the instance names generated under this InstanceTemplate would be + $(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and + $(cluster.name)-$(component.name)-$(template.name)-7 + properties: + discrete: + items: + format: int32 + type: integer + type: array + ranges: + items: + description: |- + Range represents a range with a start and an end value. + It is used to define a continuous segment. + properties: + end: + format: int32 + type: integer + start: + format: int32 + type: integer + required: + - end + - start + type: object + type: array + type: object replicas: default: 1 description: |- diff --git a/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml b/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml index a8e587f9cda..1c5f705f244 100644 --- a/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml +++ b/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml @@ -331,11 +331,8 @@ spec: The sum of replicas across all InstanceTemplates should not exceed the total number of Replicas specified for the InstanceSet. Any remaining replicas will be generated using the default template and will follow the default naming rules. items: - description: |- - InstanceTemplate allows customization of individual replica configurations within a Component, - without altering the base component template defined in ClusterComponentSpec. - It enables the application of distinct settings to specific instances (replicas), - providing flexibility while maintaining a common configuration baseline. + description: InstanceTemplate allows customization of individual + replica configurations in a Component. properties: annotations: additionalProperties: @@ -462,7 +459,7 @@ spec: type: array image: description: Specifies an override for the first container's - image in the pod. + image in the Pod. type: string labels: additionalProperties: @@ -474,7 +471,7 @@ spec: name: description: |- Name specifies the unique name of the instance Pod created using this InstanceTemplate. - This name is constructed by concatenating the component's name, the template's name, and the instance's ordinal + This name is constructed by concatenating the Component's name, the template's name, and the instance's ordinal using the pattern: $(cluster.name)-$(component.name)-$(template.name)-$(ordinal). Ordinals start from 0. The specified name overrides any default naming conventions or patterns. maxLength: 54 @@ -518,7 +515,7 @@ spec: default: 1 description: |- Specifies the number of instances (Pods) to create from this InstanceTemplate. - This field allows setting how many replicated instances of the component, + This field allows setting how many replicated instances of the Component, with the specific overrides in the InstanceTemplate, are created. The default value is 1. A value of 0 disables instance creation. format: int32 @@ -1722,461 +1719,108 @@ spec: A constraint is considered "Unsatisfiable" for an incoming pod if and only if every possible node assignment for that pod would violate "MaxSkew" on some topology. - For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same - labelSelector spread as 3/1/1: - | zone1 | zone2 | zone3 | - | P P P | P | P | - If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled - to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies - MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler - won't make it *more* imbalanced. - It's a required field. - type: string - required: - - maxSkew - - topologyKey - - whenUnsatisfiable - type: object - type: array - type: object - volumeClaimTemplates: - description: |- - Defines VolumeClaimTemplates to override. - Add new or override existing volume claim templates. - items: - description: PersistentVolumeClaim is a user's request for - and claim to a persistent volume - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - description: |- - Standard object's metadata. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - description: |- - spec defines the desired characteristics of a volume requested by a pod author. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims - properties: - accessModes: - description: |- - accessModes contains the desired access modes the volume should have. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 - items: - type: string - type: array - dataSource: - description: |- - dataSource field can be used to specify either: - * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) - * An existing PVC (PersistentVolumeClaim) - If the provisioner or an external controller can support the specified data source, - it will create a new volume based on the contents of the specified data source. - When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, - and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. - If the namespace is specified, then dataSourceRef will not be copied to dataSource. - properties: - apiGroup: - description: |- - APIGroup is the group for the resource being referenced. - If APIGroup is not specified, the specified Kind must be in the core API group. - For any other third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - required: - - kind - - name - type: object - x-kubernetes-map-type: atomic - dataSourceRef: - description: |- - dataSourceRef specifies the object from which to populate the volume with data, if a non-empty - volume is desired. This may be any object from a non-empty API group (non - core object) or a PersistentVolumeClaim object. - When this field is specified, volume binding will only succeed if the type of - the specified object matches some installed volume populator or dynamic - provisioner. - This field will replace the functionality of the dataSource field and as such - if both fields are non-empty, they must have the same value. For backwards - compatibility, when namespace isn't specified in dataSourceRef, - both fields (dataSource and dataSourceRef) will be set to the same - value automatically if one of them is empty and the other is non-empty. - When namespace is specified in dataSourceRef, - dataSource isn't set to the same value and must be empty. - There are three important differences between dataSource and dataSourceRef: - * While dataSource only allows two specific types of objects, dataSourceRef - allows any non-core object, as well as PersistentVolumeClaim objects. - * While dataSource ignores disallowed values (dropping them), dataSourceRef - preserves all values, and generates an error if a disallowed value is - specified. - * While dataSource only allows local objects, dataSourceRef allows objects - in any namespaces. - (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. - (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. - properties: - apiGroup: - description: |- - APIGroup is the group for the resource being referenced. - If APIGroup is not specified, the specified Kind must be in the core API group. - For any other third-party types, APIGroup is required. - type: string - kind: - description: Kind is the type of resource being - referenced - type: string - name: - description: Name is the name of resource being - referenced - type: string - namespace: - description: |- - Namespace is the namespace of resource being referenced - Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. - type: string - required: - - kind - - name - type: object - resources: - description: |- - resources represents the minimum resources the volume should have. - If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements - that are lower than previous value but must still be higher than capacity recorded in the - status field of the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources - properties: - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - selector: - description: selector is a label query over volumes - to consider for binding. - properties: - matchExpressions: - description: matchExpressions is a list of label - selector requirements. The requirements are - ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: key is the label key that the - selector applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - storageClassName: - description: |- - storageClassName is the name of the StorageClass required by the claim. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 - type: string - volumeAttributesClassName: - description: |- - volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. - If specified, the CSI driver will create or update the volume with the attributes defined - in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, - it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass - will be applied to the claim but it's not allowed to reset this field to empty string once it is set. - If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass - will be set by the persistentvolume controller if it exists. - If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be - set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource - exists. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#volumeattributesclass - (Alpha) Using this field requires the VolumeAttributesClass feature gate to be enabled. - type: string - volumeMode: - description: |- - volumeMode defines what type of volume is required by the claim. - Value of Filesystem is implied when not included in claim spec. - type: string - volumeName: - description: volumeName is the binding reference to - the PersistentVolume backing this claim. - type: string - type: object - status: - description: |- - status represents the current information/status of a persistent volume claim. - Read-only. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims - properties: - accessModes: - description: |- - accessModes contains the actual access modes the volume backing the PVC has. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 - items: - type: string - type: array - allocatedResourceStatuses: - additionalProperties: - description: |- - When a controller receives persistentvolume claim update with ClaimResourceStatus for a resource - that it does not recognizes, then it should ignore that update and let other controllers - handle it. - type: string - description: "allocatedResourceStatuses stores status - of resource being resized for the given PVC.\nKey - names follow standard Kubernetes label syntax. Valid - values are either:\n\t* Un-prefixed keys:\n\t\t- - storage - the capacity of the volume.\n\t* Custom - resources must use implementation-defined prefixed - names such as \"example.com/my-custom-resource\"\nApart - from above values - keys that are unprefixed or - have kubernetes.io prefix are considered\nreserved - and hence may not be used.\n\n\nClaimResourceStatus - can be in any of following states:\n\t- ControllerResizeInProgress:\n\t\tState - set when resize controller starts resizing the volume - in control-plane.\n\t- ControllerResizeFailed:\n\t\tState - set when resize has failed in resize controller - with a terminal error.\n\t- NodeResizePending:\n\t\tState - set when resize controller has finished resizing - the volume but further resizing of\n\t\tvolume is - needed on the node.\n\t- NodeResizeInProgress:\n\t\tState - set when kubelet starts resizing the volume.\n\t- - NodeResizeFailed:\n\t\tState set when resizing has - failed in kubelet with a terminal error. Transient - errors don't set\n\t\tNodeResizeFailed.\nFor example: - if expanding a PVC for more capacity - this field - can be one of the following states:\n\t- pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeInProgress\"\n - pvc.status.allocatedResourceStatus['storage'] - = \"ControllerResizeFailed\"\n - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizePending\"\n - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeInProgress\"\n - pvc.status.allocatedResourceStatus['storage'] - = \"NodeResizeFailed\"\nWhen this field is not set, - it means that no resize operation is in progress - for the given PVC.\n\n\nA controller that receives - PVC update with previously unknown resourceName - or ClaimResourceStatus\nshould ignore the update - for the purpose it was designed. For example - a - controller that\nonly is responsible for resizing - capacity of the volume, should ignore PVC updates - that change other valid\nresources associated with - PVC.\n\n\nThis is an alpha field and requires enabling - RecoverVolumeExpansionFailure feature." - type: object - x-kubernetes-map-type: granular - allocatedResources: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: "allocatedResources tracks the resources - allocated to a PVC including its capacity.\nKey - names follow standard Kubernetes label syntax. Valid - values are either:\n\t* Un-prefixed keys:\n\t\t- - storage - the capacity of the volume.\n\t* Custom - resources must use implementation-defined prefixed - names such as \"example.com/my-custom-resource\"\nApart - from above values - keys that are unprefixed or - have kubernetes.io prefix are considered\nreserved - and hence may not be used.\n\n\nCapacity reported - here may be larger than the actual capacity when - a volume expansion operation\nis requested.\nFor - storage quota, the larger value from allocatedResources - and PVC.spec.resources is used.\nIf allocatedResources - is not set, PVC.spec.resources alone is used for - quota calculation.\nIf a volume expansion capacity - request is lowered, allocatedResources is only\nlowered - if there are no expansion operations in progress - and if the actual volume capacity\nis equal or lower - than the requested capacity.\n\n\nA controller that - receives PVC update with previously unknown resourceName\nshould - ignore the update for the purpose it was designed. - For example - a controller that\nonly is responsible - for resizing capacity of the volume, should ignore - PVC updates that change other valid\nresources associated - with PVC.\n\n\nThis is an alpha field and requires - enabling RecoverVolumeExpansionFailure feature." - type: object - capacity: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: capacity represents the actual resources - of the underlying volume. - type: object - conditions: - description: |- - conditions is the current Condition of persistent volume claim. If underlying persistent volume is being - resized then the Condition will be set to 'ResizeStarted'. - items: - description: PersistentVolumeClaimCondition contains - details about state of pvc - properties: - lastProbeTime: - description: lastProbeTime is the time we probed - the condition. - format: date-time - type: string - lastTransitionTime: - description: lastTransitionTime is the time - the condition transitioned from one status - to another. - format: date-time - type: string - message: - description: message is the human-readable message - indicating details about last transition. - type: string - reason: - description: |- - reason is a unique, this should be a short, machine understandable string that gives the reason - for condition's last transition. If it reports "ResizeStarted" that means the underlying - persistent volume is being resized. - type: string - status: - type: string - type: - description: PersistentVolumeClaimConditionType - is a valid value of PersistentVolumeClaimCondition.Type - type: string - required: - - status - - type - type: object - type: array - currentVolumeAttributesClassName: - description: |- - currentVolumeAttributesClassName is the current name of the VolumeAttributesClass the PVC is using. - When unset, there is no VolumeAttributeClass applied to this PersistentVolumeClaim - This is an alpha field and requires enabling VolumeAttributesClass feature. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. type: string - modifyVolumeStatus: + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + type: object + volumeClaimTemplates: + description: |- + Defines VolumeClaimTemplates to override. + Add new or override existing volume claim templates. + items: + properties: + name: + description: |- + Refers to the name of a volumeMount defined in either: + + + - `componentDefinition.spec.runtime.containers[*].volumeMounts` + - `clusterDefinition.spec.componentDefs[*].podSpec.containers[*].volumeMounts` (deprecated) + + + The value of `name` must match the `name` field of a volumeMount specified in the corresponding `volumeMounts` array. + type: string + spec: + description: |- + Defines the desired characteristics of a PersistentVolumeClaim that will be created for the volume + with the mount name specified in the `name` field. + + + When a Pod is created for this ClusterComponent, a new PVC will be created based on the specification + defined in the `spec` field. The PVC will be associated with the volume mount specified by the `name` field. + properties: + accessModes: description: |- - ModifyVolumeStatus represents the status object of ControllerModifyVolume operation. - When this is unset, there is no ModifyVolume operation being attempted. - This is an alpha field and requires enabling VolumeAttributesClass feature. + Contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1. + items: + type: string + type: array + x-kubernetes-preserve-unknown-fields: true + resources: + description: |- + Represents the minimum resources the volume should have. + If the RecoverVolumeExpansionFailure feature is enabled, users are allowed to specify resource requirements that + are lower than the previous value but must still be higher than the capacity recorded in the status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources. properties: - status: - description: "status is the status of the ControllerModifyVolume - operation. It can be in any of following states:\n - - Pending\n Pending indicates that the PersistentVolumeClaim - cannot be modified due to unmet requirements, - such as\n the specified VolumeAttributesClass - not existing.\n - InProgress\n InProgress - indicates that the volume is being modified.\n - - Infeasible\n Infeasible indicates that the - request has been rejected as invalid by the - CSI driver. To\n\t resolve the error, a valid - VolumeAttributesClass needs to be specified.\nNote: - New statuses can be added in the future. Consumers - should check for unknown statuses and fail appropriately." - type: string - targetVolumeAttributesClassName: - description: targetVolumeAttributesClassName is - the name of the VolumeAttributesClass the PVC - currently being reconciled - type: string - required: - - status + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object type: object - phase: - description: phase represents the current phase of - PersistentVolumeClaim. + x-kubernetes-preserve-unknown-fields: true + storageClassName: + description: |- + The name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1. + type: string + volumeMode: + description: Defines what type of volume is required + by the claim, either Block or Filesystem. type: string type: object + required: + - name type: object type: array volumeMounts: description: |- Defines VolumeMounts to override. - Add new or override existing volume mounts of the first container in the pod. + Add new or override existing volume mounts of the first container in the Pod. items: description: VolumeMount describes a mounting of a Volume within a container. @@ -4061,6 +3705,275 @@ spec: required: - command type: object + switchover: + description: Defines the procedure for a controlled transition + of a role to a new replica. + properties: + exec: + description: |- + Defines the command to run. + + + This field cannot be updated. + properties: + args: + description: Args represents the arguments that are passed + to the `command` for execution. + items: + type: string + type: array + command: + description: |- + Specifies the command to be executed inside the container. + The working directory for this command is the container's root directory('/'). + Commands are executed directly without a shell environment, meaning shell-specific syntax ('|', etc.) is not supported. + If the shell is required, it must be explicitly invoked in the command. + + + A successful execution is indicated by an exit status of 0; any non-zero status signifies a failure. + items: + type: string + type: array + container: + description: |- + Specifies the name of the container within the same pod whose resources will be shared with the action. + This allows the action to utilize the specified container's resources without executing within it. + + + The name must match one of the containers defined in `componentDefinition.spec.runtime`. + + + The resources that can be shared are included: + + + - volume mounts + + + This field cannot be updated. + type: string + env: + description: |- + Represents a list of environment variables that will be injected into the container. + These variables enable the container to adapt its behavior based on the environment it's running in. + + + This field cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults to + "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: |- + Specifies the container image to be used for running the Action. + + + When specified, a dedicated container will be created using this image to execute the Action. + All actions with same image will share the same container. + + + This field cannot be updated. + type: string + matchingKey: + description: |- + Used in conjunction with the `targetPodSelector` field to refine the selection of target pod(s) for Action execution. + The impact of this field depends on the `targetPodSelector` value: + + + - When `targetPodSelector` is set to `Any` or `All`, this field will be ignored. + - When `targetPodSelector` is set to `Role`, only those replicas whose role matches the `matchingKey` + will be selected for the Action. + + + This field cannot be updated. + type: string + targetPodSelector: + description: |- + Defines the criteria used to select the target Pod(s) for executing the Action. + This is useful when there is no default target replica identified. + It allows for precise control over which Pod(s) the Action should run in. + + + If not specified, the Action will be executed in the pod where the Action is triggered, such as the pod + to be removed or added; or a random pod if the Action is triggered at the component level, such as + post-provision or pre-terminate of the component. + + + This field cannot be updated. + enum: + - Any + - All + - Role + - Ordinal + type: string + type: object + preCondition: + description: |- + Specifies the state that the cluster must reach before the Action is executed. + Currently, this is only applicable to the `postProvision` action. + + + The conditions are as follows: + + + - `Immediately`: Executed right after the Component object is created. + The readiness of the Component and its resources is not guaranteed at this stage. + - `RuntimeReady`: The Action is triggered after the Component object has been created and all associated + runtime resources (e.g. Pods) are in a ready state. + - `ComponentReady`: The Action is triggered after the Component itself is in a ready state. + This process does not affect the readiness state of the Component or the Cluster. + - `ClusterReady`: The Action is executed after the Cluster is in a ready state. + This execution does not alter the Component or the Cluster's state of readiness. + + + This field cannot be updated. + type: string + retryPolicy: + description: |- + Defines the strategy to be taken when retrying the Action after a failure. + + + It specifies the conditions under which the Action should be retried and the limits to apply, + such as the maximum number of retries and backoff strategy. + + + This field cannot be updated. + properties: + maxRetries: + default: 0 + description: |- + Defines the maximum number of retry attempts that should be made for a given Action. + This value is set to 0 by default, indicating that no retries will be made. + type: integer + retryInterval: + default: 0 + description: |- + Indicates the duration of time to wait between each retry attempt. + This value is set to 0 by default, indicating that there will be no delay between retry attempts. + format: int64 + type: integer + type: object + timeoutSeconds: + default: 0 + description: |- + Specifies the maximum duration in seconds that the Action is allowed to run. + + + If the Action does not complete within this time frame, it will be terminated. + + + This field cannot be updated. + format: int32 + type: integer + type: object switchoverAction: description: |- Specifies the environment variables that can be used in all following Actions: diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index eebf2a02817..a89a2f86bd2 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -2187,7 +2187,7 @@ SidecarDefinitionStatus

Action

-(Appears on:ComponentLifecycleActions, Probe, ShardingLifecycleActions) +(Appears on:ComponentLifecycleActions, Probe, ShardingLifecycleActions, MembershipReconfiguration)

Action defines a customizable hook or procedure tailored for different database engines, @@ -7747,7 +7747,7 @@ ContainerVars

InstanceTemplate

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

InstanceTemplate allows customization of individual replica configurations in a Component.

@@ -7791,6 +7791,24 @@ The default value is 1. A value of 0 disables instance creation.

+ordinals
+ + +Ordinals + + + + +

Specifies the desired Ordinals of this InstanceTemplate. +The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate.

+

For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, +then the instance names generated under this InstanceTemplate would be +$(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and +$(cluster.name)-$(component.name)-$(template.name)-7

+ + + + annotations
map[string]string @@ -8324,6 +8342,46 @@ VarOption +

Ordinals +

+

+(Appears on:InstanceTemplate, InstanceSetSpec) +

+
+

Ordinals represents a combination of continuous segments and individual values.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+ranges
+ + +[]Range + + +
+
+discrete
+ +[]int32 + +
+

PasswordConfig

@@ -8698,6 +8756,45 @@ string +

Range +

+

+(Appears on:Ordinals) +

+
+

Range represents a range with a start and an end value. +It is used to define a continuous segment.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+start
+ +int32 + +
+
+end
+ +int32 + +
+

ReplicaRole

@@ -28738,7 +28835,7 @@ Defaults to 1 if unspecified.

defaultTemplateOrdinals
- + Ordinals @@ -28796,7 +28893,7 @@ Kubernetes core/v1.PodTemplateSpec instances
- + []InstanceTemplate @@ -29255,7 +29352,7 @@ Defaults to 1 if unspecified.

defaultTemplateOrdinals
- + Ordinals @@ -29313,7 +29410,7 @@ Kubernetes core/v1.PodTemplateSpec instances
- + []InstanceTemplate @@ -29748,201 +29845,6 @@ key is the pod name, value is the revision.

-

InstanceTemplate -

-

-(Appears on:InstanceSetSpec) -

-
-

InstanceTemplate allows customization of individual replica configurations within a Component, -without altering the base component template defined in ClusterComponentSpec. -It enables the application of distinct settings to specific instances (replicas), -providing flexibility while maintaining a common configuration baseline.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-name
- -string - -
-

Name specifies the unique name of the instance Pod created using this InstanceTemplate. -This name is constructed by concatenating the component’s name, the template’s name, and the instance’s ordinal -using the pattern: $(cluster.name)-$(component.name)-$(template.name)-$(ordinal). Ordinals start from 0. -The specified name overrides any default naming conventions or patterns.

-
-replicas
- -int32 - -
-(Optional) -

Specifies the number of instances (Pods) to create from this InstanceTemplate. -This field allows setting how many replicated instances of the component, -with the specific overrides in the InstanceTemplate, are created. -The default value is 1. A value of 0 disables instance creation.

-
-ordinals
- - -Ordinals - - -
-

Specifies the desired Ordinals of this InstanceTemplate. -The Ordinals used to specify the ordinal of the instance (pod) names to be generated under this InstanceTemplate.

-

For example, if Ordinals is {ranges: [{start: 0, end: 1}], discrete: [7]}, -then the instance names generated under this InstanceTemplate would be -$(cluster.name)-$(component.name)-$(template.name)-0、$(cluster.name)-$(component.name)-$(template.name)-1 and -$(cluster.name)-$(component.name)-$(template.name)-7

-
-annotations
- -map[string]string - -
-(Optional) -

Specifies a map of key-value pairs to be merged into the Pod’s existing annotations. -Existing keys will have their values overwritten, while new keys will be added to the annotations.

-
-labels
- -map[string]string - -
-(Optional) -

Specifies a map of key-value pairs that will be merged into the Pod’s existing labels. -Values for existing keys will be overwritten, and new keys will be added.

-
-image
- -string - -
-(Optional) -

Specifies an override for the first container’s image in the pod.

-
-schedulingPolicy
- - -SchedulingPolicy - - -
-(Optional) -

Specifies the scheduling policy for the Component.

-
-resources
- - -Kubernetes core/v1.ResourceRequirements - - -
-(Optional) -

Specifies an override for the resource requirements of the first container in the Pod. -This field allows for customizing resource allocation (CPU, memory, etc.) for the container.

-
-env
- - -[]Kubernetes core/v1.EnvVar - - -
-(Optional) -

Defines Env to override. -Add new or override existing envs.

-
-volumes
- - -[]Kubernetes core/v1.Volume - - -
-(Optional) -

Defines Volumes to override. -Add new or override existing volumes.

-
-volumeMounts
- - -[]Kubernetes core/v1.VolumeMount - - -
-(Optional) -

Defines VolumeMounts to override. -Add new or override existing volume mounts of the first container in the pod.

-
-volumeClaimTemplates
- - -[]Kubernetes core/v1.PersistentVolumeClaim - - -
-(Optional) -

Defines VolumeClaimTemplates to override. -Add new or override existing volume claim templates.

-

InstanceTemplateStatus

@@ -30194,44 +30096,18 @@ Action If the Image is not configured, the Image from the previous non-nil action will be used.

- - -

Ordinals -

-

-(Appears on:InstanceSetSpec, InstanceTemplate) -

-
-

Ordinals represents a combination of continuous segments and individual values.

-
- - - - - - - - - - - - @@ -30260,45 +30136,6 @@ Any attempt to modify other fields will be rejected.

FieldDescription
-ranges
+switchover
- -[]Range + +Action
-
-discrete
- -[]int32 - -
+(Optional) +

Defines the procedure for a controlled transition of a role to a new replica.

-

Range -

-

-(Appears on:Ordinals) -

-
-

Range represents a range with a start and an end value. -It is used to define a continuous segment.

-
- - - - - - - - - - - - - - - - - -
FieldDescription
-start
- -int32 - -
-
-end
- -int32 - -
-

ReplicaRole

@@ -30382,117 +30219,6 @@ bool -

SchedulingPolicy -

-

-(Appears on:InstanceTemplate) -

-
-

SchedulingPolicy the scheduling policy. -Deprecated: Unify with apps/v1alpha1.SchedulingPolicy

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescription
-schedulerName
- -string - -
-(Optional) -

If specified, the Pod will be dispatched by specified scheduler. -If not specified, the Pod will be dispatched by default scheduler.

-
-nodeSelector
- -map[string]string - -
-(Optional) -

NodeSelector is a selector which must be true for the Pod to fit on a node. -Selector which must match a node’s labels for the Pod to be scheduled on that node. -More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

-
-nodeName
- -string - -
-(Optional) -

NodeName is a request to schedule this Pod onto a specific node. If it is non-empty, -the scheduler simply schedules this Pod onto that node, assuming that it fits resource -requirements.

-
-affinity
- - -Kubernetes core/v1.Affinity - - -
-(Optional) -

Specifies a group of affinity scheduling rules of the Cluster, including NodeAffinity, PodAffinity, and PodAntiAffinity.

-
-tolerations
- - -[]Kubernetes core/v1.Toleration - - -
-(Optional) -

Allows Pods to be scheduled onto nodes with matching taints. -Each toleration in the array allows the Pod to tolerate node taints based on -specified key, value, effect, and operator.

-
    -
  • The key, value, and effect identify the taint that the toleration matches.
  • -
  • The operator determines how the toleration matches the taint.
  • -
-

Pods with matching tolerations are allowed to be scheduled on tainted nodes, typically reserved for specific purposes.

-
-topologySpreadConstraints
- - -[]Kubernetes core/v1.TopologySpreadConstraint - - -
-(Optional) -

TopologySpreadConstraints describes how a group of Pods ought to spread across topology -domains. Scheduler will schedule Pods in a way which abides by the constraints. -All topologySpreadConstraints are ANDed.

-

workloads.kubeblocks.io/v1alpha1

diff --git a/pkg/controller/builder/builder_instance_set.go b/pkg/controller/builder/builder_instance_set.go index 79ea71f824e..1106d82f044 100644 --- a/pkg/controller/builder/builder_instance_set.go +++ b/pkg/controller/builder/builder_instance_set.go @@ -25,6 +25,7 @@ import ( 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" ) @@ -86,6 +87,16 @@ func (builder *InstanceSetBuilder) SetRoles(roles []workloads.ReplicaRole) *Inst return builder } +func (builder *InstanceSetBuilder) SetLifecycleActions(lifecycleActions *kbappsv1.ComponentLifecycleActions) *InstanceSetBuilder { + if lifecycleActions != nil && lifecycleActions.Switchover != nil { + if builder.get().Spec.MembershipReconfiguration == nil { + builder.get().Spec.MembershipReconfiguration = &workloads.MembershipReconfiguration{} + } + builder.get().Spec.MembershipReconfiguration.Switchover = lifecycleActions.Switchover + } + return builder +} + func (builder *InstanceSetBuilder) SetTemplate(template corev1.PodTemplateSpec) *InstanceSetBuilder { builder.get().Spec.Template = template return builder diff --git a/pkg/controller/component/its_convertor.go b/pkg/controller/component/its_convertor.go index af8af9b5d16..afe6a8ce55a 100644 --- a/pkg/controller/component/its_convertor.go +++ b/pkg/controller/component/its_convertor.go @@ -24,13 +24,11 @@ import ( 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" kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" - viper "github.com/apecloud/kubeblocks/pkg/viperx" ) // BuildWorkloadFrom builds a new Component object based on SynthesizedComponent. @@ -186,58 +184,19 @@ func AppsInstanceToWorkloadInstance(instance *kbappsv1.InstanceTemplate) *worklo if instance == nil { return nil } - var schedulingPolicy *workloads.SchedulingPolicy - if instance.SchedulingPolicy != nil { - schedulingPolicy = &workloads.SchedulingPolicy{ - SchedulerName: instance.SchedulingPolicy.SchedulerName, - NodeSelector: instance.SchedulingPolicy.NodeSelector, - NodeName: instance.SchedulingPolicy.NodeName, - Affinity: instance.SchedulingPolicy.Affinity, - Tolerations: instance.SchedulingPolicy.Tolerations, - TopologySpreadConstraints: instance.SchedulingPolicy.TopologySpreadConstraints, - } - } - return &workloads.InstanceTemplate{ Name: instance.Name, Replicas: instance.Replicas, Annotations: instance.Annotations, Labels: instance.Labels, Image: instance.Image, - SchedulingPolicy: schedulingPolicy, + SchedulingPolicy: instance.SchedulingPolicy, Resources: instance.Resources, Env: instance.Env, Volumes: instance.Volumes, VolumeMounts: instance.VolumeMounts, - VolumeClaimTemplates: toPersistentVolumeClaims(instance.VolumeClaimTemplates), - } -} - -func toPersistentVolumeClaims(vcts []kbappsv1.ClusterComponentVolumeClaimTemplate) []corev1.PersistentVolumeClaim { - storageClassName := func(spec kbappsv1.PersistentVolumeClaimSpec, defaultStorageClass string) *string { - if spec.StorageClassName != nil && *spec.StorageClassName != "" { - return spec.StorageClassName - } - if defaultStorageClass != "" { - return &defaultStorageClass - } - return nil - } - var pvcs []corev1.PersistentVolumeClaim - for _, v := range vcts { - pvcs = append(pvcs, corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: v.Name, - }, - Spec: corev1.PersistentVolumeClaimSpec{ - AccessModes: v.Spec.AccessModes, - Resources: v.Spec.Resources, - StorageClassName: storageClassName(v.Spec, viper.GetString(constant.CfgKeyDefaultStorageClass)), - VolumeMode: v.Spec.VolumeMode, - }, - }) + VolumeClaimTemplates: instance.VolumeClaimTemplates, } - return pvcs } // parseITSConvertorArgs parses the args of ITS convertor. diff --git a/pkg/controller/component/replicas.go b/pkg/controller/component/replicas.go index 8dfcc89c652..041038deb98 100644 --- a/pkg/controller/component/replicas.go +++ b/pkg/controller/component/replicas.go @@ -221,7 +221,7 @@ func NewReplicaTask(compName, uid string, source *corev1.Pod, replicas []string) NotifyAtFinish: true, ReportPeriodSeconds: defaultNewReplicaTaskReportPeriodSeconds, NewReplica: &proto.NewReplicaTask{ - Remote: PodFQDN(source.Namespace, compName, source.Name), + Remote: intctrlutil.PodFQDN(source.Namespace, compName, source.Name), Port: port, Replicas: strings.Join(replicas, ","), }, diff --git a/pkg/controller/component/service_reference_test.go b/pkg/controller/component/service_reference_test.go index b05cf84304d..088a8001f26 100644 --- a/pkg/controller/component/service_reference_test.go +++ b/pkg/controller/component/service_reference_test.go @@ -31,9 +31,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/instanceset" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/generics" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" ) @@ -434,7 +434,7 @@ var _ = Describe("service references", func() { err := buildServiceReferencesWithoutResolve(testCtx.Ctx, reader, synthesizedComp, compDef, comp) Expect(err).Should(Succeed()) - svcFQDN := serviceFQDN("external", reader.objs[0].GetName()) + svcFQDN := intctrlutil.ServiceFQDN("external", reader.objs[0].GetName()) Expect(synthesizedComp.ServiceReferences).Should(HaveKey(serviceRefDeclaration.Name)) serviceDescriptor := synthesizedComp.ServiceReferences[serviceRefDeclaration.Name] @@ -564,10 +564,10 @@ var _ = Describe("service references", func() { } etcdComp := reader.objs[0].(*appsv1.Component) - podNames, _ := instanceset.GenerateAllInstanceNames(etcdComp.Name, etcdComp.Spec.Replicas, nil, nil, workloads.Ordinals{}) + podNames, _ := instanceset.GenerateAllInstanceNames(etcdComp.Name, etcdComp.Spec.Replicas, nil, nil, appsv1.Ordinals{}) expectedPodFQDNs := strings.Join([]string{ - PodFQDN(namespace, etcdComp.Name, podNames[0]), - PodFQDN(namespace, etcdComp.Name, podNames[1]), + intctrlutil.PodFQDN(namespace, etcdComp.Name, podNames[0]), + intctrlutil.PodFQDN(namespace, etcdComp.Name, podNames[1]), }, ",") err := buildServiceReferencesWithoutResolve(testCtx.Ctx, reader, synthesizedComp, compDef, comp) @@ -630,7 +630,7 @@ var _ = Describe("service references", func() { } compName := constant.GenerateClusterComponentName(etcdCluster, etcdComponent) - expectedPodFQDNs := PodFQDN(namespace, compName, reader.objs[1].GetName()) + expectedPodFQDNs := intctrlutil.PodFQDN(namespace, compName, reader.objs[1].GetName()) err := buildServiceReferencesWithoutResolve(testCtx.Ctx, reader, synthesizedComp, compDef, comp) Expect(err).Should(Succeed()) diff --git a/pkg/controller/component/vars.go b/pkg/controller/component/vars.go index 80fa3fca267..5d8504b4b3a 100644 --- a/pkg/controller/component/vars.go +++ b/pkg/controller/component/vars.go @@ -37,10 +37,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/common" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/instanceset" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" "github.com/apecloud/kubeblocks/pkg/generics" ) @@ -530,7 +530,7 @@ func composeHostValueFromServices(obj any, fqdn bool) string { if !fqdn { return svc.Name } - return serviceFQDN(svc.Namespace, svc.Name) + return intctrlutil.ServiceFQDN(svc.Namespace, svc.Name) } svcNames := make([]string, 0) @@ -1153,13 +1153,13 @@ func componentVarPodsGetter(ctx context.Context, cli client.Reader, for i := range comp.Spec.Instances { templates = append(templates, &comp.Spec.Instances[i]) } - names, err := instanceset.GenerateAllInstanceNames(comp.Name, comp.Spec.Replicas, templates, comp.Spec.OfflineInstances, workloads.Ordinals{}) + names, err := instanceset.GenerateAllInstanceNames(comp.Name, comp.Spec.Replicas, templates, comp.Spec.OfflineInstances, appsv1.Ordinals{}) if err != nil { return "", err } if fqdn { for i := range names { - names[i] = PodFQDN(namespace, comp.Name, names[i]) + names[i] = intctrlutil.PodFQDN(namespace, comp.Name, names[i]) } } return strings.Join(names, ","), nil @@ -1189,7 +1189,7 @@ func componentVarPodsWithRoleGetter(ctx context.Context, cli client.Reader, if fqdn { fullCompName := constant.GenerateClusterComponentName(clusterName, compName) for i := range names { - names[i] = PodFQDN(namespace, fullCompName, names[i]) + names[i] = intctrlutil.PodFQDN(namespace, fullCompName, names[i]) } } return strings.Join(names, ","), nil diff --git a/pkg/controller/component/vars_test.go b/pkg/controller/component/vars_test.go index 8dde6e8c1eb..fe7cd91d495 100644 --- a/pkg/controller/component/vars_test.go +++ b/pkg/controller/component/vars_test.go @@ -34,6 +34,7 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/constant" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) var _ = Describe("vars", func() { @@ -1781,12 +1782,13 @@ var _ = Describe("vars", func() { fqdnList := func(fn ...func(string) string) []string { l := make([]string, 0) for _, i := range mockInstanceList { - l = append(l, PodFQDN(synthesizedComp.Namespace, synthesizedComp.FullCompName, i)) + l = append(l, intctrlutil.PodFQDN(synthesizedComp.Namespace, synthesizedComp.FullCompName, i)) } return l } checkEnvVarWithValue(envVars, "podFQDNs", strings.Join(fqdnList(), ",")) - checkEnvVarWithValue(envVars, "podFQDNs4Leader", PodFQDN(synthesizedComp.Namespace, synthesizedComp.FullCompName, podName("leader"))) + checkEnvVarWithValue(envVars, "podFQDNs4Leader", + intctrlutil.PodFQDN(synthesizedComp.Namespace, synthesizedComp.FullCompName, podName("leader"))) }) }) diff --git a/pkg/controller/component/workload_utils.go b/pkg/controller/component/workload_utils.go index 9d3d16d269c..080abecd368 100644 --- a/pkg/controller/component/workload_utils.go +++ b/pkg/controller/component/workload_utils.go @@ -35,7 +35,6 @@ import ( "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/instanceset" "github.com/apecloud/kubeblocks/pkg/generics" - viper "github.com/apecloud/kubeblocks/pkg/viperx" ) func ListOwnedWorkloads(ctx context.Context, cli client.Reader, namespace, clusterName, compName string) ([]*workloads.InstanceSet, error) { @@ -139,7 +138,7 @@ func GenerateAllPodNames( Replicas: instances[i].Replicas, }) } - return instanceset.GenerateAllInstanceNames(fullCompName, compReplicas, templates, offlineInstances, workloads.Ordinals{}) + return instanceset.GenerateAllInstanceNames(fullCompName, compReplicas, templates, offlineInstances, appsv1.Ordinals{}) } // GenerateAllPodNamesToSet generate all pod names for a component @@ -180,15 +179,3 @@ func GetTemplateNameAndOrdinal(workloadName, podName string) (string, int32, err } return templateName, int32(index), nil } - -func PodFQDN(namespace, compName, podName string) string { - return fmt.Sprintf("%s.%s-headless.%s.svc.%s", podName, compName, namespace, clusterDomain()) -} - -func serviceFQDN(namespace, serviceName string) string { - return fmt.Sprintf("%s.%s.svc.%s", serviceName, namespace, clusterDomain()) -} - -func clusterDomain() string { - return viper.GetString(constant.KubernetesClusterDomainEnv) -} diff --git a/pkg/controller/factory/builder.go b/pkg/controller/factory/builder.go index 428e4d9e41f..981ea564212 100644 --- a/pkg/controller/factory/builder.go +++ b/pkg/controller/factory/builder.go @@ -77,7 +77,8 @@ func BuildInstanceSet(synthesizedComp *component.SynthesizedComponent, component SetTemplate(template). AddMatchLabelsInMap(constant.GetCompLabels(clusterName, compName)). SetReplicas(synthesizedComp.Replicas). - SetMinReadySeconds(synthesizedComp.MinReadySeconds) + SetMinReadySeconds(synthesizedComp.MinReadySeconds). + SetLifecycleActions(synthesizedComp.LifecycleActions) var vcts []corev1.PersistentVolumeClaim for _, vct := range synthesizedComp.VolumeClaimTemplates { diff --git a/pkg/controller/instanceset/instance_util.go b/pkg/controller/instanceset/instance_util.go index 750da4ab900..11a5432e33a 100644 --- a/pkg/controller/instanceset/instance_util.go +++ b/pkg/controller/instanceset/instance_util.go @@ -39,6 +39,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/builder" @@ -50,7 +51,7 @@ import ( type InstanceTemplate interface { GetName() string GetReplicas() int32 - GetOrdinals() workloads.Ordinals + GetOrdinals() kbappsv1.Ordinals } type instanceTemplateExt struct { @@ -339,7 +340,7 @@ func buildInstanceName2TemplateMap(itsExt *instanceSetExt) (map[string]*instance return allNameTemplateMap, nil } -func GenerateAllInstanceNames(parentName string, replicas int32, templates []InstanceTemplate, offlineInstances []string, defaultTemplateOrdinals workloads.Ordinals) ([]string, error) { +func GenerateAllInstanceNames(parentName string, replicas int32, templates []InstanceTemplate, offlineInstances []string, defaultTemplateOrdinals kbappsv1.Ordinals) ([]string, error) { totalReplicas := int32(0) instanceNameList := make([]string, 0) for _, template := range templates { @@ -439,7 +440,7 @@ func GetOrdinalListByTemplateName(its *workloads.InstanceSet, templateName strin return ConvertOrdinalsToSortedList(ordinals) } -func GetOrdinalsByTemplateName(its *workloads.InstanceSet, templateName string) (workloads.Ordinals, error) { +func GetOrdinalsByTemplateName(its *workloads.InstanceSet, templateName string) (kbappsv1.Ordinals, error) { if templateName == "" { return its.Spec.DefaultTemplateOrdinals, nil } @@ -448,10 +449,10 @@ func GetOrdinalsByTemplateName(its *workloads.InstanceSet, templateName string) return template.Ordinals, nil } } - return workloads.Ordinals{}, fmt.Errorf("template %s not found", templateName) + return kbappsv1.Ordinals{}, fmt.Errorf("template %s not found", templateName) } -func ConvertOrdinalsToSortedList(ordinals workloads.Ordinals) ([]int32, error) { +func ConvertOrdinalsToSortedList(ordinals kbappsv1.Ordinals) ([]int32, error) { ordinalList := sets.New(ordinals.Discrete...) for _, item := range ordinals.Ranges { start := item.Start @@ -956,7 +957,8 @@ func buildInstanceTemplateExt(template workloads.InstanceTemplate, templateExt * return vm.Name == item.Name } }) - intctrlutil.MergeList(&template.VolumeClaimTemplates, &templateExt.VolumeClaimTemplates, + vcts := intctrlutil.ToCoreV1PVCs(template.VolumeClaimTemplates) + intctrlutil.MergeList(&vcts, &templateExt.VolumeClaimTemplates, func(item corev1.PersistentVolumeClaim) func(corev1.PersistentVolumeClaim) bool { return func(claim corev1.PersistentVolumeClaim) bool { return claim.Name == item.Name diff --git a/pkg/controller/instanceset/instance_util_test.go b/pkg/controller/instanceset/instance_util_test.go index e7fea0a6574..7b92a354680 100644 --- a/pkg/controller/instanceset/instance_util_test.go +++ b/pkg/controller/instanceset/instance_util_test.go @@ -35,6 +35,7 @@ import ( "k8s.io/utils/pointer" "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/constant" "github.com/apecloud/kubeblocks/pkg/controller/builder" @@ -528,7 +529,7 @@ var _ = Describe("instance util test", func() { var templates []InstanceTemplate templates = append(templates, templatesFoo, templateBar) offlineInstances := []string{"foo-bar-1", "foo-0"} - instanceNameList, err := GenerateAllInstanceNames(parentName, 5, templates, offlineInstances, workloads.Ordinals{}) + instanceNameList, err := GenerateAllInstanceNames(parentName, 5, templates, offlineInstances, kbappsv1.Ordinals{}) Expect(err).Should(BeNil()) podNamesExpected := []string{"foo-1", "foo-2", "foo-bar-0", "foo-bar-2", "foo-foo-0"} @@ -536,8 +537,8 @@ var _ = Describe("instance util test", func() { }) It("with Ordinals, without offlineInstances", func() { parentName := "foo" - defaultTemplateOrdinals := workloads.Ordinals{ - Ranges: []workloads.Range{ + defaultTemplateOrdinals := kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 1, End: 2, @@ -547,15 +548,15 @@ var _ = Describe("instance util test", func() { templatesFoo := &workloads.InstanceTemplate{ Name: "foo", Replicas: pointer.Int32(1), - Ordinals: workloads.Ordinals{ + Ordinals: kbappsv1.Ordinals{ Discrete: []int32{0}, }, } templateBar := &workloads.InstanceTemplate{ Name: "bar", Replicas: pointer.Int32(3), - Ordinals: workloads.Ordinals{ - Ranges: []workloads.Range{ + Ordinals: kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 2, End: 3, @@ -574,8 +575,8 @@ var _ = Describe("instance util test", func() { }) It("with templatesOrdinals, with offlineInstances", func() { parentName := "foo" - defaultTemplateOrdinals := workloads.Ordinals{ - Ranges: []workloads.Range{ + defaultTemplateOrdinals := kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 1, End: 2, @@ -585,15 +586,15 @@ var _ = Describe("instance util test", func() { templatesFoo := &workloads.InstanceTemplate{ Name: "foo", Replicas: pointer.Int32(1), - Ordinals: workloads.Ordinals{ + Ordinals: kbappsv1.Ordinals{ Discrete: []int32{0}, }, } templateBar := &workloads.InstanceTemplate{ Name: "bar", Replicas: pointer.Int32(2), - Ordinals: workloads.Ordinals{ - Ranges: []workloads.Range{ + Ordinals: kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 2, End: 3, @@ -613,8 +614,8 @@ var _ = Describe("instance util test", func() { }) It("with templatesOrdinals, with offlineInstances, replicas error", func() { parentName := "foo" - defaultTemplateOrdinals := workloads.Ordinals{ - Ranges: []workloads.Range{ + defaultTemplateOrdinals := kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 1, End: 2, @@ -624,15 +625,15 @@ var _ = Describe("instance util test", func() { templatesFoo := &workloads.InstanceTemplate{ Name: "foo", Replicas: pointer.Int32(1), - Ordinals: workloads.Ordinals{ + Ordinals: kbappsv1.Ordinals{ Discrete: []int32{0}, }, } templateBar := &workloads.InstanceTemplate{ Name: "bar", Replicas: pointer.Int32(3), - Ordinals: workloads.Ordinals{ - Ranges: []workloads.Range{ + Ordinals: kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 2, End: 3, @@ -657,8 +658,8 @@ var _ = Describe("instance util test", func() { It("should work well", func() { its := &workloads.InstanceSet{ Spec: workloads.InstanceSetSpec{ - DefaultTemplateOrdinals: workloads.Ordinals{ - Ranges: []workloads.Range{ + DefaultTemplateOrdinals: kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 1, End: 2, @@ -668,14 +669,14 @@ var _ = Describe("instance util test", func() { Instances: []workloads.InstanceTemplate{ { Name: "foo", - Ordinals: workloads.Ordinals{ + Ordinals: kbappsv1.Ordinals{ Discrete: []int32{0}, }, }, { Name: "bar", - Ordinals: workloads.Ordinals{ - Ranges: []workloads.Range{ + Ordinals: kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 2, End: 3, @@ -718,8 +719,8 @@ var _ = Describe("instance util test", func() { It("should work well", func() { its := &workloads.InstanceSet{ Spec: workloads.InstanceSetSpec{ - DefaultTemplateOrdinals: workloads.Ordinals{ - Ranges: []workloads.Range{ + DefaultTemplateOrdinals: kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 1, End: 2, @@ -729,14 +730,14 @@ var _ = Describe("instance util test", func() { Instances: []workloads.InstanceTemplate{ { Name: "foo", - Ordinals: workloads.Ordinals{ + Ordinals: kbappsv1.Ordinals{ Discrete: []int32{0}, }, }, { Name: "bar", - Ordinals: workloads.Ordinals{ - Ranges: []workloads.Range{ + Ordinals: kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 2, End: 3, @@ -755,8 +756,8 @@ var _ = Describe("instance util test", func() { ordinalsDefault, err := GetOrdinalsByTemplateName(its, templateNameDefault) Expect(err).Should(BeNil()) - ordinalsDefaultExpected := workloads.Ordinals{ - Ranges: []workloads.Range{ + ordinalsDefaultExpected := kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 1, End: 2, @@ -767,15 +768,15 @@ var _ = Describe("instance util test", func() { ordinalsFoo, err := GetOrdinalsByTemplateName(its, templateNameFoo) Expect(err).Should(BeNil()) - ordinalsFooExpected := workloads.Ordinals{ + ordinalsFooExpected := kbappsv1.Ordinals{ Discrete: []int32{0}, } Expect(ordinalsFoo).Should(Equal(ordinalsFooExpected)) ordinalsBar, err := GetOrdinalsByTemplateName(its, templateNameBar) Expect(err).Should(BeNil()) - ordinalsBarExpected := workloads.Ordinals{ - Ranges: []workloads.Range{ + ordinalsBarExpected := kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 2, End: 3, @@ -786,7 +787,7 @@ var _ = Describe("instance util test", func() { Expect(ordinalsBar).Should(Equal(ordinalsBarExpected)) ordinalsNotFound, err := GetOrdinalsByTemplateName(its, templateNameNotFound) - Expect(ordinalsNotFound).Should(Equal(workloads.Ordinals{})) + Expect(ordinalsNotFound).Should(Equal(kbappsv1.Ordinals{})) errExpected := fmt.Errorf("template %s not found", templateNameNotFound) Expect(err).Should(Equal(errExpected)) }) @@ -794,8 +795,8 @@ var _ = Describe("instance util test", func() { Context("ConvertOrdinalsToSortedList", func() { It("should work well", func() { - ordinals := workloads.Ordinals{ - Ranges: []workloads.Range{ + ordinals := kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 2, End: 4, @@ -808,8 +809,8 @@ var _ = Describe("instance util test", func() { sets.New(ordinalList...).Equal(sets.New[int32](0, 2, 3, 4, 6)) }) It("rightNumber must >= leftNumber", func() { - ordinals := workloads.Ordinals{ - Ranges: []workloads.Range{ + ordinals := kbappsv1.Ordinals{ + Ranges: []kbappsv1.Range{ { Start: 4, End: 2, diff --git a/pkg/controller/instanceset/reconciler_update.go b/pkg/controller/instanceset/reconciler_update.go index 7e8c453063e..b63d82dae8e 100644 --- a/pkg/controller/instanceset/reconciler_update.go +++ b/pkg/controller/instanceset/reconciler_update.go @@ -20,6 +20,7 @@ along with this program. If not, see . package instanceset import ( + "errors" "fmt" "time" @@ -30,8 +31,10 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/sets" + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/controller/kubebuilderx" + "github.com/apecloud/kubeblocks/pkg/controller/lifecycle" "github.com/apecloud/kubeblocks/pkg/controller/model" ) @@ -95,6 +98,7 @@ 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? return kubebuilderx.Continue, nil } @@ -176,12 +180,18 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder return kubebuilderx.Continue, err } newPod := copyAndMerge(pod, newInstance.pod) + if err = r.switchover(tree, its, newPod.(*corev1.Pod)); err != nil { + return kubebuilderx.Continue, err + } if err = tree.Update(newPod); err != nil { return kubebuilderx.Continue, err } updatingPods++ } else if updatePolicy == RecreatePolicy { if !isTerminating(pod) { + if err = r.switchover(tree, its, pod); err != nil { + return kubebuilderx.Continue, err + } if err = tree.Delete(pod); err != nil { return kubebuilderx.Continue, err } @@ -199,6 +209,30 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder return kubebuilderx.Continue, nil } +func (r *updateReconciler) switchover(tree *kubebuilderx.ObjectTree, its *workloads.InstanceSet, pod *corev1.Pod) error { + if its.Spec.MembershipReconfiguration == nil || its.Spec.MembershipReconfiguration.Switchover == nil { + return nil + } + + lifecycleActions := &kbappsv1.ComponentLifecycleActions{ + Switchover: its.Spec.MembershipReconfiguration.Switchover, + } + // TODO: template vars + lfa, err := lifecycle.New(its.Namespace, its.Name, its.Name, lifecycleActions, nil, pod) + if err != nil { + return err + } + err = lfa.Switchover(tree.Context, nil, nil, "") + if err != nil { + if errors.Is(err, lifecycle.ErrActionNotDefined) { + return nil + } + return err + } + tree.Logger.Info("successfully call switchover action for pod", "pod", pod.Name) + return nil +} + func buildBlockedCondition(its *workloads.InstanceSet, message string) *metav1.Condition { return &metav1.Condition{ Type: string(workloads.InstanceUpdateRestricted), diff --git a/pkg/controller/instanceset/tree_loader.go b/pkg/controller/instanceset/tree_loader.go index ea3de56d94c..78a25b30ca3 100644 --- a/pkg/controller/instanceset/tree_loader.go +++ b/pkg/controller/instanceset/tree_loader.go @@ -50,6 +50,7 @@ func (r *treeLoader) Load(ctx context.Context, reader client.Reader, req ctrl.Re return nil, err } + tree.Context = ctx tree.EventRecorder = recorder tree.Logger = logger tree.SetFinalizer(finalizer) diff --git a/pkg/controller/instanceset/utils.go b/pkg/controller/instanceset/utils.go index 78b8a2740c3..df7110ffa1e 100644 --- a/pkg/controller/instanceset/utils.go +++ b/pkg/controller/instanceset/utils.go @@ -90,67 +90,6 @@ func getRoleName(pod *corev1.Pod) string { return strings.ToLower(pod.Labels[constant.RoleLabelKey]) } -// IsInstancesReady gives Instance level 'ready' state when all instances are available -func IsInstancesReady(its *workloads.InstanceSet) bool { - if its == nil { - return false - } - // check whether the cluster has been initialized - if its.Status.ReadyInitReplicas != its.Status.InitReplicas { - return false - } - // check whether latest spec has been sent to the underlying workload - if its.Status.ObservedGeneration != its.Generation { - return false - } - // check whether the underlying workload is ready - if its.Spec.Replicas == nil { - return false - } - replicas := *its.Spec.Replicas - if its.Status.Replicas != replicas || - its.Status.ReadyReplicas != replicas || - its.Status.UpdatedReplicas != replicas { - return false - } - // check availableReplicas only if minReadySeconds is set - if its.Spec.MinReadySeconds > 0 && its.Status.AvailableReplicas != replicas { - return false - } - - return true -} - -// IsInstanceSetReady gives InstanceSet level 'ready' state: -// 1. all instances are available -// 2. and all members have role set (if they are role-ful) -func IsInstanceSetReady(its *workloads.InstanceSet) bool { - instancesReady := IsInstancesReady(its) - if !instancesReady { - return false - } - - // check whether role probe has done - if len(its.Spec.Roles) == 0 { - return true - } - membersStatus := its.Status.MembersStatus - if len(membersStatus) != int(*its.Spec.Replicas) { - return false - } - if its.Status.ReadyWithoutPrimary { - return true - } - hasLeader := false - for _, status := range membersStatus { - if status.ReplicaRole != nil && status.ReplicaRole.IsLeader { - hasLeader = true - break - } - } - return hasLeader -} - // AddAnnotationScope will add AnnotationScope defined by 'scope' to all keys in map 'annotations'. func AddAnnotationScope(scope AnnotationScope, annotations map[string]string) map[string]string { if annotations == nil { diff --git a/pkg/controller/instanceset/utils_test.go b/pkg/controller/instanceset/utils_test.go index bde7f772e09..b21504d8552 100644 --- a/pkg/controller/instanceset/utils_test.go +++ b/pkg/controller/instanceset/utils_test.go @@ -171,7 +171,7 @@ var _ = Describe("utils test", func() { It("should work well", func() { By("set its to nil") its = nil - Expect(IsInstanceSetReady(its)).Should(BeFalse()) + Expect(its.IsInstanceSetReady()).Should(BeFalse()) By("set its to not initialized") replicas := int32(3) @@ -182,17 +182,17 @@ var _ = Describe("utils test", func() { its.Status = workloads.InstanceSetStatus{ InitReplicas: replicas, } - Expect(IsInstanceSetReady(its)).Should(BeFalse()) + Expect(its.IsInstanceSetReady()).Should(BeFalse()) By("set its.status.observedGeneration to not equal generation") its.Status.ReadyInitReplicas = replicas its.Generation = 1 - Expect(IsInstanceSetReady(its)).Should(BeFalse()) + Expect(its.IsInstanceSetReady()).Should(BeFalse()) By("set its.status.replicas to not as expected") its.Status.ObservedGeneration = its.Generation its.Status.Replicas = replicas - 1 - Expect(IsInstanceSetReady(its)).Should(BeFalse()) + Expect(its.IsInstanceSetReady()).Should(BeFalse()) By("set spec.minReadySeconds to not zero") its.Status.Replicas = replicas @@ -200,16 +200,16 @@ var _ = Describe("utils test", func() { its.Status.UpdatedReplicas = replicas its.Status.AvailableReplicas = replicas - 1 its.Spec.MinReadySeconds = int32(5) - Expect(IsInstanceSetReady(its)).Should(BeFalse()) + Expect(its.IsInstanceSetReady()).Should(BeFalse()) By("set its to role-less") its.Status.AvailableReplicas = replicas its.Spec.Roles = nil - Expect(IsInstanceSetReady(its)).Should(BeTrue()) + Expect(its.IsInstanceSetReady()).Should(BeTrue()) By("set its to role-ful") its.Spec.Roles = roles - Expect(IsInstanceSetReady(its)).Should(BeFalse()) + Expect(its.IsInstanceSetReady()).Should(BeFalse()) By("set membersStatus to ready") its.Status.MembersStatus = []workloads.MemberStatus{ @@ -226,7 +226,7 @@ var _ = Describe("utils test", func() { ReplicaRole: &roles[2], }, } - Expect(IsInstanceSetReady(its)).Should(BeTrue()) + Expect(its.IsInstanceSetReady()).Should(BeTrue()) }) }) diff --git a/pkg/controller/kubebuilderx/reconciler.go b/pkg/controller/kubebuilderx/reconciler.go index 1926d1f146d..c939338b1ae 100644 --- a/pkg/controller/kubebuilderx/reconciler.go +++ b/pkg/controller/kubebuilderx/reconciler.go @@ -35,6 +35,7 @@ import ( type ObjectTree struct { // TODO(free6om): should find a better place to hold these two params? + context.Context record.EventRecorder logr.Logger @@ -113,6 +114,7 @@ func (t *ObjectTree) DeepCopy() (*ObjectTree, error) { } out.children = children out.finalizer = t.finalizer + out.Context = t.Context out.EventRecorder = t.EventRecorder out.Logger = t.Logger return out, nil diff --git a/pkg/controller/component/lifecycle/errors.go b/pkg/controller/lifecycle/errors.go similarity index 100% rename from pkg/controller/component/lifecycle/errors.go rename to pkg/controller/lifecycle/errors.go diff --git a/pkg/controller/component/lifecycle/kbagent.go b/pkg/controller/lifecycle/kbagent.go similarity index 84% rename from pkg/controller/component/lifecycle/kbagent.go rename to pkg/controller/lifecycle/kbagent.go index 0c8019abc37..1b4a9f69637 100644 --- a/pkg/controller/component/lifecycle/kbagent.go +++ b/pkg/controller/lifecycle/kbagent.go @@ -33,8 +33,6 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/component" - "github.com/apecloud/kubeblocks/pkg/controller/instanceset" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" kbagt "github.com/apecloud/kubeblocks/pkg/kbagent" kbacli "github.com/apecloud/kubeblocks/pkg/kbagent/client" @@ -47,68 +45,72 @@ type lifecycleAction interface { } type kbagent struct { - synthesizedComp *component.SynthesizedComponent - pods []*corev1.Pod - pod *corev1.Pod + namespace string + clusterName string + compName string + lifecycleActions *appsv1.ComponentLifecycleActions + templateVars map[string]any + pods []*corev1.Pod + pod *corev1.Pod } var _ Lifecycle = &kbagent{} func (a *kbagent) PostProvision(ctx context.Context, cli client.Reader, opts *Options) error { lfa := &postProvision{ - namespace: a.synthesizedComp.Namespace, - clusterName: a.synthesizedComp.ClusterName, - compName: a.synthesizedComp.Name, - action: a.synthesizedComp.LifecycleActions.PostProvision, + namespace: a.namespace, + clusterName: a.clusterName, + compName: a.compName, + action: a.lifecycleActions.PostProvision, } return a.ignoreOutput(a.checkedCallAction(ctx, cli, lfa.action, lfa, opts)) } func (a *kbagent) PreTerminate(ctx context.Context, cli client.Reader, opts *Options) error { lfa := &preTerminate{ - namespace: a.synthesizedComp.Namespace, - clusterName: a.synthesizedComp.ClusterName, - compName: a.synthesizedComp.Name, - action: a.synthesizedComp.LifecycleActions.PreTerminate, + namespace: a.namespace, + clusterName: a.clusterName, + compName: a.compName, + action: a.lifecycleActions.PreTerminate, } return a.ignoreOutput(a.checkedCallAction(ctx, cli, lfa.action, lfa, opts)) } func (a *kbagent) RoleProbe(ctx context.Context, cli client.Reader, opts *Options) ([]byte, error) { - return a.checkedCallProbe(ctx, cli, a.synthesizedComp.LifecycleActions.RoleProbe, &roleProbe{}, opts) + return a.checkedCallProbe(ctx, cli, a.lifecycleActions.RoleProbe, &roleProbe{}, opts) } func (a *kbagent) Switchover(ctx context.Context, cli client.Reader, opts *Options, candidate string) error { roleName := a.pod.Labels[constant.RoleLabelKey] lfa := &switchover{ - namespace: a.synthesizedComp.Namespace, - clusterName: a.synthesizedComp.ClusterName, - compName: a.synthesizedComp.Name, + namespace: a.namespace, + clusterName: a.clusterName, + compName: a.compName, role: roleName, currentPod: a.pod.Name, candidatePod: candidate, } - return a.ignoreOutput(a.checkedCallAction(ctx, cli, a.synthesizedComp.LifecycleActions.Switchover, lfa, opts)) + return a.ignoreOutput(a.checkedCallAction(ctx, cli, a.lifecycleActions.Switchover, lfa, opts)) } func (a *kbagent) MemberJoin(ctx context.Context, cli client.Reader, opts *Options) error { lfa := &memberJoin{ - namespace: a.synthesizedComp.Namespace, - clusterName: a.synthesizedComp.ClusterName, - compName: a.synthesizedComp.Name, + namespace: a.namespace, + clusterName: a.clusterName, + compName: a.compName, pod: a.pod, } - return a.ignoreOutput(a.checkedCallAction(ctx, cli, a.synthesizedComp.LifecycleActions.MemberJoin, lfa, opts)) + return a.ignoreOutput(a.checkedCallAction(ctx, cli, a.lifecycleActions.MemberJoin, lfa, opts)) } func (a *kbagent) MemberLeave(ctx context.Context, cli client.Reader, opts *Options) error { lfa := &memberLeave{ - namespace: a.synthesizedComp.Namespace, - clusterName: a.synthesizedComp.ClusterName, - compName: a.synthesizedComp.Name, + namespace: a.namespace, + clusterName: a.clusterName, + compName: a.compName, pod: a.pod, } - return a.ignoreOutput(a.checkedCallAction(ctx, cli, a.synthesizedComp.LifecycleActions.MemberLeave, lfa, opts)) + return a.ignoreOutput(a.checkedCallAction(ctx, cli, a.lifecycleActions.MemberLeave, lfa, opts)) } func (a *kbagent) AccountProvision(ctx context.Context, cli client.Reader, opts *Options, statement, user, password string) error { @@ -117,7 +119,7 @@ func (a *kbagent) AccountProvision(ctx context.Context, cli client.Reader, opts user: user, password: password, } - return a.ignoreOutput(a.checkedCallAction(ctx, cli, a.synthesizedComp.LifecycleActions.AccountProvision, lfa, opts)) + return a.ignoreOutput(a.checkedCallAction(ctx, cli, a.lifecycleActions.AccountProvision, lfa, opts)) } func (a *kbagent) ignoreOutput(_ []byte, err error) error { @@ -165,7 +167,7 @@ func (a *kbagent) clusterReadyCheck(ctx context.Context, cli client.Reader) erro cluster := object.(*appsv1.Cluster) return cluster.Status.Phase == appsv1.RunningClusterPhase } - return a.readyCheck(ctx, cli, a.synthesizedComp.ClusterName, "cluster", &appsv1.Cluster{}, ready) + return a.readyCheck(ctx, cli, a.clusterName, "cluster", &appsv1.Cluster{}, ready) } func (a *kbagent) compReadyCheck(ctx context.Context, cli client.Reader) error { @@ -173,22 +175,22 @@ func (a *kbagent) compReadyCheck(ctx context.Context, cli client.Reader) error { comp := object.(*appsv1.Component) return comp.Status.Phase == appsv1.RunningComponentPhase } - compName := constant.GenerateClusterComponentName(a.synthesizedComp.ClusterName, a.synthesizedComp.Name) + compName := constant.GenerateClusterComponentName(a.clusterName, a.compName) return a.readyCheck(ctx, cli, compName, "component", &appsv1.Component{}, ready) } func (a *kbagent) runtimeReadyCheck(ctx context.Context, cli client.Reader) error { - name := constant.GenerateWorkloadNamePattern(a.synthesizedComp.ClusterName, a.synthesizedComp.Name) + name := constant.GenerateWorkloadNamePattern(a.clusterName, a.compName) ready := func(object client.Object) bool { its := object.(*workloads.InstanceSet) - return instanceset.IsInstancesReady(its) + return its.IsInstancesReady() } return a.readyCheck(ctx, cli, name, "runtime", &workloads.InstanceSet{}, ready) } func (a *kbagent) readyCheck(ctx context.Context, cli client.Reader, name, kind string, obj client.Object, ready func(object client.Object) bool) error { key := types.NamespacedName{ - Namespace: a.synthesizedComp.Namespace, + Namespace: a.namespace, Name: name, } if err := cli.Get(ctx, key, obj); err != nil { @@ -255,7 +257,7 @@ func (a *kbagent) parameters(ctx context.Context, cli client.Reader, lfa lifecyc func (a *kbagent) templateVarsParameters() (map[string]string, error) { m := map[string]string{} - for k, v := range a.synthesizedComp.TemplateVars { + for k, v := range a.templateVars { m[k] = v.(string) } return m, nil diff --git a/pkg/controller/component/lifecycle/lfa_account.go b/pkg/controller/lifecycle/lfa_account.go similarity index 100% rename from pkg/controller/component/lifecycle/lfa_account.go rename to pkg/controller/lifecycle/lfa_account.go diff --git a/pkg/controller/component/lifecycle/lfa_component.go b/pkg/controller/lifecycle/lfa_component.go similarity index 92% rename from pkg/controller/component/lifecycle/lfa_component.go rename to pkg/controller/lifecycle/lfa_component.go index ccdbaf73f95..eb5a6baf4b6 100644 --- a/pkg/controller/component/lifecycle/lfa_component.go +++ b/pkg/controller/lifecycle/lfa_component.go @@ -21,14 +21,15 @@ package lifecycle import ( "context" + "fmt" "strings" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/model" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) const ( @@ -126,8 +127,8 @@ func hackParameters4Comp(ctx context.Context, cli client.Reader, namespace, clus cl := make([][]string, 0) ccl := make([][]string, 0) for _, comp := range compList.Items { - name, _ := component.ShortName(clusterName, comp.Name) - pods, err := component.ListOwnedPods(ctx, cli, namespace, clusterName, name) + name, _ := shortName(clusterName, comp.Name) + pods, err := intctrlutil.ListOwnedPods(ctx, cli, namespace, clusterName, name) if err != nil { return err } @@ -163,7 +164,7 @@ func hackParameters4Comp(ctx context.Context, cli client.Reader, namespace, clus func() { all, deleting, undeleted := make([]string, 0), make([]string, 0), make([]string, 0) for _, comp := range compList.Items { - name, _ := component.ShortName(clusterName, comp.Name) + name, _ := shortName(clusterName, comp.Name) all = append(all, name) if model.IsObjectDeleting(&comp) { deleting = append(deleting, name) @@ -179,7 +180,7 @@ func hackParameters4Comp(ctx context.Context, cli client.Reader, namespace, clus if terminate { func() { for _, comp := range compList.Items { - name, _ := component.ShortName(clusterName, comp.Name) + name, _ := shortName(clusterName, comp.Name) if name == compName { if comp.Annotations != nil { val, ok := comp.Annotations[constant.ComponentScaleInAnnotationKey] @@ -194,3 +195,11 @@ func hackParameters4Comp(ctx context.Context, cli client.Reader, namespace, clus } return m, nil } + +func shortName(clusterName, compName string) (string, error) { + name, found := strings.CutPrefix(compName, fmt.Sprintf("%s-", clusterName)) + if !found { + return "", fmt.Errorf("the component name has no cluster name as prefix: %s", compName) + } + return name, nil +} diff --git a/pkg/controller/component/lifecycle/lfa_member.go b/pkg/controller/lifecycle/lfa_member.go similarity index 90% rename from pkg/controller/component/lifecycle/lfa_member.go rename to pkg/controller/lifecycle/lfa_member.go index 3d274216c92..445381a85ab 100644 --- a/pkg/controller/component/lifecycle/lfa_member.go +++ b/pkg/controller/lifecycle/lfa_member.go @@ -26,7 +26,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/component" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) const ( @@ -74,10 +74,10 @@ func (a *switchover) parameters(ctx context.Context, cli client.Reader) (map[str compName := constant.GenerateClusterComponentName(a.clusterName, a.compName) if len(a.candidatePod) > 0 { m[switchoverCandidateName] = a.candidatePod - m[switchoverCandidateFQDN] = component.PodFQDN(a.namespace, compName, a.candidatePod) + m[switchoverCandidateFQDN] = intctrlutil.PodFQDN(a.namespace, compName, a.candidatePod) } m[switchoverCurrentName] = a.currentPod - m[switchoverCurrentFQDN] = component.PodFQDN(a.namespace, compName, a.currentPod) + m[switchoverCurrentFQDN] = intctrlutil.PodFQDN(a.namespace, compName, a.currentPod) m[switchoverRole] = a.role return m, nil } @@ -102,7 +102,7 @@ func (a *memberJoin) parameters(ctx context.Context, cli client.Reader) (map[str // - KB_JOIN_MEMBER_POD_NAME: The pod name of the replica being added to the group. compName := constant.GenerateClusterComponentName(a.clusterName, a.compName) return map[string]string{ - joinMemberPodFQDNVar: component.PodFQDN(a.namespace, compName, a.pod.Name), + joinMemberPodFQDNVar: intctrlutil.PodFQDN(a.namespace, compName, a.pod.Name), joinMemberPodNameVar: a.pod.Name, }, nil } @@ -127,7 +127,7 @@ func (a *memberLeave) parameters(ctx context.Context, cli client.Reader) (map[st // - KB_LEAVE_MEMBER_POD_NAME: The pod name of the replica being removed from the group. compName := constant.GenerateClusterComponentName(a.clusterName, a.compName) return map[string]string{ - leaveMemberPodFQDNVar: component.PodFQDN(a.namespace, compName, a.pod.Name), + leaveMemberPodFQDNVar: intctrlutil.PodFQDN(a.namespace, compName, a.pod.Name), leaveMemberPodNameVar: a.pod.Name, }, nil } diff --git a/pkg/controller/component/lifecycle/lifecycle.go b/pkg/controller/lifecycle/lifecycle.go similarity index 83% rename from pkg/controller/component/lifecycle/lifecycle.go rename to pkg/controller/lifecycle/lifecycle.go index eebfbd28982..18253406aa1 100644 --- a/pkg/controller/component/lifecycle/lifecycle.go +++ b/pkg/controller/lifecycle/lifecycle.go @@ -27,7 +27,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - "github.com/apecloud/kubeblocks/pkg/controller/component" ) type Options struct { @@ -58,7 +57,8 @@ type Lifecycle interface { AccountProvision(ctx context.Context, cli client.Reader, opts *Options, statement, user, password string) error } -func New(synthesizedComp *component.SynthesizedComponent, pod *corev1.Pod, pods ...*corev1.Pod) (Lifecycle, error) { +func New(namespace, clusterName, compName string, lifecycleActions *appsv1.ComponentLifecycleActions, + templateVars map[string]any, pod *corev1.Pod, pods ...*corev1.Pod) (Lifecycle, error) { if pod == nil && len(pods) == 0 { return nil, fmt.Errorf("either pod or pods must be provided to call lifecycle actions") } @@ -69,8 +69,12 @@ func New(synthesizedComp *component.SynthesizedComponent, pod *corev1.Pod, pods pods = []*corev1.Pod{pod} } return &kbagent{ - synthesizedComp: synthesizedComp, - pods: pods, - pod: pod, + namespace: namespace, + clusterName: clusterName, + compName: compName, + lifecycleActions: lifecycleActions, + templateVars: templateVars, + pods: pods, + pod: pod, }, nil } diff --git a/pkg/controller/component/lifecycle/lifecycle_test.go b/pkg/controller/lifecycle/lifecycle_test.go similarity index 80% rename from pkg/controller/component/lifecycle/lifecycle_test.go rename to pkg/controller/lifecycle/lifecycle_test.go index 7ad5323b33e..f6d64108149 100644 --- a/pkg/controller/component/lifecycle/lifecycle_test.go +++ b/pkg/controller/lifecycle/lifecycle_test.go @@ -37,7 +37,6 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/component" kbacli "github.com/apecloud/kubeblocks/pkg/kbagent/client" "github.com/apecloud/kubeblocks/pkg/kbagent/proto" ) @@ -94,8 +93,11 @@ var mockKBAgentClient = func(mock func(*kbacli.MockClientMockRecorder)) { var _ = Describe("lifecycle", func() { var ( - synthesizedComp *component.SynthesizedComponent - pods []*corev1.Pod + namespace string + clusterName string + compName string + lifecycleActions *appsv1.ComponentLifecycleActions + pods []*corev1.Pod ) cleanEnv := func() { @@ -109,46 +111,34 @@ var _ = Describe("lifecycle", func() { BeforeEach(func() { cleanEnv() - synthesizedComp = &component.SynthesizedComponent{ - Namespace: "default", - ClusterName: "test-cluster", - Name: "kbagent", - PodSpec: &corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test-kbagent", - }, + namespace = "default" + clusterName = "test-cluster" + compName = "kbagent" + lifecycleActions = &appsv1.ComponentLifecycleActions{ + PostProvision: &appsv1.Action{ + Exec: &appsv1.ExecAction{ + Command: []string{"/bin/bash", "-c", "echo -n post-provision"}, + }, + TimeoutSeconds: 5, + RetryPolicy: &appsv1.RetryPolicy{ + MaxRetries: 5, + RetryInterval: 10, }, }, - LifecycleActions: &appsv1.ComponentLifecycleActions{ - PostProvision: &appsv1.Action{ + RoleProbe: &appsv1.Probe{ + Action: appsv1.Action{ Exec: &appsv1.ExecAction{ - Command: []string{"/bin/bash", "-c", "echo -n post-provision"}, + Command: []string{"/bin/bash", "-c", "echo -n role-probe"}, }, TimeoutSeconds: 5, - RetryPolicy: &appsv1.RetryPolicy{ - MaxRetries: 5, - RetryInterval: 10, - }, - }, - RoleProbe: &appsv1.Probe{ - Action: appsv1.Action{ - Exec: &appsv1.ExecAction{ - Command: []string{"/bin/bash", "-c", "echo -n role-probe"}, - }, - TimeoutSeconds: 5, - }, - InitialDelaySeconds: 5, - PeriodSeconds: 1, - SuccessThreshold: 3, - FailureThreshold: 3, }, + InitialDelaySeconds: 5, + PeriodSeconds: 1, + SuccessThreshold: 3, + FailureThreshold: 3, }, } - - pods = []*corev1.Pod{ - {}, - } + pods = []*corev1.Pod{{}} }) AfterEach(func() { @@ -159,19 +149,22 @@ var _ = Describe("lifecycle", func() { Context("new", func() { It("nil pod", func() { - _, err := New(nil, nil) + _, err := New("", "", "", nil, nil, nil) Expect(err).ShouldNot(BeNil()) Expect(err.Error()).Should(ContainSubstring("either pod or pods must be provided to call lifecycle actions")) }) It("pod", func() { pod := pods[0] - lifecycle, err := New(synthesizedComp, pod) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, pod) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) agent := lifecycle.(*kbagent) - Expect(agent.synthesizedComp).Should(Equal(synthesizedComp)) + Expect(agent.namespace).Should(Equal(namespace)) + Expect(agent.clusterName).Should(Equal(clusterName)) + Expect(agent.compName).Should(Equal(compName)) + Expect(agent.lifecycleActions).Should(Equal(lifecycleActions)) Expect(agent.pod).Should(Equal(pod)) Expect(agent.pods).Should(HaveLen(1)) Expect(agent.pods[0]).Should(Equal(pod)) @@ -179,12 +172,15 @@ var _ = Describe("lifecycle", func() { It("pods", func() { pod := pods[0] - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) agent := lifecycle.(*kbagent) - Expect(agent.synthesizedComp).Should(Equal(synthesizedComp)) + Expect(agent.namespace).Should(Equal(namespace)) + Expect(agent.clusterName).Should(Equal(clusterName)) + Expect(agent.compName).Should(Equal(compName)) + Expect(agent.lifecycleActions).Should(Equal(lifecycleActions)) Expect(agent.pod).Should(Equal(pod)) Expect(agent.pods).Should(HaveLen(1)) Expect(agent.pods[0]).Should(Equal(pod)) @@ -193,7 +189,7 @@ var _ = Describe("lifecycle", func() { Context("call action", func() { It("not defined", func() { - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -203,11 +199,11 @@ var _ = Describe("lifecycle", func() { }) It("action request", func() { - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) - action := synthesizedComp.LifecycleActions.PostProvision + action := lifecycleActions.PostProvision mockKBAgentClient(func(recorder *kbacli.MockClientMockRecorder) { recorder.Action(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req proto.ActionRequest) (proto.ActionResponse, error) { Expect(req.Action).Should(Equal("postProvision")) @@ -233,7 +229,7 @@ var _ = Describe("lifecycle", func() { }) It("succeed", func() { - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -248,7 +244,7 @@ var _ = Describe("lifecycle", func() { }) It("succeed and stdout", func() { - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -266,7 +262,7 @@ var _ = Describe("lifecycle", func() { }) It("fail - error code", func() { - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -335,7 +331,7 @@ var _ = Describe("lifecycle", func() { }) It("fail - error msg", func() { - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -355,7 +351,7 @@ var _ = Describe("lifecycle", func() { }) It("parameters", func() { - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -364,19 +360,19 @@ var _ = Describe("lifecycle", func() { objs: []client.Object{ &appsv1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, - Name: constant.GenerateClusterComponentName(synthesizedComp.ClusterName, synthesizedComp.Name), + Namespace: namespace, + Name: constant.GenerateClusterComponentName(clusterName, compName), Labels: map[string]string{ - constant.AppInstanceLabelKey: synthesizedComp.ClusterName, + constant.AppInstanceLabelKey: clusterName, }, }, }, &appsv1.Component{ ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, - Name: constant.GenerateClusterComponentName(synthesizedComp.ClusterName, "another"), + Namespace: namespace, + Name: constant.GenerateClusterComponentName(clusterName, "another"), Labels: map[string]string{ - constant.AppInstanceLabelKey: synthesizedComp.ClusterName, + constant.AppInstanceLabelKey: clusterName, }, }, }, @@ -387,7 +383,7 @@ var _ = Describe("lifecycle", func() { recorder.Action(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req proto.ActionRequest) (proto.ActionResponse, error) { Expect(req.Action).Should(Equal("postProvision")) Expect(req.Parameters).ShouldNot(BeNil()) // legacy parameters for post-provision action - Expect(req.Parameters[hackedAllCompList]).Should(Equal(strings.Join([]string{synthesizedComp.Name, "another"}, ","))) + Expect(req.Parameters[hackedAllCompList]).Should(Equal(strings.Join([]string{compName, "another"}, ","))) return proto.ActionResponse{}, nil }).AnyTimes() }) @@ -399,9 +395,8 @@ var _ = Describe("lifecycle", func() { It("template vars", func() { key := "TEMPLATE_VAR1" val := "template-vars1" - synthesizedComp.TemplateVars = map[string]any{key: val} - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, map[string]any{key: val}, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -423,9 +418,9 @@ var _ = Describe("lifecycle", func() { It("precondition", func() { clusterReady := appsv1.ClusterReadyPreConditionType - synthesizedComp.LifecycleActions.PostProvision.PreCondition = &clusterReady + lifecycleActions.PostProvision.PreCondition = &clusterReady - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -434,8 +429,8 @@ var _ = Describe("lifecycle", func() { objs: []client.Object{ &appsv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, - Name: synthesizedComp.ClusterName, + Namespace: namespace, + Name: clusterName, }, Status: appsv1.ClusterStatus{ Phase: appsv1.RunningClusterPhase, @@ -456,9 +451,9 @@ var _ = Describe("lifecycle", func() { It("precondition - fail", func() { clusterReady := appsv1.ClusterReadyPreConditionType - synthesizedComp.LifecycleActions.PostProvision.PreCondition = &clusterReady + lifecycleActions.PostProvision.PreCondition = &clusterReady - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -467,8 +462,8 @@ var _ = Describe("lifecycle", func() { objs: []client.Object{ &appsv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, - Name: synthesizedComp.ClusterName, + Namespace: namespace, + Name: clusterName, }, Status: appsv1.ClusterStatus{ Phase: appsv1.FailedClusterPhase, @@ -483,11 +478,11 @@ var _ = Describe("lifecycle", func() { }) It("pod selector - any", func() { - synthesizedComp.LifecycleActions.PostProvision.Exec.TargetPodSelector = appsv1.AnyReplica + lifecycleActions.PostProvision.Exec.TargetPodSelector = appsv1.AnyReplica pods = []*corev1.Pod{ { ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, + Namespace: namespace, Name: "pod-0", }, Spec: corev1.PodSpec{ @@ -505,7 +500,7 @@ var _ = Describe("lifecycle", func() { }, { ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, + Namespace: namespace, Name: "pod-1", }, Spec: corev1.PodSpec{ @@ -523,7 +518,7 @@ var _ = Describe("lifecycle", func() { }, } - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -537,12 +532,12 @@ var _ = Describe("lifecycle", func() { }) It("pod selector - role", func() { - synthesizedComp.LifecycleActions.PostProvision.Exec.TargetPodSelector = appsv1.RoleSelector - synthesizedComp.LifecycleActions.PostProvision.Exec.MatchingKey = "leader" + lifecycleActions.PostProvision.Exec.TargetPodSelector = appsv1.RoleSelector + lifecycleActions.PostProvision.Exec.MatchingKey = "leader" pods = []*corev1.Pod{ { ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, + Namespace: namespace, Name: "pod-0", Labels: map[string]string{ constant.RoleLabelKey: "follower", @@ -563,7 +558,7 @@ var _ = Describe("lifecycle", func() { }, { ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, + Namespace: namespace, Name: "pod-1", Labels: map[string]string{ constant.RoleLabelKey: "leader", @@ -584,7 +579,7 @@ var _ = Describe("lifecycle", func() { }, } - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) @@ -594,12 +589,12 @@ var _ = Describe("lifecycle", func() { }) It("pod selector - has no matched", func() { - synthesizedComp.LifecycleActions.PostProvision.Exec.TargetPodSelector = appsv1.RoleSelector - synthesizedComp.LifecycleActions.PostProvision.Exec.MatchingKey = "leader" + lifecycleActions.PostProvision.Exec.TargetPodSelector = appsv1.RoleSelector + lifecycleActions.PostProvision.Exec.MatchingKey = "leader" pods = []*corev1.Pod{ { ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, + Namespace: namespace, Name: "pod-0", Labels: map[string]string{ constant.RoleLabelKey: "follower", @@ -608,7 +603,7 @@ var _ = Describe("lifecycle", func() { }, { ObjectMeta: metav1.ObjectMeta{ - Namespace: synthesizedComp.Namespace, + Namespace: namespace, Name: "pod-1", Labels: map[string]string{ constant.RoleLabelKey: "follower", @@ -617,7 +612,7 @@ var _ = Describe("lifecycle", func() { }, } - lifecycle, err := New(synthesizedComp, nil, pods...) + lifecycle, err := New(namespace, clusterName, compName, lifecycleActions, nil, nil, pods...) Expect(err).Should(BeNil()) Expect(lifecycle).ShouldNot(BeNil()) diff --git a/pkg/controller/component/lifecycle/suite_test.go b/pkg/controller/lifecycle/suite_test.go similarity index 98% rename from pkg/controller/component/lifecycle/suite_test.go rename to pkg/controller/lifecycle/suite_test.go index 541c2ba6798..2d1dd2cb62b 100644 --- a/pkg/controller/component/lifecycle/suite_test.go +++ b/pkg/controller/lifecycle/suite_test.go @@ -84,7 +84,7 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "..", "..", "config", "crd", "bases"), + filepath.Join("..", "..", "..", "config", "crd", "bases"), // use dependent external CRDs. // resolved by ref: https://github.com/operator-framework/operator-sdk/issues/4434#issuecomment-786794418 // filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "kubernetes-csi/external-snapshotter/", diff --git a/pkg/controller/multicluster/setup.go b/pkg/controller/multicluster/setup.go index 782395fbb0e..74957ec4d72 100644 --- a/pkg/controller/multicluster/setup.go +++ b/pkg/controller/multicluster/setup.go @@ -23,6 +23,7 @@ import ( "fmt" "strings" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -32,7 +33,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/client/config" - intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" ) var ( @@ -201,11 +203,38 @@ func clientOptions(scheme *runtime.Scheme, ctx string, config *rest.Config) (cli Mapper: mapper, Cache: &client.CacheOptions{ Unstructured: false, - DisableFor: intctrlutil.GetUncachedObjects(), + DisableFor: getUncachedObjects(), }, }, nil } +// getUncachedObjects returns a list of K8s objects, for these object types, +// and their list types, client.Reader will read directly from the API server instead +// of the cache, which may not be up-to-date. +// see sigs.k8s.io/controller-runtime/pkg/client/split.go to understand how client +// works with this UncachedObjects filter. +func getUncachedObjects() []client.Object { + // client-side read cache reduces the number of requests processed in the API server, + // which is good for performance. However, it can sometimes lead to obscure issues, + // most notably lacking read-after-write consistency, i.e. reading a value immediately + // after updating it may miss to see the changes. + // while in most cases this problem can be mitigated by retrying later in an idempotent + // manner, there are some cases where it cannot, for example if a decision is to be made + // that has side-effect operations such as returning an error message to the user + // (in webhook) or deleting certain resources (in controllerutil.HandleCRDeletion). + // additionally, retry loops cause unnecessary delays when reconciliations are processed. + // for the sake of performance, now only the objects created by the end-user is listed here, + // to solve the two problems mentioned above. + // consider carefully before adding new objects to this list. + return []client.Object{ + // avoid to cache potential large data objects + &corev1.ConfigMap{}, + &corev1.Secret{}, + &appsv1.Cluster{}, + &appsv1alpha1.Configuration{}, + } +} + func cacheOptions(opts client.Options) cache.Options { return cache.Options{ HTTPClient: opts.HTTPClient, diff --git a/pkg/controllerutil/volume_util.go b/pkg/controllerutil/volume_util.go index 01ee24a0fda..e9a3be8b900 100644 --- a/pkg/controllerutil/volume_util.go +++ b/pkg/controllerutil/volume_util.go @@ -26,6 +26,7 @@ import ( "golang.org/x/exp/maps" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/constant" @@ -116,3 +117,30 @@ func BuildVolumeMode(configs []string, configSpec appsv1.ComponentTemplateSpec) } return &scriptsDefaultMode } + +func ToCoreV1PVCs(vcts []appsv1.ClusterComponentVolumeClaimTemplate) []corev1.PersistentVolumeClaim { + storageClassName := func(spec appsv1.PersistentVolumeClaimSpec, defaultStorageClass string) *string { + if spec.StorageClassName != nil && *spec.StorageClassName != "" { + return spec.StorageClassName + } + if defaultStorageClass != "" { + return &defaultStorageClass + } + return nil + } + var pvcs []corev1.PersistentVolumeClaim + for _, v := range vcts { + pvcs = append(pvcs, corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: v.Name, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: v.Spec.AccessModes, + Resources: v.Spec.Resources, + StorageClassName: storageClassName(v.Spec, viper.GetString(constant.CfgKeyDefaultStorageClass)), + VolumeMode: v.Spec.VolumeMode, + }, + }) + } + return pvcs +} diff --git a/pkg/controllerutil/workload_utils.go b/pkg/controllerutil/workload_utils.go new file mode 100644 index 00000000000..c9f53868d1a --- /dev/null +++ b/pkg/controllerutil/workload_utils.go @@ -0,0 +1,90 @@ +/* +Copyright (C) 2022-2024 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 controllerutil + +import ( + "context" + "fmt" + "maps" + "reflect" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/multicluster" + "github.com/apecloud/kubeblocks/pkg/generics" + viper "github.com/apecloud/kubeblocks/pkg/viperx" +) + +func ListOwnedPods(ctx context.Context, cli client.Reader, namespace, clusterName, compName string, + opts ...client.ListOption) ([]*corev1.Pod, error) { + return listPods(ctx, cli, namespace, clusterName, compName, nil, opts...) +} + +func listPods(ctx context.Context, cli client.Reader, namespace, clusterName, compName string, + labels map[string]string, opts ...client.ListOption) ([]*corev1.Pod, error) { + if labels == nil { + labels = constant.GetCompLabels(clusterName, compName) + } else { + maps.Copy(labels, constant.GetCompLabels(clusterName, compName)) + } + if opts == nil { + opts = make([]client.ListOption, 0) + } + opts = append(opts, inDataContext()) + return listObjWithLabelsInNamespace(ctx, cli, generics.PodSignature, namespace, labels, opts...) +} + +func listObjWithLabelsInNamespace[T generics.Object, PT generics.PObject[T], L generics.ObjList[T], PL generics.PObjList[T, L]]( + ctx context.Context, cli client.Reader, _ func(T, PT, L, PL), namespace string, labels client.MatchingLabels, opts ...client.ListOption) ([]PT, error) { + if opts == nil { + opts = make([]client.ListOption, 0) + } + opts = append(opts, []client.ListOption{labels, client.InNamespace(namespace)}...) + + var objList L + if err := cli.List(ctx, PL(&objList), opts...); err != nil { + return nil, err + } + + objs := make([]PT, 0) + items := reflect.ValueOf(&objList).Elem().FieldByName("Items").Interface().([]T) + for i := range items { + objs = append(objs, &items[i]) + } + return objs, nil +} + +func inDataContext() *multicluster.ClientOption { + return multicluster.InDataContext() +} + +func PodFQDN(namespace, compName, podName string) string { + return fmt.Sprintf("%s.%s-headless.%s.svc.%s", podName, compName, namespace, clusterDomain()) +} + +func ServiceFQDN(namespace, serviceName string) string { + return fmt.Sprintf("%s.%s.svc.%s", serviceName, namespace, clusterDomain()) +} + +func clusterDomain() string { + return viper.GetString(constant.KubernetesClusterDomainEnv) +} diff --git a/pkg/operations/switchover.go b/pkg/operations/switchover.go index bb44784b613..aec7fa45b2c 100644 --- a/pkg/operations/switchover.go +++ b/pkg/operations/switchover.go @@ -38,7 +38,7 @@ import ( opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" - "github.com/apecloud/kubeblocks/pkg/controller/component/lifecycle" + "github.com/apecloud/kubeblocks/pkg/controller/lifecycle" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) @@ -257,7 +257,8 @@ func doSwitchover(ctx context.Context, cli client.Reader, synthesizedComp *compo } } - lfa, err := lifecycle.New(synthesizedComp, pod, pods...) + lfa, err := lifecycle.New(synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name, + synthesizedComp.LifecycleActions, synthesizedComp.TemplateVars, pod, pods...) if err != nil { return err }