/* Copyright 2021 The KubeVela Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package workloads import ( "testing" "k8s.io/utils/pointer" "k8s.io/apimachinery/pkg/util/intstr" "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" ) var ( rolloutPercentSpec = &v1alpha1.RolloutPlan{ RolloutBatches: []v1alpha1.RolloutBatch{ { Replicas: intstr.FromString("20%"), }, { Replicas: intstr.FromString("40%"), }, { Replicas: intstr.FromString("30%"), }, { Replicas: intstr.FromString("10%"), }, }, } rolloutNumericSpec = &v1alpha1.RolloutPlan{ RolloutBatches: []v1alpha1.RolloutBatch{ { Replicas: intstr.FromInt(1), }, { Replicas: intstr.FromInt(2), }, { Replicas: intstr.FromInt(4), }, { Replicas: intstr.FromInt(3), }, }, } rolloutMixedSpec = &v1alpha1.RolloutPlan{ RolloutBatches: []v1alpha1.RolloutBatch{ { Replicas: intstr.FromInt(1), }, { Replicas: intstr.FromString("20%"), }, { Replicas: intstr.FromString("50%"), }, { Replicas: intstr.FromInt(2), }, }, } rolloutRelaxSpec = &v1alpha1.RolloutPlan{ RolloutBatches: []v1alpha1.RolloutBatch{ { Replicas: intstr.FromString("20%"), }, { Replicas: intstr.FromInt(2), }, { Replicas: intstr.FromInt(2), }, { Replicas: intstr.FromString("50%"), }, }, } rolloutOverFlowSpec = &v1alpha1.RolloutPlan{ RolloutBatches: []v1alpha1.RolloutBatch{ { Replicas: intstr.FromString("140%"), }, { Replicas: intstr.FromInt(1), }, { Replicas: intstr.FromString("40%"), }, }, } ) func TestCalculateNewBatchTarget(t *testing.T) { // test common rollout if got := calculateNewBatchTarget(rolloutMixedSpec, 0, 10, 0); got != 1 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 1) } if got := calculateNewBatchTarget(rolloutMixedSpec, 0, 10, 1); got != 3 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 3) } if got := calculateNewBatchTarget(rolloutMixedSpec, 0, 10, 2); got != 8 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 8) } if got := calculateNewBatchTarget(rolloutMixedSpec, 0, 10, 3); got != 10 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 10) } // test scale up if got := calculateNewBatchTarget(rolloutMixedSpec, 2, 12, 0); got != 3 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 3) } if got := calculateNewBatchTarget(rolloutMixedSpec, 3, 13, 1); got != 6 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 6) } if got := calculateNewBatchTarget(rolloutMixedSpec, 4, 14, 2); got != 12 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 12) } if got := calculateNewBatchTarget(rolloutMixedSpec, 5, 15, 3); got != 15 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 15) } // test scale down if got := calculateNewBatchTarget(rolloutMixedSpec, 10, 0, 0); got != 9 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 9) } if got := calculateNewBatchTarget(rolloutMixedSpec, 20, 5, 1); got != 16 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 16) } if got := calculateNewBatchTarget(rolloutMixedSpec, 30, 15, 2); got != 18 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 18) } if got := calculateNewBatchTarget(rolloutMixedSpec, 40, 10, 3); got != 10 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 10) } } func TestCalculateNewBatchTargetCornerCases(t *testing.T) { // test current batch overflow if got := calculateNewBatchTarget(rolloutMixedSpec, 2, 12, 4); got != 12 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 12) } if got := calculateNewBatchTarget(rolloutMixedSpec, 13, 3, 5); got != 3 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 3) } // numeric value doesn't match the range if got := calculateNewBatchTarget(rolloutNumericSpec, 16, 10, 0); got != 15 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 15) } if got := calculateNewBatchTarget(rolloutPercentSpec, 10, 10, 2); got != 10 { t.Errorf("calculateNewBatchTarget() = %v, want %v", got, 10) } } func TestVerifyBatchesWithRolloutNormal(t *testing.T) { if err := verifyBatchesWithRollout(rolloutMixedSpec, 10); err != nil { t.Errorf("verifyBatchesWithRollout() = %v, want nil", err) } if err := verifyBatchesWithRollout(rolloutMixedSpec, 12); err != nil { t.Errorf("verifyBatchesWithRollout() = %v, want nil", err) } if err := verifyBatchesWithRollout(rolloutMixedSpec, 13); err != nil { t.Errorf("verifyBatchesWithRollout() = %v, want nil", err) } if err := verifyBatchesWithRollout(rolloutMixedSpec, 20); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } if err := verifyBatchesWithRollout(rolloutMixedSpec, 6); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } } func TestVerifyBatchesWithRolloutRelaxed(t *testing.T) { // last batch as a percentage always succeeds if err := verifyBatchesWithRollout(rolloutRelaxSpec, 10); err != nil { t.Errorf("verifyBatchesWithRollout() = %v, want nil", err) } if err := verifyBatchesWithRollout(rolloutRelaxSpec, 100); err != nil { t.Errorf("verifyBatchesWithRollout() = %v, want nil", err) } if err := verifyBatchesWithRollout(rolloutRelaxSpec, 31); err != nil { t.Errorf("verifyBatchesWithRollout() = %v, want nil", err) } // last can't be zero if err := verifyBatchesWithRollout(rolloutRelaxSpec, 6); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } // overflow always fail if err := verifyBatchesWithRollout(rolloutOverFlowSpec, 10); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } if err := verifyBatchesWithRollout(rolloutOverFlowSpec, 100); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } if err := verifyBatchesWithRollout(rolloutOverFlowSpec, 1); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } if err := verifyBatchesWithRollout(rolloutOverFlowSpec, 0); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } } func TestVerifyEmptyRolloutBatches(t *testing.T) { plan := &v1alpha1.RolloutPlan{ TargetSize: pointer.Int32Ptr(2), } if err := verifyBatchesWithRollout(plan, 3); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } } func TestVerifyBatchesWithRolloutNumeric(t *testing.T) { // test hard number if err := verifyBatchesWithRollout(rolloutNumericSpec, 6); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } if err := verifyBatchesWithRollout(rolloutNumericSpec, 11); err == nil { t.Errorf("verifyBatchesWithRollout() = %v, want error", nil) } if err := verifyBatchesWithRollout(rolloutNumericSpec, 10); err != nil { t.Errorf("verifyBatchesWithRollout() = %v, want nil", err) } } func Test_VerifyBatchesWithScalePassCases(t *testing.T) { tests := map[string]struct { rolloutSpec *v1alpha1.RolloutPlan originalSize int targetSize int }{ "percent equal case 1": { rolloutSpec: rolloutPercentSpec, originalSize: 12, targetSize: 12, }, "percent equal case 2": { rolloutSpec: rolloutPercentSpec, originalSize: 0, targetSize: 0, }, "percent increase case": { rolloutSpec: rolloutPercentSpec, originalSize: 12, targetSize: 22, }, "percent decrease case 1": { rolloutSpec: rolloutPercentSpec, originalSize: 27, targetSize: 7, }, "percent decrease case 2": { rolloutSpec: rolloutPercentSpec, originalSize: 27, targetSize: 0, }, "relax increase 1": { rolloutSpec: rolloutRelaxSpec, originalSize: 12, targetSize: 32, }, "relax increase 2": { rolloutSpec: rolloutRelaxSpec, originalSize: 12, targetSize: 22, }, "mix increase 1": { rolloutSpec: rolloutMixedSpec, originalSize: 13, targetSize: 26, }, "mix increase 2": { rolloutSpec: rolloutMixedSpec, originalSize: 30, targetSize: 42, }, "mix decrease 1": { rolloutSpec: rolloutMixedSpec, originalSize: 32, targetSize: 20, }, "mix decrease 2": { rolloutSpec: rolloutMixedSpec, originalSize: 12, targetSize: 0, }, "numeric increase": { rolloutSpec: rolloutNumericSpec, originalSize: 16, targetSize: 26, }, "numeric decrease": { rolloutSpec: rolloutNumericSpec, originalSize: 13, targetSize: 3, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { if err := verifyBatchesWithScale(tt.rolloutSpec, tt.originalSize, tt.targetSize); err != nil { t.Errorf("verifyBatchesWithScale() error = %v, want pass", err) } }) } } func Test_VerifyBatchesWithScaleFailCases(t *testing.T) { tests := map[string]struct { rolloutSpec *v1alpha1.RolloutPlan originalSize int targetSize int }{ "total percent more than 100 increase": { rolloutSpec: rolloutOverFlowSpec, originalSize: 12, targetSize: 115, }, "total percent more than 100 decrease": { rolloutSpec: rolloutOverFlowSpec, originalSize: 312, targetSize: 15, }, "total percent more than 100 equal": { rolloutSpec: rolloutOverFlowSpec, originalSize: 12, targetSize: 12, }, "percent increase case less than batch number": { rolloutSpec: rolloutPercentSpec, originalSize: 12, targetSize: 15, }, "percent decrease case": { rolloutSpec: rolloutPercentSpec, originalSize: 10, targetSize: 7, }, "relax increase too little": { rolloutSpec: rolloutRelaxSpec, originalSize: 12, targetSize: 17, }, "mix increase": { rolloutSpec: rolloutMixedSpec, originalSize: 13, targetSize: 20, }, "mix decrease": { rolloutSpec: rolloutMixedSpec, originalSize: 42, targetSize: 33, }, "numeric increase": { rolloutSpec: rolloutNumericSpec, originalSize: 16, targetSize: 32, }, "numeric decrease 1": { rolloutSpec: rolloutNumericSpec, originalSize: 13, targetSize: 10, }, "numeric decrease 2": { rolloutSpec: rolloutNumericSpec, originalSize: 16, targetSize: 10, }, "empty rollingBatches": { rolloutSpec: &v1alpha1.RolloutPlan{}, targetSize: 3, originalSize: 2, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { if err := verifyBatchesWithScale(tt.rolloutSpec, tt.originalSize, tt.targetSize); err == nil { t.Errorf("verifyBatchesWithScale passed, want fail") } }) } }