diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go index d602b67f9c0..90aabaf6dca 100644 --- a/apis/apps/v1/componentdefinition_types.go +++ b/apis/apps/v1/componentdefinition_types.go @@ -415,11 +415,16 @@ type ComponentDefinitionSpec struct { // +optional Available *ComponentAvailable `json:"available,omitempty"` + // FIXME: there's a bug in CEL's cost estimation when chaining .filter() and .map(). + // It was fixed in k8s 1.30, see: https://github.com/kubernetes/kubernetes/pull/123562. + // Maybe we can add this back later. + // TODO +kubebuilder:validation:XValidation:rule="self.filter(x, x.participatesInQuorum == true).map(x, x.updatePriority).min() > self.filter(x, x.participatesInQuorum == false).map(x, x.updatePriority).max()",message="Roles participate in quorum should have higher update priority than roles do not participate in quorum." + // Enumerate all possible roles assigned to each replica of the Component, influencing its behavior. // - // A replica can have zero to multiple roles. - // KubeBlocks operator determines the roles of each replica by invoking the `lifecycleActions.roleProbe` method. - // This action returns a list of roles for each replica, and the returned roles must be predefined in the `roles` field. + // A replica can have zero or one role. + // KubeBlocks operator determines the role of each replica by invoking the `lifecycleActions.roleProbe` method. + // This action returns the role for each replica, and the returned role must be predefined here. // // The roles assigned to a replica can influence various aspects of the Component's behavior, such as: // @@ -430,6 +435,7 @@ type ComponentDefinitionSpec struct { // // This field is immutable. // + // +kubebuilder:validation:MaxItems=128 // +optional Roles []ReplicaRole `json:"roles,omitempty"` @@ -1419,10 +1425,15 @@ type ComponentAvailableProbeAssertion struct { Strict *bool `json:"strict,omitempty"` } -// ReplicaRole represents a role that can be assumed by a component instance. +// ReplicaRole represents a role that can be assigned to a component instance, defining its behavior and responsibilities. type ReplicaRole struct { - // Defines the role's identifier. It is used to set the "apps.kubeblocks.io/role" label value - // on the corresponding object. + // Name defines the role's unique identifier. This value is used to set the "apps.kubeblocks.io/role" label + // on the corresponding object to identify its role. + // + // For example, common role names include: + // - "leader": The primary/master instance that handles write operations + // - "follower": Secondary/replica instances that replicate data from the leader + // - "learner": Read-only instances that don't participate in elections // // This field is immutable once set. // @@ -1431,32 +1442,39 @@ type ReplicaRole struct { // +kubebuilder:validation:Pattern=`^.*[^\s]+.*$` Name string `json:"name"` - // Indicates whether a replica assigned this role is capable of providing services. + // UpdatePriority determines the order in which pods with different roles are updated. + // Pods are sorted by this priority (higher numbers = higher priority) and updated accordingly. + // Roles with the highest priority will be updated last. + // The default priority is 0. // - // This field is immutable once set. - // - // +kubebuilder:default=false - // +optional - Serviceable bool `json:"serviceable,omitempty"` - - // Determines if a replica in this role has the authority to perform write operations. - // A writable replica can modify data, handle update operations. + // For example: + // - Leader role may have priority 2 (updated last) + // - Follower role may have priority 1 (updated before leader) + // - Learner role may have priority 0 (updated first) // // This field is immutable once set. // - // +kubebuilder:default=false + // +kubebuilder:default=0 // +optional - Writable bool `json:"writable,omitempty"` + UpdatePriority int `json:"updatePriority"` - // Specifies whether a replica with this role has voting rights. - // In distributed systems, this typically means the replica can participate in consensus decisions, - // configuration changes, or other processes that require a quorum. + // ParticipatesInQuorum indicates if pods with this role are counted when determining quorum. + // This affects update strategies that need to maintain quorum for availability. Roles participate + // in quorum should have higher update priority than roles do not participate in quorum. + // The default value is false. + // + // For example, in a 5-pod component where: + // - 2 learner pods (participatesInQuorum=false) + // - 2 follower pods (participatesInQuorum=true) + // - 1 leader pod (participatesInQuorum=true) + // The quorum size would be 3 (based on the 3 participating pods), allowing parallel updates + // of 2 learners and 1 follower while maintaining quorum. // // This field is immutable once set. // // +kubebuilder:default=false // +optional - Votable bool `json:"votable,omitempty"` + ParticipatesInQuorum bool `json:"participatesInQuorum"` } // UpdateStrategy defines the update strategy for cluster components. This strategy determines how updates are applied diff --git a/apis/workloads/v1/instanceset_types.go b/apis/workloads/v1/instanceset_types.go index 97ec45ed254..1a824f1479d 100644 --- a/apis/workloads/v1/instanceset_types.go +++ b/apis/workloads/v1/instanceset_types.go @@ -291,11 +291,6 @@ type InstanceSetStatus struct { // +optional MembersStatus []MemberStatus `json:"membersStatus,omitempty"` - // Indicates whether it is required for the InstanceSet to have at least one primary instance ready. - // - // +optional - ReadyWithoutPrimary bool `json:"readyWithoutPrimary,omitempty"` - // currentRevisions, if not empty, indicates the old version of the InstanceSet used to generate the underlying workload. // key is the pod name, value is the revision. // @@ -330,33 +325,9 @@ const ( PreferInPlacePodUpdatePolicyType PodUpdatePolicyType = "PreferInPlace" ) -type ReplicaRole struct { - - // Defines the role name of the replica. - // - // +kubebuilder:validation:Required - // +kubebuilder:default=leader - Name string `json:"name"` - - // Specifies the service capabilities of this member. - // - // +kubebuilder:validation:Required - // +kubebuilder:default=ReadWrite - // +kubebuilder:validation:Enum={None, Readonly, ReadWrite} - AccessMode AccessMode `json:"accessMode"` - - // Indicates if this member has voting rights. - // - // +kubebuilder:default=true - // +optional - CanVote bool `json:"canVote"` - - // Determines if this member is the leader. - // - // +kubebuilder:default=false - // +optional - IsLeader bool `json:"isLeader"` -} +// ReplicaRole represents a role that can be assigned to a component instance, defining its behavior and responsibilities. +// +kubebuilder:object:generate=false +type ReplicaRole = kbappsv1.ReplicaRole // AccessMode defines SVC access mode enums. // +enum @@ -604,18 +575,5 @@ func (r *InstanceSet) IsInstanceSetReady() bool { 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 + return len(membersStatus) == int(*r.Spec.Replicas) } diff --git a/apis/workloads/v1/zz_generated.deepcopy.go b/apis/workloads/v1/zz_generated.deepcopy.go index 1be40a7f8c0..d015fa73ef1 100644 --- a/apis/workloads/v1/zz_generated.deepcopy.go +++ b/apis/workloads/v1/zz_generated.deepcopy.go @@ -194,7 +194,7 @@ func (in *InstanceSetSpec) DeepCopyInto(out *InstanceSetSpec) { in.UpdateStrategy.DeepCopyInto(&out.UpdateStrategy) if in.Roles != nil { in, out := &in.Roles, &out.Roles - *out = make([]ReplicaRole, len(*in)) + *out = make([]appsv1.ReplicaRole, len(*in)) copy(*out, *in) } if in.MembershipReconfiguration != nil { @@ -299,7 +299,7 @@ func (in *MemberStatus) DeepCopyInto(out *MemberStatus) { *out = *in if in.ReplicaRole != nil { in, out := &in.ReplicaRole, &out.ReplicaRole - *out = new(ReplicaRole) + *out = new(appsv1.ReplicaRole) **out = **in } } @@ -358,18 +358,3 @@ func (in *MembershipReconfiguration) DeepCopy() *MembershipReconfiguration { 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 -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaRole. -func (in *ReplicaRole) DeepCopy() *ReplicaRole { - if in == nil { - return nil - } - out := new(ReplicaRole) - in.DeepCopyInto(out) - return out -} diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml index fe6bec4e932..e9349ebd2aa 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml @@ -8427,9 +8427,9 @@ spec: Enumerate all possible roles assigned to each replica of the Component, influencing its behavior. - A replica can have zero to multiple roles. - KubeBlocks operator determines the roles of each replica by invoking the `lifecycleActions.roleProbe` method. - This action returns a list of roles for each replica, and the returned roles must be predefined in the `roles` field. + A replica can have zero or one role. + KubeBlocks operator determines the role of each replica by invoking the `lifecycleActions.roleProbe` method. + This action returns the role for each replica, and the returned role must be predefined here. The roles assigned to a replica can influence various aspects of the Component's behavior, such as: @@ -8443,49 +8443,65 @@ spec: This field is immutable. items: - description: ReplicaRole represents a role that can be assumed by - a component instance. + description: ReplicaRole represents a role that can be assigned + to a component instance, defining its behavior and responsibilities. properties: name: description: |- - Defines the role's identifier. It is used to set the "apps.kubeblocks.io/role" label value - on the corresponding object. + Name defines the role's unique identifier. This value is used to set the "apps.kubeblocks.io/role" label + on the corresponding object to identify its role. + + + For example, common role names include: + - "leader": The primary/master instance that handles write operations + - "follower": Secondary/replica instances that replicate data from the leader + - "learner": Read-only instances that don't participate in elections This field is immutable once set. maxLength: 32 pattern: ^.*[^\s]+.*$ type: string - serviceable: + participatesInQuorum: default: false description: |- - Indicates whether a replica assigned this role is capable of providing services. + ParticipatesInQuorum indicates if pods with this role are counted when determining quorum. + This affects update strategies that need to maintain quorum for availability. Roles participate + in quorum should have higher update priority than roles do not participate in quorum. + The default value is false. - This field is immutable once set. - type: boolean - votable: - default: false - description: |- - Specifies whether a replica with this role has voting rights. - In distributed systems, this typically means the replica can participate in consensus decisions, - configuration changes, or other processes that require a quorum. + For example, in a 5-pod component where: + - 2 learner pods (participatesInQuorum=false) + - 2 follower pods (participatesInQuorum=true) + - 1 leader pod (participatesInQuorum=true) + The quorum size would be 3 (based on the 3 participating pods), allowing parallel updates + of 2 learners and 1 follower while maintaining quorum. This field is immutable once set. type: boolean - writable: - default: false + updatePriority: + default: 0 description: |- - Determines if a replica in this role has the authority to perform write operations. - A writable replica can modify data, handle update operations. + UpdatePriority determines the order in which pods with different roles are updated. + Pods are sorted by this priority (higher numbers = higher priority) and updated accordingly. + Roles with the highest priority will be updated last. + The default priority is 0. + + + For example: + - Leader role may have priority 2 (updated last) + - Follower role may have priority 1 (updated before leader) + - Learner role may have priority 0 (updated first) This field is immutable once set. - type: boolean + type: integer required: - name type: object + maxItems: 128 type: array runtime: description: |- diff --git a/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml b/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml index d20170e3eca..9a14f500498 100644 --- a/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml +++ b/config/crd/bases/workloads.kubeblocks.io_instancesets.yaml @@ -4098,29 +4098,62 @@ spec: description: A list of roles defined in the system. Instanceset obtains role through pods' role label `kubeblocks.io/role`. items: + description: ReplicaRole represents a role that can be assigned + to a component instance, defining its behavior and responsibilities. properties: - accessMode: - default: ReadWrite - description: Specifies the service capabilities of this member. - enum: - - None - - Readonly - - ReadWrite + name: + description: |- + Name defines the role's unique identifier. This value is used to set the "apps.kubeblocks.io/role" label + on the corresponding object to identify its role. + + + For example, common role names include: + - "leader": The primary/master instance that handles write operations + - "follower": Secondary/replica instances that replicate data from the leader + - "learner": Read-only instances that don't participate in elections + + + This field is immutable once set. + maxLength: 32 + pattern: ^.*[^\s]+.*$ type: string - canVote: - default: true - description: Indicates if this member has voting rights. - type: boolean - isLeader: + participatesInQuorum: default: false - description: Determines if this member is the leader. + description: |- + ParticipatesInQuorum indicates if pods with this role are counted when determining quorum. + This affects update strategies that need to maintain quorum for availability. Roles participate + in quorum should have higher update priority than roles do not participate in quorum. + The default value is false. + + + For example, in a 5-pod component where: + - 2 learner pods (participatesInQuorum=false) + - 2 follower pods (participatesInQuorum=true) + - 1 leader pod (participatesInQuorum=true) + The quorum size would be 3 (based on the 3 participating pods), allowing parallel updates + of 2 learners and 1 follower while maintaining quorum. + + + This field is immutable once set. type: boolean - name: - default: leader - description: Defines the role name of the replica. - type: string + updatePriority: + default: 0 + description: |- + UpdatePriority determines the order in which pods with different roles are updated. + Pods are sorted by this priority (higher numbers = higher priority) and updated accordingly. + Roles with the highest priority will be updated last. + The default priority is 0. + + + For example: + - Leader role may have priority 2 (updated last) + - Follower role may have priority 1 (updated before leader) + - Learner role may have priority 0 (updated first) + + + This field is immutable once set. + type: integer required: - - accessMode - name type: object type: array @@ -12277,29 +12310,59 @@ spec: role: description: Defines the role of the replica in the cluster. properties: - accessMode: - default: ReadWrite - description: Specifies the service capabilities of this - member. - enum: - - None - - Readonly - - ReadWrite + name: + description: |- + Name defines the role's unique identifier. This value is used to set the "apps.kubeblocks.io/role" label + on the corresponding object to identify its role. + + + For example, common role names include: + - "leader": The primary/master instance that handles write operations + - "follower": Secondary/replica instances that replicate data from the leader + - "learner": Read-only instances that don't participate in elections + + + This field is immutable once set. + maxLength: 32 + pattern: ^.*[^\s]+.*$ type: string - canVote: - default: true - description: Indicates if this member has voting rights. - type: boolean - isLeader: + participatesInQuorum: default: false - description: Determines if this member is the leader. + description: |- + ParticipatesInQuorum indicates if pods with this role are counted when determining quorum. + This affects update strategies that need to maintain quorum for availability. Roles participate + in quorum should have higher update priority than roles do not participate in quorum. + The default value is false. + + + For example, in a 5-pod component where: + - 2 learner pods (participatesInQuorum=false) + - 2 follower pods (participatesInQuorum=true) + - 1 leader pod (participatesInQuorum=true) + The quorum size would be 3 (based on the 3 participating pods), allowing parallel updates + of 2 learners and 1 follower while maintaining quorum. + + + This field is immutable once set. type: boolean - name: - default: leader - description: Defines the role name of the replica. - type: string + updatePriority: + default: 0 + description: |- + UpdatePriority determines the order in which pods with different roles are updated. + Pods are sorted by this priority (higher numbers = higher priority) and updated accordingly. + Roles with the highest priority will be updated last. + The default priority is 0. + + + For example: + - Leader role may have priority 2 (updated last) + - Follower role may have priority 1 (updated before leader) + - Learner role may have priority 0 (updated first) + + + This field is immutable once set. + type: integer required: - - accessMode - name type: object required: @@ -12324,10 +12387,6 @@ spec: this InstanceSet with a Ready Condition. format: int32 type: integer - readyWithoutPrimary: - description: Indicates whether it is required for the InstanceSet - to have at least one primary instance ready. - type: boolean replicas: description: replicas is the number of instances created by the InstanceSet controller. diff --git a/controllers/apps/component/component_controller_test.go b/controllers/apps/component/component_controller_test.go index 8dc36dc1039..a04abeb486c 100644 --- a/controllers/apps/component/component_controller_test.go +++ b/controllers/apps/component/component_controller_test.go @@ -1247,22 +1247,19 @@ var _ = Describe("Component Controller", func() { By("check default component roles") targetRoles := []workloads.ReplicaRole{ { - Name: "leader", - AccessMode: workloads.ReadWriteMode, - CanVote: true, - IsLeader: true, + Name: "leader", + ParticipatesInQuorum: true, + UpdatePriority: 5, }, { - Name: "follower", - AccessMode: workloads.ReadonlyMode, - CanVote: true, - IsLeader: false, + Name: "follower", + ParticipatesInQuorum: true, + UpdatePriority: 4, }, { - Name: "learner", - AccessMode: workloads.NoneMode, - CanVote: false, - IsLeader: false, + Name: "learner", + ParticipatesInQuorum: false, + UpdatePriority: 2, }, } itsKey := types.NamespacedName{ diff --git a/controllers/apps/component/transformer_component_status.go b/controllers/apps/component/transformer_component_status.go index f0c42c17647..1454533a5df 100644 --- a/controllers/apps/component/transformer_component_status.go +++ b/controllers/apps/component/transformer_component_status.go @@ -221,7 +221,7 @@ func (t *componentStatusTransformer) isWorkloadUpdated() bool { return generation == strconv.FormatInt(t.comp.Generation, 10) } -// isRunning checks if the component underlying workload is running. +// isRunning checks if the component's underlying workload is running. func (t *componentStatusTransformer) isInstanceSetRunning() bool { if t.runningITS == nil { return false diff --git a/controllers/apps/component/transformer_component_workload_test.go b/controllers/apps/component/transformer_component_workload_test.go index 680cc726bc1..0b00c271f6d 100644 --- a/controllers/apps/component/transformer_component_workload_test.go +++ b/controllers/apps/component/transformer_component_workload_test.go @@ -26,7 +26,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/graph" @@ -51,6 +50,11 @@ var _ = Describe("Component Workload Operations Test", func() { synthesizeComp *component.SynthesizedComponent ) + roles := []appsv1.ReplicaRole{ + {Name: "leader", UpdatePriority: 3}, + {Name: "follower", UpdatePriority: 2}, + } + newDAG := func(graphCli model.GraphClient, comp *appsv1.Component) *graph.DAG { d := graph.NewDAG() graphCli.Root(d, comp, comp, model.ActionStatusPtr()) @@ -76,10 +80,7 @@ var _ = Describe("Component Workload Operations Test", func() { Namespace: testCtx.DefaultNamespace, ClusterName: clusterName, Name: compName, - Roles: []appsv1.ReplicaRole{ - {Name: "leader", Serviceable: true, Writable: true, Votable: true}, - {Name: "follower", Serviceable: false, Writable: false, Votable: false}, - }, + Roles: roles, LifecycleActions: &appsv1.ComponentLifecycleActions{ MemberJoin: &appsv1.Action{ Exec: &appsv1.ExecAction{ @@ -152,10 +153,7 @@ var _ = Describe("Component Workload Operations Test", func() { AddAppComponentLabel(compName). AddAppManagedByLabel(). SetReplicas(2). - SetRoles([]workloads.ReplicaRole{ - {Name: "leader", AccessMode: workloads.ReadWriteMode, CanVote: true, IsLeader: true}, - {Name: "follower", AccessMode: workloads.ReadonlyMode, CanVote: true, IsLeader: false}, - }). + SetRoles(roles). GetObject() mockCluster := testapps.NewClusterFactory(testCtx.DefaultNamespace, "test-cluster", "test-def"). diff --git a/controllers/apps/componentdefinition_controller_test.go b/controllers/apps/componentdefinition_controller_test.go index dd7ef1f38df..6d20f406aa4 100644 --- a/controllers/apps/componentdefinition_controller_test.go +++ b/controllers/apps/componentdefinition_controller_test.go @@ -175,8 +175,8 @@ var _ = Describe("ComponentDefinition Controller", func() { SetRuntime(nil). AddService("default", "", 3306, "leader"). AddService("readonly", "readonly", 3306, "follower"). - AddRole("leader", true, true). - AddRole("follower", true, false). + AddRole("leader"). + AddRole("follower"). Create(&testCtx).GetObject() checkObjectStatus(componentDefObj, kbappsv1.AvailablePhase) @@ -188,8 +188,8 @@ var _ = Describe("ComponentDefinition Controller", func() { SetRuntime(nil). AddService("default", "", 3306, "leader"). AddService("default", "readonly", 3306, "follower"). - AddRole("leader", true, true). - AddRole("follower", true, false). + AddRole("leader"). + AddRole("follower"). Create(&testCtx).GetObject() checkObjectStatus(componentDefObj, kbappsv1.UnavailablePhase) @@ -201,8 +201,8 @@ var _ = Describe("ComponentDefinition Controller", func() { SetRuntime(nil). AddService("default", "default", 3306, "leader"). AddService("readonly", "default", 3306, "follower"). - AddRole("leader", true, true). - AddRole("follower", true, false). + AddRole("leader"). + AddRole("follower"). Create(&testCtx).GetObject() checkObjectStatus(componentDefObj, kbappsv1.UnavailablePhase) @@ -214,8 +214,8 @@ var _ = Describe("ComponentDefinition Controller", func() { SetRuntime(nil). AddService("default", "", 3306, "leader"). AddService("readonly", "", 3306, "follower"). - AddRole("leader", true, true). - AddRole("follower", true, false). + AddRole("leader"). + AddRole("follower"). Create(&testCtx).GetObject() checkObjectStatus(componentDefObj, kbappsv1.UnavailablePhase) @@ -226,8 +226,8 @@ var _ = Describe("ComponentDefinition Controller", func() { componentDefObj := testapps.NewComponentDefinitionFactory(componentDefName). SetRuntime(nil). AddServiceExt("default", "", corev1.ServiceSpec{}, "leader"). - AddRole("leader", true, true). - AddRole("follower", true, false). + AddRole("leader"). + AddRole("follower"). Create(&testCtx).GetObject() checkObjectStatus(componentDefObj, kbappsv1.UnavailablePhase) @@ -240,8 +240,8 @@ var _ = Describe("ComponentDefinition Controller", func() { AddService("default", "", 3306, "leader"). AddService("readonly", "readonly", 3306, "follower"). AddService("undefined", "undefined", 3306, "undefined"). - AddRole("leader", true, true). - AddRole("follower", true, false). + AddRole("leader"). + AddRole("follower"). Create(&testCtx).GetObject() checkObjectStatus(componentDefObj, kbappsv1.UnavailablePhase) @@ -461,9 +461,9 @@ var _ = Describe("ComponentDefinition Controller", func() { By("create a ComponentDefinition obj") componentDefObj := testapps.NewComponentDefinitionFactory(componentDefName). SetRuntime(nil). - AddRole("leader", true, true). - AddRole("follower", true, false). - AddRole("learner", false, false). + AddRole("leader"). + AddRole("follower"). + AddRole("learner"). Create(&testCtx).GetObject() checkObjectStatus(componentDefObj, kbappsv1.AvailablePhase) @@ -473,10 +473,10 @@ var _ = Describe("ComponentDefinition Controller", func() { By("create a ComponentDefinition obj") componentDefObj := testapps.NewComponentDefinitionFactory(componentDefName). SetRuntime(nil). - AddRole("leader", true, true). - AddRole("follower", true, false). - AddRole("learner", false, false). - AddRole("learner", true, false). + AddRole("leader"). + AddRole("follower"). + AddRole("learner"). + AddRole("learner"). Create(&testCtx).GetObject() checkObjectStatus(componentDefObj, kbappsv1.UnavailablePhase) diff --git a/controllers/apps/configuration/policy_util.go b/controllers/apps/configuration/policy_util.go index 3f92f3ff0df..f142f4d74f7 100644 --- a/controllers/apps/configuration/policy_util.go +++ b/controllers/apps/configuration/policy_util.go @@ -33,7 +33,6 @@ import ( "github.com/apecloud/kubeblocks/pkg/configuration/core" cfgproto "github.com/apecloud/kubeblocks/pkg/configuration/proto" "github.com/apecloud/kubeblocks/pkg/constant" - "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/controller/configuration" "github.com/apecloud/kubeblocks/pkg/controller/instanceset" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" @@ -85,7 +84,11 @@ func getPodsForOnlineUpdate(params reconfigureParams) ([]corev1.Pod, error) { } if params.SynthesizedComponent != nil { - instanceset.SortPods(pods, instanceset.ComposeRolePriorityMap(component.ConvertSynthesizeCompRoleToInstanceSetRole(params.SynthesizedComponent)), true) + instanceset.SortPods( + pods, + instanceset.ComposeRolePriorityMap(params.SynthesizedComponent.Roles), + true, + ) } return pods, nil } diff --git a/controllers/apps/configuration/policy_util_test.go b/controllers/apps/configuration/policy_util_test.go index 451b92ac42b..4859b8e1a4d 100644 --- a/controllers/apps/configuration/policy_util_test.go +++ b/controllers/apps/configuration/policy_util_test.go @@ -164,16 +164,14 @@ func newMockReconfigureParams(testName string, cli client.Client, paramOps ...Pa MinReadySeconds: 5, Roles: []appsv1.ReplicaRole{ { - Name: "leader", - Serviceable: true, - Writable: true, - Votable: true, + Name: "leader", + ParticipatesInQuorum: true, + UpdatePriority: 5, }, { - Name: "follower", - Serviceable: true, - Writable: false, - Votable: true, + Name: "follower", + ParticipatesInQuorum: true, + UpdatePriority: 4, }, }, }, diff --git a/controllers/k8score/event_controller_test.go b/controllers/k8score/event_controller_test.go index 5a4e90c93bd..6d105276a68 100644 --- a/controllers/k8score/event_controller_test.go +++ b/controllers/k8score/event_controller_test.go @@ -143,16 +143,14 @@ var _ = Describe("Event Controller", func() { Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(its), func(tmpITS *workloads.InstanceSet) { tmpITS.Spec.Roles = []workloads.ReplicaRole{ { - Name: "leader", - IsLeader: true, - AccessMode: workloads.ReadWriteMode, - CanVote: true, + Name: "leader", + ParticipatesInQuorum: true, + UpdatePriority: 5, }, { - Name: "follower", - IsLeader: false, - AccessMode: workloads.ReadonlyMode, - CanVote: true, + Name: "follower", + ParticipatesInQuorum: true, + UpdatePriority: 4, }, } })()).Should(Succeed()) diff --git a/controllers/operations/opsrequest_controller_test.go b/controllers/operations/opsrequest_controller_test.go index 9512deebe58..e209c9c9b03 100644 --- a/controllers/operations/opsrequest_controller_test.go +++ b/controllers/operations/opsrequest_controller_test.go @@ -146,7 +146,7 @@ var _ = Describe("OpsRequest Controller", func() { By("mock pods are available and wait for cluster enter running phase") podName := fmt.Sprintf("%s-%s-0", clusterObj.Name, mysqlCompName) pod := testapps.MockInstanceSetPod(&testCtx, nil, clusterObj.Name, mysqlCompName, - podName, "leader", "ReadWrite") + podName, "leader") // the opsRequest will use startTime to check some condition. // if there is no sleep for 1 second, unstable error may occur. @@ -193,7 +193,7 @@ var _ = Describe("OpsRequest Controller", func() { testk8s.MockPodIsTerminating(ctx, testCtx, pod) testk8s.RemovePodFinalizer(ctx, testCtx, pod) testapps.MockInstanceSetPod(&testCtx, nil, clusterObj.Name, mysqlCompName, - pod.Name, "leader", "ReadWrite", scalingCtx.target) + pod.Name, "leader", scalingCtx.target) Expect(testapps.ChangeObj(&testCtx, verticalScalingOpsRequest, func(lopsReq *opsv1alpha1.OpsRequest) { if lopsReq.Annotations == nil { lopsReq.Annotations = map[string]string{} diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml index fe6bec4e932..e9349ebd2aa 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml @@ -8427,9 +8427,9 @@ spec: Enumerate all possible roles assigned to each replica of the Component, influencing its behavior. - A replica can have zero to multiple roles. - KubeBlocks operator determines the roles of each replica by invoking the `lifecycleActions.roleProbe` method. - This action returns a list of roles for each replica, and the returned roles must be predefined in the `roles` field. + A replica can have zero or one role. + KubeBlocks operator determines the role of each replica by invoking the `lifecycleActions.roleProbe` method. + This action returns the role for each replica, and the returned role must be predefined here. The roles assigned to a replica can influence various aspects of the Component's behavior, such as: @@ -8443,49 +8443,65 @@ spec: This field is immutable. items: - description: ReplicaRole represents a role that can be assumed by - a component instance. + description: ReplicaRole represents a role that can be assigned + to a component instance, defining its behavior and responsibilities. properties: name: description: |- - Defines the role's identifier. It is used to set the "apps.kubeblocks.io/role" label value - on the corresponding object. + Name defines the role's unique identifier. This value is used to set the "apps.kubeblocks.io/role" label + on the corresponding object to identify its role. + + + For example, common role names include: + - "leader": The primary/master instance that handles write operations + - "follower": Secondary/replica instances that replicate data from the leader + - "learner": Read-only instances that don't participate in elections This field is immutable once set. maxLength: 32 pattern: ^.*[^\s]+.*$ type: string - serviceable: + participatesInQuorum: default: false description: |- - Indicates whether a replica assigned this role is capable of providing services. + ParticipatesInQuorum indicates if pods with this role are counted when determining quorum. + This affects update strategies that need to maintain quorum for availability. Roles participate + in quorum should have higher update priority than roles do not participate in quorum. + The default value is false. - This field is immutable once set. - type: boolean - votable: - default: false - description: |- - Specifies whether a replica with this role has voting rights. - In distributed systems, this typically means the replica can participate in consensus decisions, - configuration changes, or other processes that require a quorum. + For example, in a 5-pod component where: + - 2 learner pods (participatesInQuorum=false) + - 2 follower pods (participatesInQuorum=true) + - 1 leader pod (participatesInQuorum=true) + The quorum size would be 3 (based on the 3 participating pods), allowing parallel updates + of 2 learners and 1 follower while maintaining quorum. This field is immutable once set. type: boolean - writable: - default: false + updatePriority: + default: 0 description: |- - Determines if a replica in this role has the authority to perform write operations. - A writable replica can modify data, handle update operations. + UpdatePriority determines the order in which pods with different roles are updated. + Pods are sorted by this priority (higher numbers = higher priority) and updated accordingly. + Roles with the highest priority will be updated last. + The default priority is 0. + + + For example: + - Leader role may have priority 2 (updated last) + - Follower role may have priority 1 (updated before leader) + - Learner role may have priority 0 (updated first) This field is immutable once set. - type: boolean + type: integer required: - name type: object + maxItems: 128 type: array runtime: description: |- diff --git a/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml b/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml index d20170e3eca..9a14f500498 100644 --- a/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml +++ b/deploy/helm/crds/workloads.kubeblocks.io_instancesets.yaml @@ -4098,29 +4098,62 @@ spec: description: A list of roles defined in the system. Instanceset obtains role through pods' role label `kubeblocks.io/role`. items: + description: ReplicaRole represents a role that can be assigned + to a component instance, defining its behavior and responsibilities. properties: - accessMode: - default: ReadWrite - description: Specifies the service capabilities of this member. - enum: - - None - - Readonly - - ReadWrite + name: + description: |- + Name defines the role's unique identifier. This value is used to set the "apps.kubeblocks.io/role" label + on the corresponding object to identify its role. + + + For example, common role names include: + - "leader": The primary/master instance that handles write operations + - "follower": Secondary/replica instances that replicate data from the leader + - "learner": Read-only instances that don't participate in elections + + + This field is immutable once set. + maxLength: 32 + pattern: ^.*[^\s]+.*$ type: string - canVote: - default: true - description: Indicates if this member has voting rights. - type: boolean - isLeader: + participatesInQuorum: default: false - description: Determines if this member is the leader. + description: |- + ParticipatesInQuorum indicates if pods with this role are counted when determining quorum. + This affects update strategies that need to maintain quorum for availability. Roles participate + in quorum should have higher update priority than roles do not participate in quorum. + The default value is false. + + + For example, in a 5-pod component where: + - 2 learner pods (participatesInQuorum=false) + - 2 follower pods (participatesInQuorum=true) + - 1 leader pod (participatesInQuorum=true) + The quorum size would be 3 (based on the 3 participating pods), allowing parallel updates + of 2 learners and 1 follower while maintaining quorum. + + + This field is immutable once set. type: boolean - name: - default: leader - description: Defines the role name of the replica. - type: string + updatePriority: + default: 0 + description: |- + UpdatePriority determines the order in which pods with different roles are updated. + Pods are sorted by this priority (higher numbers = higher priority) and updated accordingly. + Roles with the highest priority will be updated last. + The default priority is 0. + + + For example: + - Leader role may have priority 2 (updated last) + - Follower role may have priority 1 (updated before leader) + - Learner role may have priority 0 (updated first) + + + This field is immutable once set. + type: integer required: - - accessMode - name type: object type: array @@ -12277,29 +12310,59 @@ spec: role: description: Defines the role of the replica in the cluster. properties: - accessMode: - default: ReadWrite - description: Specifies the service capabilities of this - member. - enum: - - None - - Readonly - - ReadWrite + name: + description: |- + Name defines the role's unique identifier. This value is used to set the "apps.kubeblocks.io/role" label + on the corresponding object to identify its role. + + + For example, common role names include: + - "leader": The primary/master instance that handles write operations + - "follower": Secondary/replica instances that replicate data from the leader + - "learner": Read-only instances that don't participate in elections + + + This field is immutable once set. + maxLength: 32 + pattern: ^.*[^\s]+.*$ type: string - canVote: - default: true - description: Indicates if this member has voting rights. - type: boolean - isLeader: + participatesInQuorum: default: false - description: Determines if this member is the leader. + description: |- + ParticipatesInQuorum indicates if pods with this role are counted when determining quorum. + This affects update strategies that need to maintain quorum for availability. Roles participate + in quorum should have higher update priority than roles do not participate in quorum. + The default value is false. + + + For example, in a 5-pod component where: + - 2 learner pods (participatesInQuorum=false) + - 2 follower pods (participatesInQuorum=true) + - 1 leader pod (participatesInQuorum=true) + The quorum size would be 3 (based on the 3 participating pods), allowing parallel updates + of 2 learners and 1 follower while maintaining quorum. + + + This field is immutable once set. type: boolean - name: - default: leader - description: Defines the role name of the replica. - type: string + updatePriority: + default: 0 + description: |- + UpdatePriority determines the order in which pods with different roles are updated. + Pods are sorted by this priority (higher numbers = higher priority) and updated accordingly. + Roles with the highest priority will be updated last. + The default priority is 0. + + + For example: + - Leader role may have priority 2 (updated last) + - Follower role may have priority 1 (updated before leader) + - Learner role may have priority 0 (updated first) + + + This field is immutable once set. + type: integer required: - - accessMode - name type: object required: @@ -12324,10 +12387,6 @@ spec: this InstanceSet with a Ready Condition. format: int32 type: integer - readyWithoutPrimary: - description: Indicates whether it is required for the InstanceSet - to have at least one primary instance ready. - type: boolean replicas: description: replicas is the number of instances created by the InstanceSet controller. diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index 1fb5b6f1892..a1df0105db3 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -1385,9 +1385,9 @@ ComponentAvailable
Enumerate all possible roles assigned to each replica of the Component, influencing its behavior.
-A replica can have zero to multiple roles.
-KubeBlocks operator determines the roles of each replica by invoking the lifecycleActions.roleProbe
method.
-This action returns a list of roles for each replica, and the returned roles must be predefined in the roles
field.
A replica can have zero or one role.
+KubeBlocks operator determines the role of each replica by invoking the lifecycleActions.roleProbe
method.
+This action returns the role for each replica, and the returned role must be predefined here.
The roles assigned to a replica can influence various aspects of the Component’s behavior, such as:
roleSelector
.Enumerate all possible roles assigned to each replica of the Component, influencing its behavior.
-A replica can have zero to multiple roles.
-KubeBlocks operator determines the roles of each replica by invoking the lifecycleActions.roleProbe
method.
-This action returns a list of roles for each replica, and the returned roles must be predefined in the roles
field.
A replica can have zero or one role.
+KubeBlocks operator determines the role of each replica by invoking the lifecycleActions.roleProbe
method.
+This action returns the role for each replica, and the returned role must be predefined here.
The roles assigned to a replica can influence various aspects of the Component’s behavior, such as:
roleSelector
.-(Appears on:ComponentDefinitionSpec) +(Appears on:ComponentDefinitionSpec, InstanceSetSpec, MemberStatus)
ReplicaRole represents a role that can be assumed by a component instance.
+ReplicaRole represents a role that can be assigned to a component instance, defining its behavior and responsibilities.
- Defines the role’s identifier. It is used to set the “apps.kubeblocks.io/role” label value -on the corresponding object. -This field is immutable once set. - |
-
-|
-serviceable - -bool - - |
-
-(Optional)
- Indicates whether a replica assigned this role is capable of providing services. +Name defines the role’s unique identifier. This value is used to set the “apps.kubeblocks.io/role” label +on the corresponding object to identify its role. +For example, common role names include: +- “leader”: The primary/master instance that handles write operations +- “follower”: Secondary/replica instances that replicate data from the leader +- “learner”: Read-only instances that don’t participate in elections This field is immutable once set. |
-writable + updatePriority -bool +int |
(Optional)
- Determines if a replica in this role has the authority to perform write operations. -A writable replica can modify data, handle update operations. +UpdatePriority determines the order in which pods with different roles are updated. +Pods are sorted by this priority (higher numbers = higher priority) and updated accordingly. +Roles with the highest priority will be updated last. +The default priority is 0. +For example: +- Leader role may have priority 2 (updated last) +- Follower role may have priority 1 (updated before leader) +- Learner role may have priority 0 (updated first) This field is immutable once set. |
-votable + participatesInQuorum bool |
(Optional)
- Specifies whether a replica with this role has voting rights. -In distributed systems, this typically means the replica can participate in consensus decisions, -configuration changes, or other processes that require a quorum. +ParticipatesInQuorum indicates if pods with this role are counted when determining quorum. +This affects update strategies that need to maintain quorum for availability. Roles participate +in quorum should have higher update priority than roles do not participate in quorum. +The default value is false. +For example, in a 5-pod component where: +- 2 learner pods (participatesInQuorum=false) +- 2 follower pods (participatesInQuorum=true) +- 1 leader pod (participatesInQuorum=true) +The quorum size would be 3 (based on the 3 participating pods), allowing parallel updates +of 2 learners and 1 follower while maintaining quorum. This field is immutable once set. |
roles - + []ReplicaRole @@ -29210,9 +29214,6 @@ InstanceSetStatus |
string
alias)-(Appears on:ReplicaRole) -
AccessMode defines SVC access mode enums.
roles
readyWithoutPrimary
Indicates whether it is required for the InstanceSet to have at least one primary instance ready.
-currentRevisions
role
-(Appears on:InstanceSetSpec, MemberStatus) -
-Field | -Description | -
---|---|
-name - -string - - |
-
- Defines the role name of the replica. - |
-
-accessMode - - -AccessMode - - - |
-
- Specifies the service capabilities of this member. - |
-
-canVote - -bool - - |
-
-(Optional)
- Indicates if this member has voting rights. - |
-
-isLeader - -bool - - |
-
-(Optional)
- Determines if this member is the leader. - |
-
string
alias)