From 444f21c564a67dd212865e72b068c5d3e9c8f78c Mon Sep 17 00:00:00 2001 From: Liming Liu Date: Tue, 5 Dec 2023 16:35:26 +0000 Subject: [PATCH] add support for the traffic weight > 100. --- pkg/apis/rollouts/v1alpha1/types.go | 2 + pkg/apis/rollouts/validation/validation.go | 11 +- .../rollouts/validation/validation_test.go | 2 +- .../info/rollout_info.go | 1 + rollout/trafficrouting.go | 14 +- rollout/trafficrouting/nginx/nginx.go | 5 +- utils/replicaset/canary.go | 52 +++--- utils/replicaset/canary_test.go | 149 +++++++++--------- utils/weightutil/weight.go | 13 ++ 9 files changed, 142 insertions(+), 107 deletions(-) create mode 100644 utils/weightutil/weight.go diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index e3c425cb0b..9aebf82208 100755 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -379,6 +379,8 @@ type RolloutTrafficRouting struct { ManagedRoutes []MangedRoutes `json:"managedRoutes,omitempty" protobuf:"bytes,8,rep,name=managedRoutes"` // Apisix holds specific configuration to use Apisix to route traffic Apisix *ApisixTrafficRouting `json:"apisix,omitempty" protobuf:"bytes,9,opt,name=apisix"` + + MaxTrafficWeight *int32 `json:"maxTrafficWeight,omitempty" protobuf:"varint,10,opt,name=maxTrafficWeight"` // +kubebuilder:validation:Schemaless // +kubebuilder:pruning:PreserveUnknownFields // +kubebuilder:validation:Type=object diff --git a/pkg/apis/rollouts/validation/validation.go b/pkg/apis/rollouts/validation/validation.go index 066c13038a..9d180d8134 100644 --- a/pkg/apis/rollouts/validation/validation.go +++ b/pkg/apis/rollouts/validation/validation.go @@ -28,7 +28,7 @@ const ( // MissingFieldMessage the message to indicate rollout is missing a field MissingFieldMessage = "Rollout has missing field '%s'" // InvalidSetWeightMessage indicates the setweight value needs to be between 0 and 100 - InvalidSetWeightMessage = "SetWeight needs to be between 0 and 100" + InvalidSetWeightMessage = "SetWeight needs to be between 0 and %d" // InvalidCanaryExperimentTemplateWeightWithoutTrafficRouting indicates experiment weight cannot be set without trafficRouting InvalidCanaryExperimentTemplateWeightWithoutTrafficRouting = "Experiment template weight cannot be set unless TrafficRouting is enabled" // InvalidSetCanaryScaleTrafficPolicy indicates that TrafficRouting, required for SetCanaryScale, is missing @@ -292,8 +292,13 @@ func ValidateRolloutStrategyCanary(rollout *v1alpha1.Rollout, fldPath *field.Pat step.Experiment == nil, step.Pause == nil, step.SetWeight == nil, step.Analysis == nil, step.SetCanaryScale == nil, step.SetHeaderRoute == nil, step.SetMirrorRoute == nil) allErrs = append(allErrs, field.Invalid(stepFldPath, errVal, InvalidStepMessage)) } - if step.SetWeight != nil && (*step.SetWeight < 0 || *step.SetWeight > 100) { - allErrs = append(allErrs, field.Invalid(stepFldPath.Child("setWeight"), *canary.Steps[i].SetWeight, InvalidSetWeightMessage)) + // use the one defined in the traffic spec + maxTrafficWeight := int32(100) + if canary.TrafficRouting != nil && canary.TrafficRouting.MaxTrafficWeight != nil { + maxTrafficWeight = *canary.TrafficRouting.MaxTrafficWeight + } + if step.SetWeight != nil && (*step.SetWeight < 0 || *step.SetWeight > maxTrafficWeight) { + allErrs = append(allErrs, field.Invalid(stepFldPath.Child("setWeight"), *canary.Steps[i].SetWeight, fmt.Sprintf(InvalidSetWeightMessage, maxTrafficWeight))) } if step.Pause != nil && step.Pause.DurationSeconds() < 0 { allErrs = append(allErrs, field.Invalid(stepFldPath.Child("pause").Child("duration"), step.Pause.DurationSeconds(), InvalidDurationMessage)) diff --git a/pkg/apis/rollouts/validation/validation_test.go b/pkg/apis/rollouts/validation/validation_test.go index 58a4571ae7..cf724e204a 100644 --- a/pkg/apis/rollouts/validation/validation_test.go +++ b/pkg/apis/rollouts/validation/validation_test.go @@ -239,7 +239,7 @@ func TestValidateRolloutStrategyCanary(t *testing.T) { invalidRo := ro.DeepCopy() invalidRo.Spec.Strategy.Canary.Steps[0].SetWeight = &setWeight allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) - assert.Equal(t, InvalidSetWeightMessage, allErrs[0].Detail) + assert.Equal(t, fmt.Sprintf(InvalidSetWeightMessage, 100), allErrs[0].Detail) }) t.Run("invalid duration set in paused step", func(t *testing.T) { diff --git a/pkg/kubectl-argo-rollouts/info/rollout_info.go b/pkg/kubectl-argo-rollouts/info/rollout_info.go index 8afc8cf02f..aa51dc6261 100644 --- a/pkg/kubectl-argo-rollouts/info/rollout_info.go +++ b/pkg/kubectl-argo-rollouts/info/rollout_info.go @@ -63,6 +63,7 @@ func NewRolloutInfo( if ro.Spec.Strategy.Canary.TrafficRouting == nil { for _, rs := range roInfo.ReplicaSets { if rs.Canary { + // use the max traffic weight in the spec instead of 100 roInfo.ActualWeight = fmt.Sprintf("%d", (rs.Available*100)/ro.Status.AvailableReplicas) } } diff --git a/rollout/trafficrouting.go b/rollout/trafficrouting.go index a87e31a9e8..09716d6138 100644 --- a/rollout/trafficrouting.go +++ b/rollout/trafficrouting.go @@ -26,6 +26,7 @@ import ( "github.com/argoproj/argo-rollouts/utils/record" replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" rolloututil "github.com/argoproj/argo-rollouts/utils/rollout" + "github.com/argoproj/argo-rollouts/utils/weightutil" ) // NewTrafficRoutingReconciler identifies return the TrafficRouting Plugin that the rollout wants to modify @@ -132,6 +133,7 @@ func (c *Controller) NewTrafficRoutingReconciler(roCtx *rolloutContext) ([]traff return nil, nil } +// this currently only be used in the canary strategy func (c *rolloutContext) reconcileTrafficRouting() error { reconcilers, err := c.newTrafficRoutingReconciler(c) // a return here does ensure that all trafficReconcilers are healthy @@ -199,7 +201,8 @@ func (c *rolloutContext) reconcileTrafficRouting() error { // But we can only increase canary weight according to available replica counts of the canary. // we will need to set the desiredWeight to 0 when the newRS is not available. if c.rollout.Spec.Strategy.Canary.DynamicStableScale { - desiredWeight = (100 * c.newRS.Status.AvailableReplicas) / *c.rollout.Spec.Replicas + // TODO: handle total weight + desiredWeight = (weightutil.MaxTrafficWeight(c.rollout) * c.newRS.Status.AvailableReplicas) / *c.rollout.Spec.Replicas } else if c.rollout.Status.Canary.Weights != nil { desiredWeight = c.rollout.Status.Canary.Weights.Canary.Weight } @@ -227,7 +230,7 @@ func (c *rolloutContext) reconcileTrafficRouting() error { desiredWeight = replicasetutil.GetCurrentSetWeight(c.rollout) weightDestinations = append(weightDestinations, c.calculateWeightDestinationsFromExperiment()...) } else { - desiredWeight = 100 + desiredWeight = weightutil.MaxTrafficWeight(c.rollout) } } // We need to check for revision > 1 because when we first install the rollout we run step 0 this prevents that. @@ -301,7 +304,9 @@ func (c *rolloutContext) calculateDesiredWeightOnAbortOrStableRollback() int32 { } // When using dynamic stable scaling, we must dynamically decreasing the weight to the canary // according to the availability of the stable (whatever it can support). - desiredWeight := 100 - ((100 * c.stableRS.Status.AvailableReplicas) / *c.rollout.Spec.Replicas) + // TODO: handle total weight + // c.rollout.Spec.Strategy.Canary.TrafficRouting.MaxTrafficWeight + desiredWeight := weightutil.MaxTrafficWeight(c.rollout) - ((weightutil.MaxTrafficWeight(c.rollout) * c.stableRS.Status.AvailableReplicas) / *c.rollout.Spec.Replicas) if c.rollout.Status.Canary.Weights != nil { // This ensures that if we are already at a lower weight, then we will not // increase the weight because stable availability is flapping (e.g. pod restarts) @@ -334,7 +339,8 @@ func calculateWeightStatus(ro *v1alpha1.Rollout, canaryHash, stableHash string, ServiceName: ro.Spec.Strategy.Canary.CanaryService, }, } - stableWeight := 100 - desiredWeight + // TODO: handle total weight + stableWeight := weightutil.MaxTrafficWeight(ro) - desiredWeight for _, weightDest := range weightDestinations { weights.Additional = append(weights.Additional, weightDest) stableWeight -= weightDest.Weight diff --git a/rollout/trafficrouting/nginx/nginx.go b/rollout/trafficrouting/nginx/nginx.go index 3d56c55f6a..d7a39be2cc 100644 --- a/rollout/trafficrouting/nginx/nginx.go +++ b/rollout/trafficrouting/nginx/nginx.go @@ -208,7 +208,10 @@ func (r *Reconciler) buildLegacyCanaryIngress(stableIngress *extensionsv1beta1.I // Always set `canary` and `canary-weight` - `canary-by-header` and `canary-by-cookie`, if set, will always take precedence desiredCanaryIngress.Annotations[fmt.Sprintf("%s/canary", annotationPrefix)] = "true" desiredCanaryIngress.Annotations[fmt.Sprintf("%s/canary-weight", annotationPrefix)] = fmt.Sprintf("%d", desiredWeight) - + if r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.MaxTrafficWeight != nil { + weightTotal := *r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.MaxTrafficWeight + desiredCanaryIngress.Annotations[fmt.Sprintf("%s/canary-weight-total", annotationPrefix)] = fmt.Sprintf("%d", weightTotal) + } return ingressutil.NewLegacyIngress(desiredCanaryIngress), nil } diff --git a/utils/replicaset/canary.go b/utils/replicaset/canary.go index 40eadb7848..2543b4f836 100755 --- a/utils/replicaset/canary.go +++ b/utils/replicaset/canary.go @@ -10,6 +10,7 @@ import ( "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/utils/defaults" + "github.com/argoproj/argo-rollouts/utils/weightutil" ) const ( @@ -56,11 +57,11 @@ func AtDesiredReplicaCountsForCanary(ro *v1alpha1.Rollout, newRS, stableRS *apps // when using the basic canary strategy. The function calculates the desired number of replicas for // the new and stable RS using the following equations: // -// newRS Replica count = spec.Replica * (setweight / 100) -// stableRS Replica count = spec.Replica * (1 - setweight / 100) +// newRS Replica count = spec.Replica * (setweight / maxweight) +// stableRS Replica count = spec.Replica * (1 - setweight / maxweight) // // In both equations, the function rounds the desired replica count up if the math does not divide into whole numbers -// because the rollout guarantees at least one replica for both the stable and new RS when the setWeight is not 0 or 100. +// because the rollout guarantees at least one replica for both the stable and new RS when the setWeight is not 0 or maxweight. // Then, the function finds the number of replicas it can scale up using the following equation: // // scaleUpCount := (maxSurge + rollout.Spec.Replica) - sum of rollout's RSs spec.Replica @@ -91,8 +92,9 @@ func CalculateReplicaCountsForBasicCanary(rollout *v1alpha1.Rollout, newRS *apps rolloutSpecReplica := defaults.GetReplicasOrDefault(rollout.Spec.Replicas) _, desiredWeight := GetCanaryReplicasOrWeight(rollout) maxSurge := MaxSurge(rollout) + maxWeight := weightutil.MaxTrafficWeight(rollout) - desiredNewRSReplicaCount, desiredStableRSReplicaCount := approximateWeightedCanaryStableReplicaCounts(rolloutSpecReplica, desiredWeight, maxSurge) + desiredNewRSReplicaCount, desiredStableRSReplicaCount := approximateWeightedCanaryStableReplicaCounts(rolloutSpecReplica, desiredWeight, maxWeight, maxSurge) stableRSReplicaCount := int32(0) newRSReplicaCount := int32(0) @@ -180,7 +182,7 @@ func CalculateReplicaCountsForBasicCanary(rollout *v1alpha1.Rollout, newRS *apps // canary/stable replica counts might sum to either spec.replicas or spec.replicas + 1 but will not // exceed spec.replicas if maxSurge is 0. If the canary weight is between 1-99, and spec.replicas is > 1, // we will always return a minimum of 1 for stable and canary as to not return 0. -func approximateWeightedCanaryStableReplicaCounts(specReplicas, desiredWeight, maxSurge int32) (int32, int32) { +func approximateWeightedCanaryStableReplicaCounts(specReplicas, desiredWeight, maxWeight, maxSurge int32) (int32, int32) { if specReplicas == 0 { return 0, 0 } @@ -192,14 +194,14 @@ func approximateWeightedCanaryStableReplicaCounts(specReplicas, desiredWeight, m } var options []canaryOption - ceilWeightedCanaryCount := int32(math.Ceil(float64(specReplicas*desiredWeight) / 100.0)) - floorWeightedCanaryCount := int32(math.Floor(float64(specReplicas*desiredWeight) / 100.0)) + ceilWeightedCanaryCount := int32(math.Ceil(float64(specReplicas*desiredWeight) / float64(maxWeight))) + floorWeightedCanaryCount := int32(math.Floor(float64(specReplicas*desiredWeight) / float64(maxWeight))) - tied := floorCeilingTied(desiredWeight, specReplicas) + tied := floorCeilingTied(desiredWeight, maxWeight, specReplicas) // zeroAllowed indicates if are allowed to return the floored value if it is zero. We don't allow // the value to be zero if when user has a weight from 1-99, and they run 2+ replicas (surge included) - zeroAllowed := desiredWeight == 100 || desiredWeight == 0 || (specReplicas == 1 && maxSurge == 0) + zeroAllowed := desiredWeight == (maxWeight) || desiredWeight == 0 || (specReplicas == 1 && maxSurge == 0) if ceilWeightedCanaryCount < specReplicas || zeroAllowed { options = append(options, canaryOption{ceilWeightedCanaryCount, specReplicas}) @@ -213,7 +215,7 @@ func approximateWeightedCanaryStableReplicaCounts(specReplicas, desiredWeight, m // in order to achieve a closer canary weight if maxSurge > 0 { options = append(options, canaryOption{ceilWeightedCanaryCount, specReplicas + 1}) - surgeIsTied := floorCeilingTied(desiredWeight, specReplicas+1) + surgeIsTied := floorCeilingTied(desiredWeight, maxWeight, specReplicas+1) if !surgeIsTied && (floorWeightedCanaryCount != 0 || zeroAllowed) { options = append(options, canaryOption{floorWeightedCanaryCount, specReplicas + 1}) } @@ -225,10 +227,10 @@ func approximateWeightedCanaryStableReplicaCounts(specReplicas, desiredWeight, m } bestOption := options[0] - bestDelta := weightDelta(desiredWeight, bestOption.canary, bestOption.total) + bestDelta := weightDelta(desiredWeight, maxWeight, bestOption.canary, bestOption.total) for i := 1; i < len(options); i++ { currOption := options[i] - currDelta := weightDelta(desiredWeight, currOption.canary, currOption.total) + currDelta := weightDelta(desiredWeight, maxWeight, currOption.canary, currOption.total) if currDelta < bestDelta { bestOption = currOption bestDelta = currDelta @@ -241,15 +243,15 @@ func approximateWeightedCanaryStableReplicaCounts(specReplicas, desiredWeight, m // For example: replicas: 3, desiredWeight: 50% // A canary count of 1 (33.33%) or 2 (66.66%) are both equidistant from desired weight of 50%. // When this happens, we will pick the larger canary count -func floorCeilingTied(desiredWeight, totalReplicas int32) bool { - _, frac := math.Modf(float64(totalReplicas) * (float64(desiredWeight) / 100)) +func floorCeilingTied(desiredWeight, maxWeight, totalReplicas int32) bool { + _, frac := math.Modf(float64(totalReplicas) * (float64(desiredWeight) / float64(maxWeight))) return frac == 0.5 } // weightDelta calculates the difference that the canary replicas will be from the desired weight // This is used to pick the closest approximation of canary counts. -func weightDelta(desiredWeight, canaryReplicas, totalReplicas int32) float64 { - actualWeight := float64(canaryReplicas*100) / float64(totalReplicas) +func weightDelta(desiredWeight, maxWeight, canaryReplicas, totalReplicas int32) float64 { + actualWeight := float64(canaryReplicas*maxWeight) / float64(totalReplicas) return math.Abs(actualWeight - float64(desiredWeight)) } @@ -337,11 +339,12 @@ func CalculateReplicaCountsForTrafficRoutedCanary(rollout *v1alpha1.Rollout, wei var canaryCount, stableCount int32 rolloutSpecReplica := defaults.GetReplicasOrDefault(rollout.Spec.Replicas) setCanaryScaleReplicas, desiredWeight := GetCanaryReplicasOrWeight(rollout) + maxWeight := weightutil.MaxTrafficWeight(rollout) if setCanaryScaleReplicas != nil { // a canary count was explicitly set canaryCount = *setCanaryScaleReplicas } else { - canaryCount = CheckMinPodsPerReplicaSet(rollout, trafficWeightToReplicas(rolloutSpecReplica, desiredWeight)) + canaryCount = CheckMinPodsPerReplicaSet(rollout, trafficWeightToReplicas(rolloutSpecReplica, desiredWeight, maxWeight)) } if !rollout.Spec.Strategy.Canary.DynamicStableScale { @@ -357,9 +360,10 @@ func CalculateReplicaCountsForTrafficRoutedCanary(rollout *v1alpha1.Rollout, wei // high, until we reduce traffic to it. // Case 2 occurs when we are going from high to low canary weight. In this scenario, // we need to increase the stable scale in preparation for increase of traffic to stable. - stableCount = trafficWeightToReplicas(rolloutSpecReplica, 100-desiredWeight) + // TODO calculate the replica set count from the max traffic weight. + stableCount = trafficWeightToReplicas(rolloutSpecReplica, maxWeight-desiredWeight, maxWeight) if weights != nil { - actualStableWeightReplicaCount := trafficWeightToReplicas(rolloutSpecReplica, weights.Stable.Weight) + actualStableWeightReplicaCount := trafficWeightToReplicas(rolloutSpecReplica, weights.Stable.Weight, maxWeight) stableCount = max(stableCount, actualStableWeightReplicaCount) if rollout.Status.Abort { @@ -368,7 +372,7 @@ func CalculateReplicaCountsForTrafficRoutedCanary(rollout *v1alpha1.Rollout, wei // 1. actual canary traffic weight // 2. desired canary traffic weight // This if block makes sure we don't scale down the canary prematurely - trafficWeightReplicaCount := trafficWeightToReplicas(rolloutSpecReplica, weights.Canary.Weight) + trafficWeightReplicaCount := trafficWeightToReplicas(rolloutSpecReplica, weights.Canary.Weight, maxWeight) canaryCount = max(trafficWeightReplicaCount, canaryCount) } } @@ -377,8 +381,8 @@ func CalculateReplicaCountsForTrafficRoutedCanary(rollout *v1alpha1.Rollout, wei // trafficWeightToReplicas returns the appropriate replicas given the full spec.replicas and a weight // Rounds up if not evenly divisible. -func trafficWeightToReplicas(replicas, weight int32) int32 { - return int32(math.Ceil(float64(weight*replicas) / 100)) +func trafficWeightToReplicas(replicas, weight, maxWeight int32) int32 { + return int32(math.Ceil(float64(weight*replicas) / float64(maxWeight))) } func max(left, right int32) int32 { @@ -459,7 +463,7 @@ func GetCurrentCanaryStep(rollout *v1alpha1.Rollout) (*v1alpha1.CanaryStep, *int // GetCanaryReplicasOrWeight either returns a static set of replicas or a weight percentage func GetCanaryReplicasOrWeight(rollout *v1alpha1.Rollout) (*int32, int32) { if rollout.Status.PromoteFull || rollout.Status.StableRS == "" || rollout.Status.CurrentPodHash == rollout.Status.StableRS { - return nil, 100 + return nil, weightutil.MaxTrafficWeight(rollout) } if scs := UseSetCanaryScale(rollout); scs != nil { if scs.Replicas != nil { @@ -480,7 +484,7 @@ func GetCurrentSetWeight(rollout *v1alpha1.Rollout) int32 { } currentStep, currentStepIndex := GetCurrentCanaryStep(rollout) if currentStep == nil { - return 100 + return weightutil.MaxTrafficWeight(rollout) } for i := *currentStepIndex; i >= 0; i-- { diff --git a/utils/replicaset/canary_test.go b/utils/replicaset/canary_test.go index 375ca43ce4..f468d939e7 100755 --- a/utils/replicaset/canary_test.go +++ b/utils/replicaset/canary_test.go @@ -721,82 +721,83 @@ func TestApproximateWeightedNewStableReplicaCounts(t *testing.T) { tests := []struct { replicas int32 weight int32 + maxWeight int32 maxSurge int32 expCanary int32 expStable int32 }{ - {replicas: 0, weight: 0, maxSurge: 0, expCanary: 0, expStable: 0}, // 0% - {replicas: 0, weight: 50, maxSurge: 0, expCanary: 0, expStable: 0}, // 0% - {replicas: 0, weight: 100, maxSurge: 0, expCanary: 0, expStable: 0}, // 0% - - {replicas: 0, weight: 0, maxSurge: 1, expCanary: 0, expStable: 0}, // 0% - {replicas: 0, weight: 50, maxSurge: 1, expCanary: 0, expStable: 0}, // 0% - {replicas: 0, weight: 100, maxSurge: 1, expCanary: 0, expStable: 0}, // 0% - - {replicas: 1, weight: 0, maxSurge: 0, expCanary: 0, expStable: 1}, // 0% - {replicas: 1, weight: 1, maxSurge: 0, expCanary: 0, expStable: 1}, // 0% - {replicas: 1, weight: 49, maxSurge: 0, expCanary: 0, expStable: 1}, // 0% - {replicas: 1, weight: 50, maxSurge: 0, expCanary: 1, expStable: 0}, // 100% - {replicas: 1, weight: 99, maxSurge: 0, expCanary: 1, expStable: 0}, // 100% - {replicas: 1, weight: 100, maxSurge: 0, expCanary: 1, expStable: 0}, // 100% - - {replicas: 1, weight: 0, maxSurge: 1, expCanary: 0, expStable: 1}, // 0% - {replicas: 1, weight: 1, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% - {replicas: 1, weight: 49, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% - {replicas: 1, weight: 50, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% - {replicas: 1, weight: 99, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% - {replicas: 1, weight: 100, maxSurge: 1, expCanary: 1, expStable: 0}, // 100% - - {replicas: 2, weight: 0, maxSurge: 0, expCanary: 0, expStable: 2}, // 0% - {replicas: 2, weight: 1, maxSurge: 0, expCanary: 1, expStable: 1}, // 50% - {replicas: 2, weight: 50, maxSurge: 0, expCanary: 1, expStable: 1}, // 50% - {replicas: 2, weight: 99, maxSurge: 0, expCanary: 1, expStable: 1}, // 50% - {replicas: 2, weight: 100, maxSurge: 0, expCanary: 2, expStable: 0}, // 100% - - {replicas: 2, weight: 0, maxSurge: 1, expCanary: 0, expStable: 2}, // 0% - {replicas: 2, weight: 1, maxSurge: 1, expCanary: 1, expStable: 2}, // 33.3% - {replicas: 2, weight: 50, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% - {replicas: 2, weight: 99, maxSurge: 1, expCanary: 2, expStable: 1}, // 66.6% - {replicas: 2, weight: 100, maxSurge: 1, expCanary: 2, expStable: 0}, // 100% - - {replicas: 3, weight: 10, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% - {replicas: 3, weight: 25, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% - {replicas: 3, weight: 33, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% - {replicas: 3, weight: 34, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% - {replicas: 3, weight: 49, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% - {replicas: 3, weight: 50, maxSurge: 0, expCanary: 2, expStable: 1}, // 66.6% - - {replicas: 3, weight: 10, maxSurge: 1, expCanary: 1, expStable: 3}, // 25% - {replicas: 3, weight: 25, maxSurge: 1, expCanary: 1, expStable: 3}, // 25% - {replicas: 3, weight: 33, maxSurge: 1, expCanary: 1, expStable: 2}, // 33.3% - {replicas: 3, weight: 34, maxSurge: 1, expCanary: 1, expStable: 2}, // 33.3% - {replicas: 3, weight: 49, maxSurge: 1, expCanary: 2, expStable: 2}, // 50% - {replicas: 3, weight: 50, maxSurge: 1, expCanary: 2, expStable: 2}, // 50% - - {replicas: 10, weight: 0, maxSurge: 1, expCanary: 0, expStable: 10}, // 0% - {replicas: 10, weight: 1, maxSurge: 0, expCanary: 1, expStable: 9}, // 10% - {replicas: 10, weight: 14, maxSurge: 0, expCanary: 1, expStable: 9}, // 10% - {replicas: 10, weight: 15, maxSurge: 0, expCanary: 2, expStable: 8}, // 20% - {replicas: 10, weight: 16, maxSurge: 0, expCanary: 2, expStable: 8}, // 20% - {replicas: 10, weight: 99, maxSurge: 0, expCanary: 9, expStable: 1}, // 90% - {replicas: 10, weight: 100, maxSurge: 1, expCanary: 10, expStable: 0}, // 100% - - {replicas: 10, weight: 0, maxSurge: 1, expCanary: 0, expStable: 10}, // 0% - {replicas: 10, weight: 1, maxSurge: 1, expCanary: 1, expStable: 10}, // 9.1% - {replicas: 10, weight: 18, maxSurge: 1, expCanary: 2, expStable: 9}, // 18.1% - {replicas: 10, weight: 19, maxSurge: 1, expCanary: 2, expStable: 9}, // 18.1% - {replicas: 10, weight: 20, maxSurge: 1, expCanary: 2, expStable: 8}, // 20% - {replicas: 10, weight: 23, maxSurge: 1, expCanary: 2, expStable: 8}, // 20% - {replicas: 10, weight: 24, maxSurge: 1, expCanary: 3, expStable: 8}, // 27.2% - {replicas: 10, weight: 25, maxSurge: 1, expCanary: 3, expStable: 8}, // 27.2% - {replicas: 10, weight: 99, maxSurge: 1, expCanary: 10, expStable: 1}, // 90.9% - {replicas: 10, weight: 100, maxSurge: 1, expCanary: 10, expStable: 0}, // 100% + {replicas: 0, weight: 0, maxWeight: 100, maxSurge: 0, expCanary: 0, expStable: 0}, // 0% + {replicas: 0, weight: 50, maxWeight: 100, maxSurge: 0, expCanary: 0, expStable: 0}, // 0% + {replicas: 0, weight: 100, maxWeight: 100, maxSurge: 0, expCanary: 0, expStable: 0}, // 0% + + {replicas: 0, weight: 0, maxWeight: 100, maxSurge: 1, expCanary: 0, expStable: 0}, // 0% + {replicas: 0, weight: 50, maxWeight: 100, maxSurge: 1, expCanary: 0, expStable: 0}, // 0% + {replicas: 0, weight: 100, maxWeight: 100, maxSurge: 1, expCanary: 0, expStable: 0}, // 0% + + {replicas: 1, weight: 0, maxWeight: 100, maxSurge: 0, expCanary: 0, expStable: 1}, // 0% + {replicas: 1, weight: 1, maxWeight: 100, maxSurge: 0, expCanary: 0, expStable: 1}, // 0% + {replicas: 1, weight: 49, maxWeight: 100, maxSurge: 0, expCanary: 0, expStable: 1}, // 0% + {replicas: 1, weight: 50, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 0}, // 100% + {replicas: 1, weight: 99, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 0}, // 100% + {replicas: 1, weight: 100, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 0}, // 100% + + {replicas: 1, weight: 0, maxWeight: 100, maxSurge: 1, expCanary: 0, expStable: 1}, // 0% + {replicas: 1, weight: 1, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% + {replicas: 1, weight: 49, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% + {replicas: 1, weight: 50, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% + {replicas: 1, weight: 99, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% + {replicas: 1, weight: 100, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 0}, // 100% + + {replicas: 2, weight: 0, maxWeight: 100, maxSurge: 0, expCanary: 0, expStable: 2}, // 0% + {replicas: 2, weight: 1, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 1}, // 50% + {replicas: 2, weight: 50, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 1}, // 50% + {replicas: 2, weight: 99, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 1}, // 50% + {replicas: 2, weight: 100, maxWeight: 100, maxSurge: 0, expCanary: 2, expStable: 0}, // 100% + + {replicas: 2, weight: 0, maxWeight: 100, maxSurge: 1, expCanary: 0, expStable: 2}, // 0% + {replicas: 2, weight: 1, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 2}, // 33.3% + {replicas: 2, weight: 50, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 1}, // 50% + {replicas: 2, weight: 99, maxWeight: 100, maxSurge: 1, expCanary: 2, expStable: 1}, // 66.6% + {replicas: 2, weight: 100, maxWeight: 100, maxSurge: 1, expCanary: 2, expStable: 0}, // 100% + + {replicas: 3, weight: 10, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% + {replicas: 3, weight: 25, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% + {replicas: 3, weight: 33, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% + {replicas: 3, weight: 34, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% + {replicas: 3, weight: 49, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 2}, // 33.3% + {replicas: 3, weight: 50, maxWeight: 100, maxSurge: 0, expCanary: 2, expStable: 1}, // 66.6% + + {replicas: 3, weight: 10, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 3}, // 25% + {replicas: 3, weight: 25, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 3}, // 25% + {replicas: 3, weight: 33, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 2}, // 33.3% + {replicas: 3, weight: 34, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 2}, // 33.3% + {replicas: 3, weight: 49, maxWeight: 100, maxSurge: 1, expCanary: 2, expStable: 2}, // 50% + {replicas: 3, weight: 50, maxWeight: 100, maxSurge: 1, expCanary: 2, expStable: 2}, // 50% + + {replicas: 10, weight: 0, maxWeight: 100, maxSurge: 1, expCanary: 0, expStable: 10}, // 0% + {replicas: 10, weight: 1, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 9}, // 10% + {replicas: 10, weight: 14, maxWeight: 100, maxSurge: 0, expCanary: 1, expStable: 9}, // 10% + {replicas: 10, weight: 15, maxWeight: 100, maxSurge: 0, expCanary: 2, expStable: 8}, // 20% + {replicas: 10, weight: 16, maxWeight: 100, maxSurge: 0, expCanary: 2, expStable: 8}, // 20% + {replicas: 10, weight: 99, maxWeight: 100, maxSurge: 0, expCanary: 9, expStable: 1}, // 90% + {replicas: 10, weight: 100, maxWeight: 100, maxSurge: 1, expCanary: 10, expStable: 0}, // 100% + + {replicas: 10, weight: 0, maxWeight: 100, maxSurge: 1, expCanary: 0, expStable: 10}, // 0% + {replicas: 10, weight: 1, maxWeight: 100, maxSurge: 1, expCanary: 1, expStable: 10}, // 9.1% + {replicas: 10, weight: 18, maxWeight: 100, maxSurge: 1, expCanary: 2, expStable: 9}, // 18.1% + {replicas: 10, weight: 19, maxWeight: 100, maxSurge: 1, expCanary: 2, expStable: 9}, // 18.1% + {replicas: 10, weight: 20, maxWeight: 100, maxSurge: 1, expCanary: 2, expStable: 8}, // 20% + {replicas: 10, weight: 23, maxWeight: 100, maxSurge: 1, expCanary: 2, expStable: 8}, // 20% + {replicas: 10, weight: 24, maxWeight: 100, maxSurge: 1, expCanary: 3, expStable: 8}, // 27.2% + {replicas: 10, weight: 25, maxWeight: 100, maxSurge: 1, expCanary: 3, expStable: 8}, // 27.2% + {replicas: 10, weight: 99, maxWeight: 100, maxSurge: 1, expCanary: 10, expStable: 1}, // 90.9% + {replicas: 10, weight: 100, maxWeight: 100, maxSurge: 1, expCanary: 10, expStable: 0}, // 100% } for i := range tests { test := tests[i] - t.Run(fmt.Sprintf("%s_replicas:%d_weight:%d_surge:%d", t.Name(), test.replicas, test.weight, test.maxSurge), func(t *testing.T) { - newRSReplicaCount, stableRSReplicaCount := approximateWeightedCanaryStableReplicaCounts(test.replicas, test.weight, test.maxSurge) + t.Run(fmt.Sprintf("%s_replicas:%d_weight:%d_maxweight:%d_surge:%d", t.Name(), test.replicas, test.weight, test.maxWeight, test.maxSurge), func(t *testing.T) { + newRSReplicaCount, stableRSReplicaCount := approximateWeightedCanaryStableReplicaCounts(test.replicas, test.weight, test.maxWeight, test.maxSurge) assert.Equal(t, test.expCanary, newRSReplicaCount, "check canary replica count") assert.Equal(t, test.expStable, stableRSReplicaCount, "check stable replica count") }) @@ -905,12 +906,12 @@ func TestCalculateReplicaCountsForCanaryStableRSdEdgeCases(t *testing.T) { } func TestTrafficWeightToReplicas(t *testing.T) { - assert.Equal(t, int32(0), trafficWeightToReplicas(10, 0)) - assert.Equal(t, int32(2), trafficWeightToReplicas(10, 20)) - assert.Equal(t, int32(3), trafficWeightToReplicas(10, 25)) - assert.Equal(t, int32(4), trafficWeightToReplicas(10, 33)) - assert.Equal(t, int32(10), trafficWeightToReplicas(10, 99)) - assert.Equal(t, int32(10), trafficWeightToReplicas(10, 100)) + assert.Equal(t, int32(0), trafficWeightToReplicas(10, 0, 100)) + assert.Equal(t, int32(2), trafficWeightToReplicas(10, 20, 100)) + assert.Equal(t, int32(3), trafficWeightToReplicas(10, 25, 100)) + assert.Equal(t, int32(4), trafficWeightToReplicas(10, 33, 100)) + assert.Equal(t, int32(10), trafficWeightToReplicas(10, 99, 100)) + assert.Equal(t, int32(10), trafficWeightToReplicas(10, 100, 100)) } func TestGetOtherRSs(t *testing.T) { diff --git a/utils/weightutil/weight.go b/utils/weightutil/weight.go new file mode 100644 index 0000000000..357a5fdcd3 --- /dev/null +++ b/utils/weightutil/weight.go @@ -0,0 +1,13 @@ +package weightutil + +import ( + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" +) + +func MaxTrafficWeight(ro *v1alpha1.Rollout) int32 { + maxWeight := int32(100) + if ro.Spec.Strategy.Canary != nil && ro.Spec.Strategy.Canary.TrafficRouting != nil && ro.Spec.Strategy.Canary.TrafficRouting.MaxTrafficWeight != nil { + maxWeight = *ro.Spec.Strategy.Canary.TrafficRouting.MaxTrafficWeight + } + return maxWeight +}