From b70995effc85ac02ddd19bbee40704ac990c1392 Mon Sep 17 00:00:00 2001 From: shiyan2016 Date: Mon, 14 Nov 2022 17:51:40 +0800 Subject: [PATCH] partition support float percent Signed-off-by: shiyan2016 --- pkg/util/tools.go | 35 +++++- pkg/util/tools_test.go | 111 ++++++++++++++++++ pkg/webhook/cloneset/validating/validation.go | 2 +- 3 files changed, 146 insertions(+), 2 deletions(-) diff --git a/pkg/util/tools.go b/pkg/util/tools.go index 8729585f86..497bd636df 100644 --- a/pkg/util/tools.go +++ b/pkg/util/tools.go @@ -18,7 +18,10 @@ limitations under the License. package util import ( + "fmt" "math" + "strconv" + "strings" "sync" "github.com/docker/distribution/reference" @@ -163,7 +166,7 @@ func CalculatePartitionReplicas(partition *intstrutil.IntOrString, replicasPoint } // 'roundUp=true' will ensure at least 1 old pod is reserved if partition > "0%" and replicas > 0. - pValue, err := intstrutil.GetScaledValueFromIntOrPercent(partition, replicas, true) + pValue, err := GetScaledValueFromIntOrPercent(partition, replicas, true) if err != nil { return pValue, err } @@ -189,3 +192,33 @@ func IsReferenceEqual(ref1, ref2 appsv1alpha1.TargetReference) bool { } return gv1.Group == gv2.Group && ref1.Kind == ref2.Kind && ref1.Name == ref2.Name } + +func GetScaledValueFromIntOrPercent(intOrPercent *intstrutil.IntOrString, total int, roundUp bool) (int, error) { + if intOrPercent == nil { + return 0, fmt.Errorf("nil value for IntOrString") + } + + switch intOrPercent.Type { + case intstrutil.Int: + return intOrPercent.IntValue(), nil + case intstrutil.String: + s := intOrPercent.StrVal + if strings.HasSuffix(s, "%") { + s = strings.TrimSuffix(intOrPercent.StrVal, "%") + } else { + return 0, fmt.Errorf("invalid type: string is not a percentage") + } + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0, err + } + var value int + if roundUp { + value = int(math.Ceil(v * (float64(total)) / 100)) + } else { + value = int(math.Floor(v * (float64(total)) / 100)) + } + return value, nil + } + return 0, fmt.Errorf("invalid type: neither int nor percentage") +} diff --git a/pkg/util/tools_test.go b/pkg/util/tools_test.go index 1cdf7a09fa..c5b8872bdd 100644 --- a/pkg/util/tools_test.go +++ b/pkg/util/tools_test.go @@ -285,6 +285,20 @@ func TestCalculatePartitionReplicas(t *testing.T) { expectedValue: 0, succeeded: false, }, + { + name: `replicas=9, partition=0.01%, expected=1, err==nil`, + replicas: pointer.Int32(9), + partition: &intstr.IntOrString{Type: intstr.String, StrVal: "0.01%"}, + expectedValue: 1, + succeeded: true, + }, + { + name: `replicas=999, partition=99.99%, expected=998, err==nil`, + replicas: pointer.Int32(999), + partition: &intstr.IntOrString{Type: intstr.String, StrVal: "99.99%"}, + expectedValue: 998, + succeeded: true, + }, } for _, cs := range cases { @@ -299,3 +313,100 @@ func TestCalculatePartitionReplicas(t *testing.T) { }) } } + +func TestGetScaledValueFromIntOrPercent(t *testing.T) { + tests := []struct { + input intstr.IntOrString + total int + roundUp bool + expectErr bool + expectVal int + }{ + { + input: intstr.FromInt(123), + expectErr: false, + expectVal: 123, + }, + { + input: intstr.FromString("90%"), + total: 100, + roundUp: true, + expectErr: false, + expectVal: 90, + }, + { + input: intstr.FromString("90%"), + total: 95, + roundUp: true, + expectErr: false, + expectVal: 86, + }, + { + input: intstr.FromString("90%"), + total: 95, + roundUp: false, + expectErr: false, + expectVal: 85, + }, + { + input: intstr.FromString("99.99%"), + total: 999, + roundUp: false, + expectErr: false, + expectVal: 998, + }, + { + input: intstr.FromString("99.99%"), + total: 999, + roundUp: true, + expectErr: false, + expectVal: 999, + }, + { + input: intstr.FromString("0.01%"), + total: 95, + roundUp: false, + expectErr: false, + expectVal: 0, + }, + { + input: intstr.FromString("0.01%"), + total: 95, + roundUp: true, + expectErr: false, + expectVal: 1, + }, + { + input: intstr.FromString("%"), + expectErr: true, + }, + { + input: intstr.FromString("90#"), + expectErr: true, + }, + { + input: intstr.FromString("#%"), + expectErr: true, + }, + { + input: intstr.FromString("90"), + expectErr: true, + }, + } + + for i, test := range tests { + t.Logf("test case %d", i) + value, err := GetScaledValueFromIntOrPercent(&test.input, test.total, test.roundUp) + if test.expectErr && err == nil { + t.Errorf("expected error, but got none") + continue + } + if !test.expectErr && err != nil { + t.Errorf("unexpected err: %v", err) + continue + } + if test.expectVal != value { + t.Errorf("expected %v, but got %v", test.expectVal, value) + } + } +} diff --git a/pkg/webhook/cloneset/validating/validation.go b/pkg/webhook/cloneset/validating/validation.go index 287d5d7a63..9a58078275 100644 --- a/pkg/webhook/cloneset/validating/validation.go +++ b/pkg/webhook/cloneset/validating/validation.go @@ -138,7 +138,7 @@ func (h *CloneSetCreateUpdateHandler) validateUpdateStrategy(strategy *appsv1alp appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType))) } - partition, err := intstrutil.GetValueFromIntOrPercent(strategy.Partition, replicas, true) + partition, err := util.GetScaledValueFromIntOrPercent(strategy.Partition, replicas, true) if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("partition"), strategy.Partition.String(), fmt.Sprintf("failed getValueFromIntOrPercent for partition: %v", err)))