From 57b397676771e574680e4761e7d167bee5085768 Mon Sep 17 00:00:00 2001 From: Arvind Thirumurugan Date: Tue, 28 Oct 2025 15:25:38 -0700 Subject: [PATCH 1/4] interface: add maxConcurrency for StageConfig in UpdateRun Signed-off-by: Arvind Thirumurugan --- apis/placement/v1beta1/stageupdate_types.go | 12 ++++++++++++ apis/placement/v1beta1/zz_generated.deepcopy.go | 7 ++++++- ...kubernetes-fleet.io_clusterstagedupdateruns.yaml | 13 +++++++++++++ ...etes-fleet.io_clusterstagedupdatestrategies.yaml | 13 +++++++++++++ ...cement.kubernetes-fleet.io_stagedupdateruns.yaml | 13 +++++++++++++ ....kubernetes-fleet.io_stagedupdatestrategies.yaml | 13 +++++++++++++ 6 files changed, 70 insertions(+), 1 deletion(-) diff --git a/apis/placement/v1beta1/stageupdate_types.go b/apis/placement/v1beta1/stageupdate_types.go index fb8d0b8fe..7040b4863 100644 --- a/apis/placement/v1beta1/stageupdate_types.go +++ b/apis/placement/v1beta1/stageupdate_types.go @@ -19,6 +19,7 @@ package v1beta1 import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/kubefleet-dev/kubefleet/apis" @@ -275,6 +276,17 @@ type StageConfig struct { // +kubebuilder:validation:Optional SortingLabelKey *string `json:"sortingLabelKey,omitempty"` + // MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage. + // Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%). + // Absolute number is calculated from percentage by rounding up. + // If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1). + // Defaults to 1. + // +kubebuilder:default=1 + // +kubebuilder:validation:XIntOrString + // +kubebuilder:validation:Pattern="^((100|[0-9]{1,2})%|[0-9]+)$" + // +kubebuilder:validation:Optional + MaxConcurrency *intstr.IntOrString `json:"maxConcurrency,omitempty"` + // The collection of tasks that each stage needs to complete successfully before moving to the next stage. // Each task is executed in parallel and there cannot be more than one task of the same type. // +kubebuilder:validation:MaxItems=2 diff --git a/apis/placement/v1beta1/zz_generated.deepcopy.go b/apis/placement/v1beta1/zz_generated.deepcopy.go index 487ead5f1..5bb78a856 100644 --- a/apis/placement/v1beta1/zz_generated.deepcopy.go +++ b/apis/placement/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -2741,6 +2741,11 @@ func (in *StageConfig) DeepCopyInto(out *StageConfig) { *out = new(string) **out = **in } + if in.MaxConcurrency != nil { + in, out := &in.MaxConcurrency, &out.MaxConcurrency + *out = new(intstr.IntOrString) + **out = **in + } if in.AfterStageTasks != nil { in, out := &in.AfterStageTasks, &out.AfterStageTasks *out = make([]AfterStageTask, len(*in)) diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml index 4b83c14ae..ce85c4db0 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml @@ -1871,6 +1871,19 @@ spec: type: object type: object x-kubernetes-map-type: atomic + maxConcurrency: + anyOf: + - type: integer + - type: string + default: 1 + description: |- + MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage. + Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%). + Absolute number is calculated from percentage by rounding up. + If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1). + Defaults to 1. + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true name: description: The name of the stage. This MUST be unique within the same StagedUpdateStrategy. diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml index 338ad2aad..d6eaaaba7 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml @@ -273,6 +273,19 @@ spec: type: object type: object x-kubernetes-map-type: atomic + maxConcurrency: + anyOf: + - type: integer + - type: string + default: 1 + description: |- + MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage. + Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%). + Absolute number is calculated from percentage by rounding up. + If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1). + Defaults to 1. + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true name: description: The name of the stage. This MUST be unique within the same StagedUpdateStrategy. diff --git a/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml index 32462ceaa..e4ba4676e 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml @@ -790,6 +790,19 @@ spec: type: object type: object x-kubernetes-map-type: atomic + maxConcurrency: + anyOf: + - type: integer + - type: string + default: 1 + description: |- + MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage. + Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%). + Absolute number is calculated from percentage by rounding up. + If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1). + Defaults to 1. + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true name: description: The name of the stage. This MUST be unique within the same StagedUpdateStrategy. diff --git a/config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml index 4e6d5fe02..589e536c0 100644 --- a/config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml +++ b/config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml @@ -135,6 +135,19 @@ spec: type: object type: object x-kubernetes-map-type: atomic + maxConcurrency: + anyOf: + - type: integer + - type: string + default: 1 + description: |- + MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage. + Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%). + Absolute number is calculated from percentage by rounding up. + If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1). + Defaults to 1. + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true name: description: The name of the stage. This MUST be unique within the same StagedUpdateStrategy. From 4e5999d5b0072190d6f89ad26831095eff8f3e15 Mon Sep 17 00:00:00 2001 From: Arvind Thirumurugan Date: Tue, 28 Oct 2025 17:57:07 -0700 Subject: [PATCH 2/4] fix IT Signed-off-by: Arvind Thirumurugan --- pkg/controllers/updaterun/validation_integration_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/controllers/updaterun/validation_integration_test.go b/pkg/controllers/updaterun/validation_integration_test.go index d4f9f0658..345d6c70f 100644 --- a/pkg/controllers/updaterun/validation_integration_test.go +++ b/pkg/controllers/updaterun/validation_integration_test.go @@ -27,6 +27,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/util/intstr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -303,6 +304,10 @@ var _ = Describe("UpdateRun validation tests", func() { "region": "no-exist", }, }, + MaxConcurrency: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, }) Expect(k8sClient.Status().Update(ctx, updateRun)).Should(Succeed()) @@ -316,6 +321,10 @@ var _ = Describe("UpdateRun validation tests", func() { "region": "no-exist", }, }, + MaxConcurrency: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 1, + }, }) validateClusterStagedUpdateRunStatus(ctx, updateRun, wantStatus, "the number of stages in the updateRun has changed") }) From 62c6d26aa92a9978258c5da38f2e72edf47e2ba9 Mon Sep 17 00:00:00 2001 From: Arvind Thirumurugan Date: Tue, 28 Oct 2025 18:07:52 -0700 Subject: [PATCH 3/4] lint fix Signed-off-by: Arvind Thirumurugan --- pkg/controllers/updaterun/validation_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controllers/updaterun/validation_integration_test.go b/pkg/controllers/updaterun/validation_integration_test.go index 345d6c70f..de1cd71e8 100644 --- a/pkg/controllers/updaterun/validation_integration_test.go +++ b/pkg/controllers/updaterun/validation_integration_test.go @@ -27,9 +27,9 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/util/intstr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" From 267b5c7cc525ca76acd3448a80bc9cb91d59e0cd Mon Sep 17 00:00:00 2001 From: Arvind Thirumurugan Date: Thu, 30 Oct 2025 11:16:45 -0700 Subject: [PATCH 4/4] address comment Signed-off-by: Arvind Thirumurugan --- apis/placement/v1beta1/stageupdate_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apis/placement/v1beta1/stageupdate_types.go b/apis/placement/v1beta1/stageupdate_types.go index 7040b4863..67cca7e10 100644 --- a/apis/placement/v1beta1/stageupdate_types.go +++ b/apis/placement/v1beta1/stageupdate_types.go @@ -278,7 +278,7 @@ type StageConfig struct { // MaxConcurrency specifies the maximum number of clusters that can be updated concurrently within this stage. // Value can be an absolute number (ex: 5) or a percentage of the total clusters in the stage (ex: 50%). - // Absolute number is calculated from percentage by rounding up. + // Fractional results are rounded down. A minimum of 1 update is enforced. // If not specified, all clusters in the stage are updated sequentially (effectively maxConcurrency = 1). // Defaults to 1. // +kubebuilder:default=1