123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 |
- /*
- Copyright 2016 The Kubernetes 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 podautoscaler
- import (
- "encoding/json"
- "fmt"
- "math"
- "strconv"
- "strings"
- "testing"
- "time"
- "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/client-go/informers"
- "k8s.io/client-go/kubernetes/fake"
- restclient "k8s.io/client-go/rest"
- core "k8s.io/client-go/testing"
- "k8s.io/client-go/tools/cache"
- "k8s.io/kubernetes/pkg/controller"
- "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics"
- heapster "k8s.io/heapster/metrics/api/v1/types"
- metricsapi "k8s.io/metrics/pkg/apis/metrics/v1alpha1"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- type legacyReplicaCalcTestCase struct {
- currentReplicas int32
- expectedReplicas int32
- expectedError error
- timestamp time.Time
- resource *resourceInfo
- metric *metricInfo
- podReadiness []v1.ConditionStatus
- }
- func (tc *legacyReplicaCalcTestCase) prepareTestClient(t *testing.T) *fake.Clientset {
- fakeClient := &fake.Clientset{}
- fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
- obj := &v1.PodList{}
- for i := 0; i < int(tc.currentReplicas); i++ {
- podReadiness := v1.ConditionTrue
- if tc.podReadiness != nil {
- podReadiness = tc.podReadiness[i]
- }
- podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
- pod := v1.Pod{
- Status: v1.PodStatus{
- Phase: v1.PodRunning,
- StartTime: &metav1.Time{Time: time.Now().Add(-3 * time.Minute)},
- Conditions: []v1.PodCondition{
- {
- Type: v1.PodReady,
- Status: podReadiness,
- },
- },
- },
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- Namespace: testNamespace,
- Labels: map[string]string{
- "name": podNamePrefix,
- },
- },
- Spec: v1.PodSpec{
- Containers: []v1.Container{{}, {}},
- },
- }
- if tc.resource != nil && i < len(tc.resource.requests) {
- pod.Spec.Containers[0].Resources = v1.ResourceRequirements{
- Requests: v1.ResourceList{
- tc.resource.name: tc.resource.requests[i],
- },
- }
- pod.Spec.Containers[1].Resources = v1.ResourceRequirements{
- Requests: v1.ResourceList{
- tc.resource.name: tc.resource.requests[i],
- },
- }
- }
- obj.Items = append(obj.Items, pod)
- }
- return true, obj, nil
- })
- fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) {
- var heapsterRawMemResponse []byte
- if tc.resource != nil {
- metrics := metricsapi.PodMetricsList{}
- for i, resValue := range tc.resource.levels {
- podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
- if len(tc.resource.podNames) > i {
- podName = tc.resource.podNames[i]
- }
- podMetric := metricsapi.PodMetrics{
- ObjectMeta: metav1.ObjectMeta{
- Name: podName,
- Namespace: testNamespace,
- },
- Timestamp: metav1.Time{Time: tc.timestamp},
- Containers: make([]metricsapi.ContainerMetrics, numContainersPerPod),
- }
- for i := 0; i < numContainersPerPod; i++ {
- podMetric.Containers[i] = metricsapi.ContainerMetrics{
- Name: fmt.Sprintf("container%v", i),
- Usage: v1.ResourceList{
- v1.ResourceName(tc.resource.name): *resource.NewMilliQuantity(
- int64(resValue),
- resource.DecimalSI),
- },
- }
- }
- metrics.Items = append(metrics.Items, podMetric)
- }
- heapsterRawMemResponse, _ = json.Marshal(&metrics)
- } else {
- // only return the pods that we actually asked for
- proxyAction := action.(core.ProxyGetAction)
- pathParts := strings.Split(proxyAction.GetPath(), "/")
- // pathParts should look like [ api, v1, model, namespaces, $NS, pod-list, $PODS, metrics, $METRIC... ]
- if len(pathParts) < 9 {
- return true, nil, fmt.Errorf("invalid heapster path %q", proxyAction.GetPath())
- }
- podNames := strings.Split(pathParts[7], ",")
- podPresent := make([]bool, len(tc.metric.levels))
- for _, name := range podNames {
- if len(name) <= len(podNamePrefix)+1 {
- return true, nil, fmt.Errorf("unknown pod %q", name)
- }
- num, err := strconv.Atoi(name[len(podNamePrefix)+1:])
- if err != nil {
- return true, nil, fmt.Errorf("unknown pod %q", name)
- }
- podPresent[num] = true
- }
- timestamp := tc.timestamp
- metrics := heapster.MetricResultList{}
- for i, level := range tc.metric.levels {
- if !podPresent[i] {
- continue
- }
- floatVal := float64(tc.metric.levels[i]) / 1000.0
- metric := heapster.MetricResult{
- Metrics: []heapster.MetricPoint{{Timestamp: timestamp, Value: uint64(level), FloatValue: &floatVal}},
- LatestTimestamp: timestamp,
- }
- metrics.Items = append(metrics.Items, metric)
- }
- heapsterRawMemResponse, _ = json.Marshal(&metrics)
- }
- return true, newFakeResponseWrapper(heapsterRawMemResponse), nil
- })
- return fakeClient
- }
- func (tc *legacyReplicaCalcTestCase) runTest(t *testing.T) {
- testClient := tc.prepareTestClient(t)
- metricsClient := metrics.NewHeapsterMetricsClient(testClient, metrics.DefaultHeapsterNamespace, metrics.DefaultHeapsterScheme, metrics.DefaultHeapsterService, metrics.DefaultHeapsterPort)
- informerFactory := informers.NewSharedInformerFactory(testClient, controller.NoResyncPeriodFunc())
- informer := informerFactory.Core().V1().Pods()
- replicaCalc := NewReplicaCalculator(metricsClient, informer.Lister(), defaultTestingTolerance, defaultTestingCpuInitializationPeriod, defaultTestingDelayOfInitialReadinessStatus)
- stop := make(chan struct{})
- defer close(stop)
- informerFactory.Start(stop)
- if !cache.WaitForNamedCacheSync("HPA", stop, informer.Informer().HasSynced) {
- return
- }
- selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
- MatchLabels: map[string]string{"name": podNamePrefix},
- })
- if err != nil {
- require.Nil(t, err, "something went horribly wrong...")
- }
- if tc.resource != nil {
- outReplicas, outUtilization, outRawValue, outTimestamp, err := replicaCalc.GetResourceReplicas(tc.currentReplicas, tc.resource.targetUtilization, tc.resource.name, testNamespace, selector)
- if tc.expectedError != nil {
- require.Error(t, err, "there should be an error calculating the replica count")
- assert.Contains(t, err.Error(), tc.expectedError.Error(), "the error message should have contained the expected error message")
- return
- }
- require.NoError(t, err, "there should not have been an error calculating the replica count")
- assert.Equal(t, tc.expectedReplicas, outReplicas, "replicas should be as expected")
- assert.Equal(t, tc.resource.expectedUtilization, outUtilization, "utilization should be as expected")
- assert.Equal(t, tc.resource.expectedValue, outRawValue, "raw value should be as expected")
- assert.True(t, tc.timestamp.Equal(outTimestamp), "timestamp should be as expected")
- } else {
- outReplicas, outUtilization, outTimestamp, err := replicaCalc.GetMetricReplicas(tc.currentReplicas, tc.metric.targetUtilization, tc.metric.name, testNamespace, selector, nil)
- if tc.expectedError != nil {
- require.Error(t, err, "there should be an error calculating the replica count")
- assert.Contains(t, err.Error(), tc.expectedError.Error(), "the error message should have contained the expected error message")
- return
- }
- require.NoError(t, err, "there should not have been an error calculating the replica count")
- assert.Equal(t, tc.expectedReplicas, outReplicas, "replicas should be as expected")
- assert.Equal(t, tc.metric.expectedUtilization, outUtilization, "utilization should be as expected")
- assert.True(t, tc.timestamp.Equal(outTimestamp), "timestamp should be as expected")
- }
- }
- func TestLegacyReplicaCalcDisjointResourcesMetrics(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 1,
- expectedError: fmt.Errorf("no metrics returned matched known pods"),
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0")},
- levels: []int64{100},
- podNames: []string{"an-older-pod-name"},
- targetUtilization: 100,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleUp(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 5,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{300, 500, 700},
- targetUtilization: 30,
- expectedUtilization: 50,
- expectedValue: numContainersPerPod * 500,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleUpUnreadyLessScale(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 4,
- podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue},
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{300, 500, 700},
- targetUtilization: 30,
- expectedUtilization: 60,
- expectedValue: numContainersPerPod * 600,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleUpUnreadyNoScale(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 3,
- podReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{400, 500, 700},
- targetUtilization: 30,
- expectedUtilization: 40,
- expectedValue: numContainersPerPod * 400,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleUpCM(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 4,
- metric: &metricInfo{
- name: "qps",
- levels: []int64{20000, 10000, 30000},
- targetUtilization: 15000,
- expectedUtilization: 20000,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleUpCMUnreadyNoLessScale(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 6,
- podReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse},
- metric: &metricInfo{
- name: "qps",
- levels: []int64{50000, 10000, 30000},
- targetUtilization: 15000,
- expectedUtilization: 30000,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleUpCMUnreadyScale(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 7,
- podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionFalse},
- metric: &metricInfo{
- name: "qps",
- levels: []int64{50000, 15000, 30000},
- targetUtilization: 15000,
- expectedUtilization: 31666,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleDown(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 5,
- expectedReplicas: 3,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{100, 300, 500, 250, 250},
- targetUtilization: 50,
- expectedUtilization: 28,
- expectedValue: numContainersPerPod * 280,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleDownCM(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 5,
- expectedReplicas: 3,
- metric: &metricInfo{
- name: "qps",
- levels: []int64{12000, 12000, 12000, 12000, 12000},
- targetUtilization: 20000,
- expectedUtilization: 12000,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcScaleDownIgnoresUnreadyPods(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 5,
- expectedReplicas: 2,
- podReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{100, 300, 500, 250, 250},
- targetUtilization: 50,
- expectedUtilization: 30,
- expectedValue: numContainersPerPod * 300,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcTolerance(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 3,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
- levels: []int64{1010, 1030, 1020},
- targetUtilization: 100,
- expectedUtilization: 102,
- expectedValue: numContainersPerPod * 1020,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcToleranceCM(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 3,
- metric: &metricInfo{
- name: "qps",
- levels: []int64{20000, 21000, 21000},
- targetUtilization: 20000,
- expectedUtilization: 20666,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcSuperfluousMetrics(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 4,
- expectedReplicas: 24,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{4000, 9500, 3000, 7000, 3200, 2000},
- targetUtilization: 100,
- expectedUtilization: 587,
- expectedValue: numContainersPerPod * 5875,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcMissingMetrics(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 4,
- expectedReplicas: 3,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{400, 95},
- targetUtilization: 100,
- expectedUtilization: 24,
- expectedValue: 495, // numContainersPerPod * 247, for sufficiently large values of 247
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcEmptyMetrics(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 4,
- expectedError: fmt.Errorf("unable to get metrics for resource cpu: no metrics returned from heapster"),
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{},
- targetUtilization: 100,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcEmptyCPURequest(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 1,
- expectedError: fmt.Errorf("missing request for"),
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{},
- levels: []int64{200},
- targetUtilization: 100,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcMissingMetricsNoChangeEq(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 2,
- expectedReplicas: 2,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{1000},
- targetUtilization: 100,
- expectedUtilization: 100,
- expectedValue: numContainersPerPod * 1000,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcMissingMetricsNoChangeGt(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 2,
- expectedReplicas: 2,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{1900},
- targetUtilization: 100,
- expectedUtilization: 190,
- expectedValue: numContainersPerPod * 1900,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcMissingMetricsNoChangeLt(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 2,
- expectedReplicas: 2,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{600},
- targetUtilization: 100,
- expectedUtilization: 60,
- expectedValue: numContainersPerPod * 600,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcMissingMetricsUnreadyNoChange(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 3,
- podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue},
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{100, 450},
- targetUtilization: 50,
- expectedUtilization: 45,
- expectedValue: numContainersPerPod * 450,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcMissingMetricsUnreadyScaleUp(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 3,
- expectedReplicas: 4,
- podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue},
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{100, 2000},
- targetUtilization: 50,
- expectedUtilization: 200,
- expectedValue: numContainersPerPod * 2000,
- },
- }
- tc.runTest(t)
- }
- func TestLegacyReplicaCalcMissingMetricsUnreadyScaleDown(t *testing.T) {
- tc := legacyReplicaCalcTestCase{
- currentReplicas: 4,
- expectedReplicas: 3,
- podReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue},
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- requests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
- levels: []int64{100, 100, 100},
- targetUtilization: 50,
- expectedUtilization: 10,
- expectedValue: numContainersPerPod * 100,
- },
- }
- tc.runTest(t)
- }
- // TestComputedToleranceAlgImplementation is a regression test which
- // back-calculates a minimal percentage for downscaling based on a small percentage
- // increase in pod utilization which is calibrated against the tolerance value.
- func TestLegacyReplicaCalcComputedToleranceAlgImplementation(t *testing.T) {
- startPods := int32(10)
- // 150 mCPU per pod.
- totalUsedCPUOfAllPods := int64(startPods * 150)
- // Each pod starts out asking for 2X what is really needed.
- // This means we will have a 50% ratio of used/requested
- totalRequestedCPUOfAllPods := int32(2 * totalUsedCPUOfAllPods)
- requestedToUsed := float64(totalRequestedCPUOfAllPods / int32(totalUsedCPUOfAllPods))
- // Spread the amount we ask over 10 pods. We can add some jitter later in reportedLevels.
- perPodRequested := totalRequestedCPUOfAllPods / startPods
- // Force a minimal scaling event by satisfying (tolerance < 1 - resourcesUsedRatio).
- target := math.Abs(1/(requestedToUsed*(1-defaultTestingTolerance))) + .01
- finalCPUPercentTarget := int32(target * 100)
- resourcesUsedRatio := float64(totalUsedCPUOfAllPods) / float64(float64(totalRequestedCPUOfAllPods)*target)
- // i.e. .60 * 20 -> scaled down expectation.
- finalPods := int32(math.Ceil(resourcesUsedRatio * float64(startPods)))
- // To breach tolerance we will create a utilization ratio difference of tolerance to usageRatioToleranceValue)
- tc := legacyReplicaCalcTestCase{
- currentReplicas: startPods,
- expectedReplicas: finalPods,
- resource: &resourceInfo{
- name: v1.ResourceCPU,
- levels: []int64{
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- totalUsedCPUOfAllPods / 10,
- },
- requests: []resource.Quantity{
- resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
- resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
- },
- targetUtilization: finalCPUPercentTarget,
- expectedUtilization: int32(totalUsedCPUOfAllPods*100) / totalRequestedCPUOfAllPods,
- expectedValue: numContainersPerPod * totalUsedCPUOfAllPods / 10,
- },
- }
- tc.runTest(t)
- // Reuse the data structure above, now testing "unscaling".
- // Now, we test that no scaling happens if we are in a very close margin to the tolerance
- target = math.Abs(1/(requestedToUsed*(1-defaultTestingTolerance))) + .004
- finalCPUPercentTarget = int32(target * 100)
- tc.resource.targetUtilization = finalCPUPercentTarget
- tc.currentReplicas = startPods
- tc.expectedReplicas = startPods
- tc.runTest(t)
- }
- // TODO: add more tests
|