@@ -27,6 +27,7 @@ import (
2727 "k8s.io/apimachinery/pkg/api/meta"
2828 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929 "k8s.io/apimachinery/pkg/types"
30+ "k8s.io/apimachinery/pkg/util/intstr"
3031 "k8s.io/utils/ptr"
3132 "sigs.k8s.io/controller-runtime/pkg/client"
3233
@@ -1101,6 +1102,94 @@ var _ = Describe("test RP rollout with staged update run", Label("resourceplacem
11011102 }
11021103 })
11031104 })
1105+
1106+ Context ("Test parallel cluster updates with maxConcurrency set to 3" , Ordered , func () {
1107+ var strategy * placementv1beta1.StagedUpdateStrategy
1108+ updateRunName := fmt .Sprintf (stagedUpdateRunNameWithSubIndexTemplate , GinkgoParallelProcess (), 0 )
1109+
1110+ BeforeAll (func () {
1111+ // Create the RP with external rollout strategy.
1112+ rp := & placementv1beta1.ResourcePlacement {
1113+ ObjectMeta : metav1.ObjectMeta {
1114+ Name : rpName ,
1115+ Namespace : testNamespace ,
1116+ // Add a custom finalizer; this would allow us to better observe
1117+ // the behavior of the controllers.
1118+ Finalizers : []string {customDeletionBlockerFinalizer },
1119+ },
1120+ Spec : placementv1beta1.PlacementSpec {
1121+ ResourceSelectors : configMapSelector (),
1122+ Strategy : placementv1beta1.RolloutStrategy {
1123+ Type : placementv1beta1 .ExternalRolloutStrategyType ,
1124+ },
1125+ },
1126+ }
1127+ Expect (hubClient .Create (ctx , rp )).To (Succeed (), "Failed to create RP" )
1128+
1129+ // Create a strategy with a single stage selecting all 3 clusters with maxConcurrency=3
1130+ strategy = & placementv1beta1.StagedUpdateStrategy {
1131+ ObjectMeta : metav1.ObjectMeta {
1132+ Name : strategyName ,
1133+ Namespace : testNamespace ,
1134+ },
1135+ Spec : placementv1beta1.UpdateStrategySpec {
1136+ Stages : []placementv1beta1.StageConfig {
1137+ {
1138+ Name : "parallel" ,
1139+ // Pick all clusters in a single stage
1140+ LabelSelector : & metav1.LabelSelector {},
1141+ MaxConcurrency : & intstr.IntOrString {Type : intstr .Int , IntVal : 3 },
1142+ },
1143+ },
1144+ },
1145+ }
1146+ Expect (hubClient .Create (ctx , strategy )).To (Succeed (), "Failed to create StagedUpdateStrategy with maxConcurrency=3" )
1147+ })
1148+
1149+ AfterAll (func () {
1150+ // Remove the custom deletion blocker finalizer from the RP.
1151+ ensureRPAndRelatedResourcesDeleted (types.NamespacedName {Name : rpName , Namespace : testNamespace }, allMemberClusters )
1152+
1153+ // Delete the stagedUpdateRun.
1154+ ensureStagedUpdateRunDeletion (updateRunName , testNamespace )
1155+
1156+ // Delete the stagedUpdateStrategy.
1157+ ensureStagedUpdateRunStrategyDeletion (strategyName , testNamespace )
1158+ })
1159+
1160+ It ("Should not rollout any resources to member clusters as there's no update run yet" , checkIfRemovedConfigMapFromAllMemberClustersConsistently )
1161+
1162+ It ("Should have the latest resource snapshot" , func () {
1163+ validateLatestResourceSnapshot (rpName , testNamespace , resourceSnapshotIndex1st )
1164+ })
1165+
1166+ It ("Should successfully schedule the rp" , func () {
1167+ validateLatestSchedulingPolicySnapshot (rpName , testNamespace , policySnapshotIndex1st , 3 )
1168+ })
1169+
1170+ It ("Should update rp status as pending rollout" , func () {
1171+ rpStatusUpdatedActual := rpStatusWithExternalStrategyActual (nil , "" , false , allMemberClusterNames , []string {"" , "" , "" }, []bool {false , false , false }, nil , nil )
1172+ Eventually (rpStatusUpdatedActual , eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to update RP %s/%s status as expected" , testNamespace , rpName )
1173+ })
1174+
1175+ It ("Should create a staged update run successfully" , func () {
1176+ createStagedUpdateRunSucceed (updateRunName , testNamespace , rpName , resourceSnapshotIndex1st , strategyName )
1177+ })
1178+
1179+ It ("Should complete the staged update run with all 3 clusters updated in parallel" , func () {
1180+ // With maxConcurrency=3, all 3 clusters should be updated in parallel.
1181+ // Each cluster waits 15 seconds, so total time should be under 20s.
1182+ surSucceededActual := stagedUpdateRunStatusSucceededActual (updateRunName , testNamespace , policySnapshotIndex1st , len (allMemberClusters ), defaultApplyStrategy , & strategy .Spec , [][]string {{allMemberClusterNames [0 ], allMemberClusterNames [1 ], allMemberClusterNames [2 ]}}, nil , nil , nil )
1183+ Eventually (surSucceededActual , updateRunParallelEventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to validate updateRun %s/%s succeeded" , testNamespace , updateRunName )
1184+ checkIfPlacedWorkResourcesOnMemberClustersInUpdateRun (allMemberClusters )
1185+ })
1186+
1187+ It ("Should update rp status as completed" , func () {
1188+ rpStatusUpdatedActual := rpStatusWithExternalStrategyActual (appConfigMapIdentifiers (), resourceSnapshotIndex1st , true , allMemberClusterNames ,
1189+ []string {resourceSnapshotIndex1st , resourceSnapshotIndex1st , resourceSnapshotIndex1st }, []bool {true , true , true }, nil , nil )
1190+ Eventually (rpStatusUpdatedActual , eventuallyDuration , eventuallyInterval ).Should (Succeed (), "Failed to update RP %s/%s status as expected" , testNamespace , rpName )
1191+ })
1192+ })
11041193})
11051194
11061195func createStagedUpdateStrategySucceed (strategyName , namespace string ) * placementv1beta1.StagedUpdateStrategy {
0 commit comments