k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/podautoscaler/horizontal_test.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package podautoscaler
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math"
    23  	goruntime "runtime"
    24  	"strings"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	autoscalingv1 "k8s.io/api/autoscaling/v1"
    30  	autoscalingv2 "k8s.io/api/autoscaling/v2"
    31  	v1 "k8s.io/api/core/v1"
    32  	"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
    33  	"k8s.io/apimachinery/pkg/api/resource"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/labels"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/runtime/schema"
    38  	"k8s.io/apimachinery/pkg/util/wait"
    39  	"k8s.io/apimachinery/pkg/watch"
    40  	"k8s.io/client-go/informers"
    41  	"k8s.io/client-go/kubernetes/fake"
    42  	scalefake "k8s.io/client-go/scale/fake"
    43  	core "k8s.io/client-go/testing"
    44  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    45  	autoscalingapiv2 "k8s.io/kubernetes/pkg/apis/autoscaling/v2"
    46  	"k8s.io/kubernetes/pkg/controller"
    47  	"k8s.io/kubernetes/pkg/controller/podautoscaler/metrics"
    48  	"k8s.io/kubernetes/pkg/controller/podautoscaler/monitor"
    49  	"k8s.io/kubernetes/pkg/controller/util/selectors"
    50  	"k8s.io/kubernetes/test/utils/ktesting"
    51  	cmapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta2"
    52  	emapi "k8s.io/metrics/pkg/apis/external_metrics/v1beta1"
    53  	metricsapi "k8s.io/metrics/pkg/apis/metrics/v1beta1"
    54  	metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake"
    55  	cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake"
    56  	emfake "k8s.io/metrics/pkg/client/external_metrics/fake"
    57  	"k8s.io/utils/pointer"
    58  
    59  	"github.com/stretchr/testify/assert"
    60  
    61  	_ "k8s.io/kubernetes/pkg/apis/apps/install"
    62  	_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
    63  )
    64  
    65  // From now on, the HPA controller does have history in it (scaleUpEvents, scaleDownEvents)
    66  // Hence the second HPA controller reconcile cycle might return different result (comparing with the first run).
    67  // Current test infrastructure has a race condition, when several reconcile cycles will be performed
    68  //    while it should be stopped right after the first one. And the second will raise an exception
    69  //    because of different result.
    70  
    71  // This comment has more info: https://github.com/kubernetes/kubernetes/pull/74525#issuecomment-502653106
    72  // We need to rework this infrastructure:  https://github.com/kubernetes/kubernetes/issues/79222
    73  
    74  var statusOk = []autoscalingv2.HorizontalPodAutoscalerCondition{
    75  	{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
    76  	{Type: autoscalingv2.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"},
    77  	{Type: autoscalingv2.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"},
    78  }
    79  
    80  // statusOkWithOverrides returns the "ok" status with the given conditions as overridden
    81  func statusOkWithOverrides(overrides ...autoscalingv2.HorizontalPodAutoscalerCondition) []autoscalingv2.HorizontalPodAutoscalerCondition {
    82  	resv2 := make([]autoscalingv2.HorizontalPodAutoscalerCondition, len(statusOk))
    83  	copy(resv2, statusOk)
    84  	for _, override := range overrides {
    85  		resv2 = setConditionInList(resv2, override.Type, override.Status, override.Reason, override.Message)
    86  	}
    87  
    88  	// copy to a v1 slice
    89  	resv1 := make([]autoscalingv2.HorizontalPodAutoscalerCondition, len(resv2))
    90  	for i, cond := range resv2 {
    91  		resv1[i] = autoscalingv2.HorizontalPodAutoscalerCondition{
    92  			Type:   autoscalingv2.HorizontalPodAutoscalerConditionType(cond.Type),
    93  			Status: cond.Status,
    94  			Reason: cond.Reason,
    95  		}
    96  	}
    97  
    98  	return resv1
    99  }
   100  
   101  func alwaysReady() bool { return true }
   102  
   103  type fakeResource struct {
   104  	name       string
   105  	apiVersion string
   106  	kind       string
   107  }
   108  
   109  type testCase struct {
   110  	sync.Mutex
   111  	minReplicas     int32
   112  	maxReplicas     int32
   113  	specReplicas    int32
   114  	statusReplicas  int32
   115  	initialReplicas int32
   116  	scaleUpRules    *autoscalingv2.HPAScalingRules
   117  	scaleDownRules  *autoscalingv2.HPAScalingRules
   118  
   119  	// CPU target utilization as a percentage of the requested resources.
   120  	CPUTarget                    int32
   121  	CPUCurrent                   int32
   122  	verifyCPUCurrent             bool
   123  	reportedLevels               []uint64
   124  	reportedCPURequests          []resource.Quantity
   125  	reportedPodReadiness         []v1.ConditionStatus
   126  	reportedPodStartTime         []metav1.Time
   127  	reportedPodPhase             []v1.PodPhase
   128  	reportedPodDeletionTimestamp []bool
   129  	scaleUpdated                 bool
   130  	statusUpdated                bool
   131  	eventCreated                 bool
   132  	verifyEvents                 bool
   133  	useMetricsAPI                bool
   134  	metricsTarget                []autoscalingv2.MetricSpec
   135  	expectedDesiredReplicas      int32
   136  	expectedConditions           []autoscalingv2.HorizontalPodAutoscalerCondition
   137  	// Channel with names of HPA objects which we have reconciled.
   138  	processed chan string
   139  
   140  	// expected results reported to the mock monitor at first.
   141  	expectedReportedReconciliationActionLabel     monitor.ActionLabel
   142  	expectedReportedReconciliationErrorLabel      monitor.ErrorLabel
   143  	expectedReportedMetricComputationActionLabels map[autoscalingv2.MetricSourceType]monitor.ActionLabel
   144  	expectedReportedMetricComputationErrorLabels  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel
   145  
   146  	// Target resource information.
   147  	resource *fakeResource
   148  
   149  	// Last scale time
   150  	lastScaleTime *metav1.Time
   151  
   152  	// override the test clients
   153  	testClient        *fake.Clientset
   154  	testMetricsClient *metricsfake.Clientset
   155  	testCMClient      *cmfake.FakeCustomMetricsClient
   156  	testEMClient      *emfake.FakeExternalMetricsClient
   157  	testScaleClient   *scalefake.FakeScaleClient
   158  
   159  	recommendations []timestampedRecommendation
   160  	hpaSelectors    *selectors.BiMultimap
   161  }
   162  
   163  // Needs to be called under a lock.
   164  func (tc *testCase) computeCPUCurrent() {
   165  	if len(tc.reportedLevels) != len(tc.reportedCPURequests) || len(tc.reportedLevels) == 0 {
   166  		return
   167  	}
   168  	reported := 0
   169  	for _, r := range tc.reportedLevels {
   170  		reported += int(r)
   171  	}
   172  	requested := 0
   173  	for _, req := range tc.reportedCPURequests {
   174  		requested += int(req.MilliValue())
   175  	}
   176  	tc.CPUCurrent = int32(100 * reported / requested)
   177  }
   178  
   179  func init() {
   180  	// set this high so we don't accidentally run into it when testing
   181  	scaleUpLimitFactor = 8
   182  }
   183  
   184  func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfake.Clientset, *cmfake.FakeCustomMetricsClient, *emfake.FakeExternalMetricsClient, *scalefake.FakeScaleClient) {
   185  	namespace := "test-namespace"
   186  	hpaName := "test-hpa"
   187  	podNamePrefix := "test-pod"
   188  	labelSet := map[string]string{"name": podNamePrefix}
   189  	selector := labels.SelectorFromSet(labelSet).String()
   190  
   191  	tc.Lock()
   192  
   193  	tc.scaleUpdated = false
   194  	tc.statusUpdated = false
   195  	tc.eventCreated = false
   196  	tc.processed = make(chan string, 100)
   197  	if tc.CPUCurrent == 0 {
   198  		tc.computeCPUCurrent()
   199  	}
   200  
   201  	if tc.resource == nil {
   202  		tc.resource = &fakeResource{
   203  			name:       "test-rc",
   204  			apiVersion: "v1",
   205  			kind:       "ReplicationController",
   206  		}
   207  	}
   208  	tc.Unlock()
   209  
   210  	fakeClient := &fake.Clientset{}
   211  	fakeClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   212  		tc.Lock()
   213  		defer tc.Unlock()
   214  		var behavior *autoscalingv2.HorizontalPodAutoscalerBehavior
   215  		if tc.scaleUpRules != nil || tc.scaleDownRules != nil {
   216  			behavior = &autoscalingv2.HorizontalPodAutoscalerBehavior{
   217  				ScaleUp:   tc.scaleUpRules,
   218  				ScaleDown: tc.scaleDownRules,
   219  			}
   220  		}
   221  		hpa := autoscalingv2.HorizontalPodAutoscaler{
   222  			ObjectMeta: metav1.ObjectMeta{
   223  				Name:      hpaName,
   224  				Namespace: namespace,
   225  			},
   226  			Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
   227  				ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
   228  					Kind:       tc.resource.kind,
   229  					Name:       tc.resource.name,
   230  					APIVersion: tc.resource.apiVersion,
   231  				},
   232  				MinReplicas: &tc.minReplicas,
   233  				MaxReplicas: tc.maxReplicas,
   234  				Behavior:    behavior,
   235  			},
   236  			Status: autoscalingv2.HorizontalPodAutoscalerStatus{
   237  				CurrentReplicas: tc.specReplicas,
   238  				DesiredReplicas: tc.specReplicas,
   239  				LastScaleTime:   tc.lastScaleTime,
   240  			},
   241  		}
   242  		// Initialize default values
   243  		autoscalingapiv2.SetDefaults_HorizontalPodAutoscalerBehavior(&hpa)
   244  
   245  		obj := &autoscalingv2.HorizontalPodAutoscalerList{
   246  			Items: []autoscalingv2.HorizontalPodAutoscaler{hpa},
   247  		}
   248  
   249  		if tc.CPUTarget > 0 {
   250  			obj.Items[0].Spec.Metrics = []autoscalingv2.MetricSpec{
   251  				{
   252  					Type: autoscalingv2.ResourceMetricSourceType,
   253  					Resource: &autoscalingv2.ResourceMetricSource{
   254  						Name: v1.ResourceCPU,
   255  						Target: autoscalingv2.MetricTarget{
   256  							Type:               autoscalingv2.UtilizationMetricType,
   257  							AverageUtilization: &tc.CPUTarget,
   258  						},
   259  					},
   260  				},
   261  			}
   262  		}
   263  		if len(tc.metricsTarget) > 0 {
   264  			obj.Items[0].Spec.Metrics = append(obj.Items[0].Spec.Metrics, tc.metricsTarget...)
   265  		}
   266  
   267  		if len(obj.Items[0].Spec.Metrics) == 0 {
   268  			// manually add in the defaulting logic
   269  			obj.Items[0].Spec.Metrics = []autoscalingv2.MetricSpec{
   270  				{
   271  					Type: autoscalingv2.ResourceMetricSourceType,
   272  					Resource: &autoscalingv2.ResourceMetricSource{
   273  						Name: v1.ResourceCPU,
   274  					},
   275  				},
   276  			}
   277  		}
   278  
   279  		return true, obj, nil
   280  	})
   281  
   282  	fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   283  		tc.Lock()
   284  		defer tc.Unlock()
   285  
   286  		obj := &v1.PodList{}
   287  
   288  		specifiedCPURequests := tc.reportedCPURequests != nil
   289  
   290  		numPodsToCreate := int(tc.statusReplicas)
   291  		if specifiedCPURequests {
   292  			numPodsToCreate = len(tc.reportedCPURequests)
   293  		}
   294  
   295  		for i := 0; i < numPodsToCreate; i++ {
   296  			podReadiness := v1.ConditionTrue
   297  			if tc.reportedPodReadiness != nil {
   298  				podReadiness = tc.reportedPodReadiness[i]
   299  			}
   300  			var podStartTime metav1.Time
   301  			if tc.reportedPodStartTime != nil {
   302  				podStartTime = tc.reportedPodStartTime[i]
   303  			}
   304  
   305  			podPhase := v1.PodRunning
   306  			if tc.reportedPodPhase != nil {
   307  				podPhase = tc.reportedPodPhase[i]
   308  			}
   309  
   310  			podDeletionTimestamp := false
   311  			if tc.reportedPodDeletionTimestamp != nil {
   312  				podDeletionTimestamp = tc.reportedPodDeletionTimestamp[i]
   313  			}
   314  
   315  			podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
   316  
   317  			reportedCPURequest := resource.MustParse("1.0")
   318  			if specifiedCPURequests {
   319  				reportedCPURequest = tc.reportedCPURequests[i]
   320  			}
   321  
   322  			pod := v1.Pod{
   323  				Status: v1.PodStatus{
   324  					Phase: podPhase,
   325  					Conditions: []v1.PodCondition{
   326  						{
   327  							Type:               v1.PodReady,
   328  							Status:             podReadiness,
   329  							LastTransitionTime: podStartTime,
   330  						},
   331  					},
   332  					StartTime: &podStartTime,
   333  				},
   334  				ObjectMeta: metav1.ObjectMeta{
   335  					Name:      podName,
   336  					Namespace: namespace,
   337  					Labels: map[string]string{
   338  						"name": podNamePrefix,
   339  					},
   340  				},
   341  
   342  				Spec: v1.PodSpec{
   343  					Containers: []v1.Container{
   344  						{
   345  							Name: "container1",
   346  							Resources: v1.ResourceRequirements{
   347  								Requests: v1.ResourceList{
   348  									v1.ResourceCPU: *resource.NewMilliQuantity(reportedCPURequest.MilliValue()/2, resource.DecimalSI),
   349  								},
   350  							},
   351  						},
   352  						{
   353  							Name: "container2",
   354  							Resources: v1.ResourceRequirements{
   355  								Requests: v1.ResourceList{
   356  									v1.ResourceCPU: *resource.NewMilliQuantity(reportedCPURequest.MilliValue()/2, resource.DecimalSI),
   357  								},
   358  							},
   359  						},
   360  					},
   361  				},
   362  			}
   363  			if podDeletionTimestamp {
   364  				pod.DeletionTimestamp = &metav1.Time{Time: time.Now()}
   365  			}
   366  			obj.Items = append(obj.Items, pod)
   367  		}
   368  		return true, obj, nil
   369  	})
   370  
   371  	fakeClient.AddReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   372  		handled, obj, err := func() (handled bool, ret *autoscalingv2.HorizontalPodAutoscaler, err error) {
   373  			tc.Lock()
   374  			defer tc.Unlock()
   375  
   376  			obj := action.(core.UpdateAction).GetObject().(*autoscalingv2.HorizontalPodAutoscaler)
   377  			assert.Equal(t, namespace, obj.Namespace, "the HPA namespace should be as expected")
   378  			assert.Equal(t, hpaName, obj.Name, "the HPA name should be as expected")
   379  			assert.Equal(t, tc.expectedDesiredReplicas, obj.Status.DesiredReplicas, "the desired replica count reported in the object status should be as expected")
   380  			if tc.verifyCPUCurrent {
   381  				if utilization := findCpuUtilization(obj.Status.CurrentMetrics); assert.NotNil(t, utilization, "the reported CPU utilization percentage should be non-nil") {
   382  					assert.Equal(t, tc.CPUCurrent, *utilization, "the report CPU utilization percentage should be as expected")
   383  				}
   384  			}
   385  			actualConditions := obj.Status.Conditions
   386  			// TODO: it's ok not to sort these because statusOk
   387  			// contains all the conditions, so we'll never be appending.
   388  			// Default to statusOk when missing any specific conditions
   389  			if tc.expectedConditions == nil {
   390  				tc.expectedConditions = statusOkWithOverrides()
   391  			}
   392  			// clear the message so that we can easily compare
   393  			for i := range actualConditions {
   394  				actualConditions[i].Message = ""
   395  				actualConditions[i].LastTransitionTime = metav1.Time{}
   396  			}
   397  			assert.Equal(t, tc.expectedConditions, actualConditions, "the status conditions should have been as expected")
   398  			tc.statusUpdated = true
   399  			// Every time we reconcile HPA object we are updating status.
   400  			return true, obj, nil
   401  		}()
   402  		if obj != nil {
   403  			tc.processed <- obj.Name
   404  		}
   405  		return handled, obj, err
   406  	})
   407  
   408  	fakeScaleClient := &scalefake.FakeScaleClient{}
   409  	fakeScaleClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   410  		tc.Lock()
   411  		defer tc.Unlock()
   412  
   413  		obj := &autoscalingv1.Scale{
   414  			ObjectMeta: metav1.ObjectMeta{
   415  				Name:      tc.resource.name,
   416  				Namespace: namespace,
   417  			},
   418  			Spec: autoscalingv1.ScaleSpec{
   419  				Replicas: tc.specReplicas,
   420  			},
   421  			Status: autoscalingv1.ScaleStatus{
   422  				Replicas: tc.statusReplicas,
   423  				Selector: selector,
   424  			},
   425  		}
   426  		return true, obj, nil
   427  	})
   428  
   429  	fakeScaleClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   430  		tc.Lock()
   431  		defer tc.Unlock()
   432  
   433  		obj := &autoscalingv1.Scale{
   434  			ObjectMeta: metav1.ObjectMeta{
   435  				Name:      tc.resource.name,
   436  				Namespace: namespace,
   437  			},
   438  			Spec: autoscalingv1.ScaleSpec{
   439  				Replicas: tc.specReplicas,
   440  			},
   441  			Status: autoscalingv1.ScaleStatus{
   442  				Replicas: tc.statusReplicas,
   443  				Selector: selector,
   444  			},
   445  		}
   446  		return true, obj, nil
   447  	})
   448  
   449  	fakeScaleClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   450  		tc.Lock()
   451  		defer tc.Unlock()
   452  
   453  		obj := &autoscalingv1.Scale{
   454  			ObjectMeta: metav1.ObjectMeta{
   455  				Name:      tc.resource.name,
   456  				Namespace: namespace,
   457  			},
   458  			Spec: autoscalingv1.ScaleSpec{
   459  				Replicas: tc.specReplicas,
   460  			},
   461  			Status: autoscalingv1.ScaleStatus{
   462  				Replicas: tc.statusReplicas,
   463  				Selector: selector,
   464  			},
   465  		}
   466  		return true, obj, nil
   467  	})
   468  
   469  	fakeScaleClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   470  		tc.Lock()
   471  		defer tc.Unlock()
   472  
   473  		obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
   474  		replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
   475  		assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the RC should be as expected")
   476  		tc.scaleUpdated = true
   477  		return true, obj, nil
   478  	})
   479  
   480  	fakeScaleClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   481  		tc.Lock()
   482  		defer tc.Unlock()
   483  
   484  		obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
   485  		replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
   486  		assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the deployment should be as expected")
   487  		tc.scaleUpdated = true
   488  		return true, obj, nil
   489  	})
   490  
   491  	fakeScaleClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   492  		tc.Lock()
   493  		defer tc.Unlock()
   494  
   495  		obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
   496  		replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
   497  		assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the replicaset should be as expected")
   498  		tc.scaleUpdated = true
   499  		return true, obj, nil
   500  	})
   501  
   502  	fakeWatch := watch.NewFake()
   503  	fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil))
   504  
   505  	fakeMetricsClient := &metricsfake.Clientset{}
   506  	fakeMetricsClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   507  		tc.Lock()
   508  		defer tc.Unlock()
   509  
   510  		metrics := &metricsapi.PodMetricsList{}
   511  		for i, cpu := range tc.reportedLevels {
   512  			// NB: the list reactor actually does label selector filtering for us,
   513  			// so we have to make sure our results match the label selector
   514  			podMetric := metricsapi.PodMetrics{
   515  				ObjectMeta: metav1.ObjectMeta{
   516  					Name:      fmt.Sprintf("%s-%d", podNamePrefix, i),
   517  					Namespace: namespace,
   518  					Labels:    labelSet,
   519  				},
   520  				Timestamp: metav1.Time{Time: time.Now()},
   521  				Window:    metav1.Duration{Duration: time.Minute},
   522  				Containers: []metricsapi.ContainerMetrics{
   523  					{
   524  						Name: "container1",
   525  						Usage: v1.ResourceList{
   526  							v1.ResourceCPU: *resource.NewMilliQuantity(
   527  								int64(cpu/2),
   528  								resource.DecimalSI),
   529  							v1.ResourceMemory: *resource.NewQuantity(
   530  								int64(1024*1024/2),
   531  								resource.BinarySI),
   532  						},
   533  					},
   534  					{
   535  						Name: "container2",
   536  						Usage: v1.ResourceList{
   537  							v1.ResourceCPU: *resource.NewMilliQuantity(
   538  								int64(cpu/2),
   539  								resource.DecimalSI),
   540  							v1.ResourceMemory: *resource.NewQuantity(
   541  								int64(1024*1024/2),
   542  								resource.BinarySI),
   543  						},
   544  					},
   545  				},
   546  			}
   547  			metrics.Items = append(metrics.Items, podMetric)
   548  		}
   549  
   550  		return true, metrics, nil
   551  	})
   552  
   553  	fakeCMClient := &cmfake.FakeCustomMetricsClient{}
   554  	fakeCMClient.AddReactor("get", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   555  		tc.Lock()
   556  		defer tc.Unlock()
   557  
   558  		getForAction, wasGetFor := action.(cmfake.GetForAction)
   559  		if !wasGetFor {
   560  			return true, nil, fmt.Errorf("expected a get-for action, got %v instead", action)
   561  		}
   562  
   563  		if getForAction.GetName() == "*" {
   564  			metrics := &cmapi.MetricValueList{}
   565  
   566  			// multiple objects
   567  			assert.Equal(t, "pods", getForAction.GetResource().Resource, "the type of object that we requested multiple metrics for should have been pods")
   568  			assert.Equal(t, "qps", getForAction.GetMetricName(), "the metric name requested should have been qps, as specified in the metric spec")
   569  
   570  			for i, level := range tc.reportedLevels {
   571  				podMetric := cmapi.MetricValue{
   572  					DescribedObject: v1.ObjectReference{
   573  						Kind:      "Pod",
   574  						Name:      fmt.Sprintf("%s-%d", podNamePrefix, i),
   575  						Namespace: namespace,
   576  					},
   577  					Timestamp: metav1.Time{Time: time.Now()},
   578  					Metric: cmapi.MetricIdentifier{
   579  						Name: "qps",
   580  					},
   581  					Value: *resource.NewMilliQuantity(int64(level), resource.DecimalSI),
   582  				}
   583  				metrics.Items = append(metrics.Items, podMetric)
   584  			}
   585  
   586  			return true, metrics, nil
   587  		}
   588  
   589  		name := getForAction.GetName()
   590  		mapper := testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme)
   591  		metrics := &cmapi.MetricValueList{}
   592  		var matchedTarget *autoscalingv2.MetricSpec
   593  		for i, target := range tc.metricsTarget {
   594  			if target.Type == autoscalingv2.ObjectMetricSourceType && name == target.Object.DescribedObject.Name {
   595  				gk := schema.FromAPIVersionAndKind(target.Object.DescribedObject.APIVersion, target.Object.DescribedObject.Kind).GroupKind()
   596  				mapping, err := mapper.RESTMapping(gk)
   597  				if err != nil {
   598  					t.Logf("unable to get mapping for %s: %v", gk.String(), err)
   599  					continue
   600  				}
   601  				groupResource := mapping.Resource.GroupResource()
   602  
   603  				if getForAction.GetResource().Resource == groupResource.String() {
   604  					matchedTarget = &tc.metricsTarget[i]
   605  				}
   606  			}
   607  		}
   608  		assert.NotNil(t, matchedTarget, "this request should have matched one of the metric specs")
   609  		assert.Equal(t, "qps", getForAction.GetMetricName(), "the metric name requested should have been qps, as specified in the metric spec")
   610  
   611  		metrics.Items = []cmapi.MetricValue{
   612  			{
   613  				DescribedObject: v1.ObjectReference{
   614  					Kind:       matchedTarget.Object.DescribedObject.Kind,
   615  					APIVersion: matchedTarget.Object.DescribedObject.APIVersion,
   616  					Name:       name,
   617  				},
   618  				Timestamp: metav1.Time{Time: time.Now()},
   619  				Metric: cmapi.MetricIdentifier{
   620  					Name: "qps",
   621  				},
   622  				Value: *resource.NewMilliQuantity(int64(tc.reportedLevels[0]), resource.DecimalSI),
   623  			},
   624  		}
   625  
   626  		return true, metrics, nil
   627  	})
   628  
   629  	fakeEMClient := &emfake.FakeExternalMetricsClient{}
   630  
   631  	fakeEMClient.AddReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   632  		tc.Lock()
   633  		defer tc.Unlock()
   634  
   635  		listAction, wasList := action.(core.ListAction)
   636  		if !wasList {
   637  			return true, nil, fmt.Errorf("expected a list action, got %v instead", action)
   638  		}
   639  
   640  		metrics := &emapi.ExternalMetricValueList{}
   641  
   642  		assert.Equal(t, "qps", listAction.GetResource().Resource, "the metric name requested should have been qps, as specified in the metric spec")
   643  
   644  		for _, level := range tc.reportedLevels {
   645  			metric := emapi.ExternalMetricValue{
   646  				Timestamp:  metav1.Time{Time: time.Now()},
   647  				MetricName: "qps",
   648  				Value:      *resource.NewMilliQuantity(int64(level), resource.DecimalSI),
   649  			}
   650  			metrics.Items = append(metrics.Items, metric)
   651  		}
   652  
   653  		return true, metrics, nil
   654  	})
   655  
   656  	return fakeClient, fakeMetricsClient, fakeCMClient, fakeEMClient, fakeScaleClient
   657  }
   658  
   659  func findCpuUtilization(metricStatus []autoscalingv2.MetricStatus) (utilization *int32) {
   660  	for _, s := range metricStatus {
   661  		if s.Type != autoscalingv2.ResourceMetricSourceType {
   662  			continue
   663  		}
   664  		if s.Resource == nil {
   665  			continue
   666  		}
   667  		if s.Resource.Name != v1.ResourceCPU {
   668  			continue
   669  		}
   670  		if s.Resource.Current.AverageUtilization == nil {
   671  			continue
   672  		}
   673  		return s.Resource.Current.AverageUtilization
   674  	}
   675  	return nil
   676  }
   677  
   678  func (tc *testCase) verifyResults(t *testing.T, m *mockMonitor) {
   679  	tc.Lock()
   680  	defer tc.Unlock()
   681  
   682  	assert.Equal(t, tc.specReplicas != tc.expectedDesiredReplicas, tc.scaleUpdated, "the scale should only be updated if we expected a change in replicas")
   683  	assert.True(t, tc.statusUpdated, "the status should have been updated")
   684  	if tc.verifyEvents {
   685  		assert.Equal(t, tc.specReplicas != tc.expectedDesiredReplicas, tc.eventCreated, "an event should have been created only if we expected a change in replicas")
   686  	}
   687  
   688  	tc.verifyRecordedMetric(t, m)
   689  }
   690  
   691  func (tc *testCase) verifyRecordedMetric(t *testing.T, m *mockMonitor) {
   692  	// First, wait for the reconciliation completed at least once.
   693  	m.waitUntilRecorded(t)
   694  
   695  	assert.Equal(t, tc.expectedReportedReconciliationActionLabel, m.reconciliationActionLabels[0], "the reconciliation action should be recorded in monitor expectedly")
   696  	assert.Equal(t, tc.expectedReportedReconciliationErrorLabel, m.reconciliationErrorLabels[0], "the reconciliation error should be recorded in monitor expectedly")
   697  
   698  	if len(tc.expectedReportedMetricComputationActionLabels) != len(m.metricComputationActionLabels) {
   699  		t.Fatalf("the metric computation actions for %d types should be recorded, but actually only %d was recorded", len(tc.expectedReportedMetricComputationActionLabels), len(m.metricComputationActionLabels))
   700  	}
   701  	if len(tc.expectedReportedMetricComputationErrorLabels) != len(m.metricComputationErrorLabels) {
   702  		t.Fatalf("the metric computation errors for %d types should be recorded, but actually only %d was recorded", len(tc.expectedReportedMetricComputationErrorLabels), len(m.metricComputationErrorLabels))
   703  	}
   704  
   705  	for metricType, l := range tc.expectedReportedMetricComputationActionLabels {
   706  		_, ok := m.metricComputationActionLabels[metricType]
   707  		if !ok {
   708  			t.Fatalf("the metric computation action should be recorded with metricType %s, but actually nothing was recorded", metricType)
   709  		}
   710  		assert.Equal(t, l, m.metricComputationActionLabels[metricType][0], "the metric computation action should be recorded in monitor expectedly")
   711  	}
   712  	for metricType, l := range tc.expectedReportedMetricComputationErrorLabels {
   713  		_, ok := m.metricComputationErrorLabels[metricType]
   714  		if !ok {
   715  			t.Fatalf("the metric computation error should be recorded with metricType %s, but actually nothing was recorded", metricType)
   716  		}
   717  		assert.Equal(t, l, m.metricComputationErrorLabels[metricType][0], "the metric computation error should be recorded in monitor expectedly")
   718  	}
   719  }
   720  
   721  func (tc *testCase) setupController(t *testing.T) (*HorizontalController, informers.SharedInformerFactory) {
   722  	testClient, testMetricsClient, testCMClient, testEMClient, testScaleClient := tc.prepareTestClient(t)
   723  	if tc.testClient != nil {
   724  		testClient = tc.testClient
   725  	}
   726  	if tc.testMetricsClient != nil {
   727  		testMetricsClient = tc.testMetricsClient
   728  	}
   729  	if tc.testCMClient != nil {
   730  		testCMClient = tc.testCMClient
   731  	}
   732  	if tc.testEMClient != nil {
   733  		testEMClient = tc.testEMClient
   734  	}
   735  	if tc.testScaleClient != nil {
   736  		testScaleClient = tc.testScaleClient
   737  	}
   738  	metricsClient := metrics.NewRESTMetricsClient(
   739  		testMetricsClient.MetricsV1beta1(),
   740  		testCMClient,
   741  		testEMClient,
   742  	)
   743  
   744  	eventClient := &fake.Clientset{}
   745  	eventClient.AddReactor("create", "events", func(action core.Action) (handled bool, ret runtime.Object, err error) {
   746  		tc.Lock()
   747  		defer tc.Unlock()
   748  
   749  		obj := action.(core.CreateAction).GetObject().(*v1.Event)
   750  		if tc.verifyEvents {
   751  			switch obj.Reason {
   752  			case "SuccessfulRescale":
   753  				assert.Equal(t, fmt.Sprintf("New size: %d; reason: cpu resource utilization (percentage of request) above target", tc.expectedDesiredReplicas), obj.Message)
   754  			case "DesiredReplicasComputed":
   755  				assert.Equal(t, fmt.Sprintf(
   756  					"Computed the desired num of replicas: %d (avgCPUutil: %d, current replicas: %d)",
   757  					tc.expectedDesiredReplicas,
   758  					(int64(tc.reportedLevels[0])*100)/tc.reportedCPURequests[0].MilliValue(), tc.specReplicas), obj.Message)
   759  			default:
   760  				assert.False(t, true, fmt.Sprintf("Unexpected event: %s / %s", obj.Reason, obj.Message))
   761  			}
   762  		}
   763  		tc.eventCreated = true
   764  		return true, obj, nil
   765  	})
   766  
   767  	informerFactory := informers.NewSharedInformerFactory(testClient, controller.NoResyncPeriodFunc())
   768  	defaultDownscalestabilizationWindow := 5 * time.Minute
   769  
   770  	tCtx := ktesting.Init(t)
   771  	hpaController := NewHorizontalController(
   772  		tCtx,
   773  		eventClient.CoreV1(),
   774  		testScaleClient,
   775  		testClient.AutoscalingV2(),
   776  		testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme),
   777  		metricsClient,
   778  		informerFactory.Autoscaling().V2().HorizontalPodAutoscalers(),
   779  		informerFactory.Core().V1().Pods(),
   780  		100*time.Millisecond, // we need non-zero resync period to avoid race conditions
   781  		defaultDownscalestabilizationWindow,
   782  		defaultTestingTolerance,
   783  		defaultTestingCPUInitializationPeriod,
   784  		defaultTestingDelayOfInitialReadinessStatus,
   785  	)
   786  	hpaController.hpaListerSynced = alwaysReady
   787  	if tc.recommendations != nil {
   788  		hpaController.recommendations["test-namespace/test-hpa"] = tc.recommendations
   789  	}
   790  	if tc.hpaSelectors != nil {
   791  		hpaController.hpaSelectors = tc.hpaSelectors
   792  	}
   793  
   794  	hpaController.monitor = newMockMonitor()
   795  	return hpaController, informerFactory
   796  }
   797  
   798  func hotCPUCreationTime() metav1.Time {
   799  	return metav1.Time{Time: time.Now()}
   800  }
   801  
   802  func coolCPUCreationTime() metav1.Time {
   803  	return metav1.Time{Time: time.Now().Add(-3 * time.Minute)}
   804  }
   805  
   806  func (tc *testCase) runTestWithController(t *testing.T, hpaController *HorizontalController, informerFactory informers.SharedInformerFactory) {
   807  	ctx, cancel := context.WithCancel(context.Background())
   808  	defer cancel()
   809  	informerFactory.Start(ctx.Done())
   810  	go hpaController.Run(ctx, 5)
   811  
   812  	tc.Lock()
   813  	shouldWait := tc.verifyEvents
   814  	tc.Unlock()
   815  
   816  	if shouldWait {
   817  		// We need to wait for events to be broadcasted (sleep for longer than record.sleepDuration).
   818  		timeoutTime := time.Now().Add(2 * time.Second)
   819  		for now := time.Now(); timeoutTime.After(now); now = time.Now() {
   820  			sleepUntil := timeoutTime.Sub(now)
   821  			select {
   822  			case <-tc.processed:
   823  				// drain the chan of any sent events to keep it from filling before the timeout
   824  			case <-time.After(sleepUntil):
   825  				// timeout reached, ready to verifyResults
   826  			}
   827  		}
   828  	} else {
   829  		// Wait for HPA to be processed.
   830  		<-tc.processed
   831  	}
   832  	m, ok := hpaController.monitor.(*mockMonitor)
   833  	if !ok {
   834  		t.Fatalf("test HPA controller should have mockMonitor, but actually not")
   835  	}
   836  	tc.verifyResults(t, m)
   837  }
   838  
   839  func (tc *testCase) runTest(t *testing.T) {
   840  	hpaController, informerFactory := tc.setupController(t)
   841  	tc.runTestWithController(t, hpaController, informerFactory)
   842  }
   843  
   844  // mockMonitor implements monitor.Monitor interface.
   845  // It records which results are observed in slices.
   846  type mockMonitor struct {
   847  	sync.RWMutex
   848  	reconciliationActionLabels []monitor.ActionLabel
   849  	reconciliationErrorLabels  []monitor.ErrorLabel
   850  
   851  	metricComputationActionLabels map[autoscalingv2.MetricSourceType][]monitor.ActionLabel
   852  	metricComputationErrorLabels  map[autoscalingv2.MetricSourceType][]monitor.ErrorLabel
   853  }
   854  
   855  func newMockMonitor() *mockMonitor {
   856  	return &mockMonitor{
   857  		metricComputationActionLabels: make(map[autoscalingv2.MetricSourceType][]monitor.ActionLabel),
   858  		metricComputationErrorLabels:  make(map[autoscalingv2.MetricSourceType][]monitor.ErrorLabel),
   859  	}
   860  }
   861  
   862  func (m *mockMonitor) ObserveReconciliationResult(action monitor.ActionLabel, err monitor.ErrorLabel, _ time.Duration) {
   863  	m.Lock()
   864  	defer m.Unlock()
   865  	m.reconciliationActionLabels = append(m.reconciliationActionLabels, action)
   866  	m.reconciliationErrorLabels = append(m.reconciliationErrorLabels, err)
   867  }
   868  
   869  func (m *mockMonitor) ObserveMetricComputationResult(action monitor.ActionLabel, err monitor.ErrorLabel, duration time.Duration, metricType autoscalingv2.MetricSourceType) {
   870  	m.Lock()
   871  	defer m.Unlock()
   872  
   873  	m.metricComputationActionLabels[metricType] = append(m.metricComputationActionLabels[metricType], action)
   874  	m.metricComputationErrorLabels[metricType] = append(m.metricComputationErrorLabels[metricType], err)
   875  }
   876  
   877  // waitUntilRecorded waits for the HPA controller to reconcile at least once.
   878  func (m *mockMonitor) waitUntilRecorded(t *testing.T) {
   879  	if err := wait.Poll(20*time.Millisecond, 100*time.Millisecond, func() (done bool, err error) {
   880  		m.RWMutex.RLock()
   881  		defer m.RWMutex.RUnlock()
   882  		if len(m.reconciliationActionLabels) == 0 || len(m.reconciliationErrorLabels) == 0 {
   883  			return false, nil
   884  		}
   885  		return true, nil
   886  	}); err != nil {
   887  		t.Fatalf("no reconciliation is recorded in the monitor, len(monitor.reconciliationActionLabels)=%v len(monitor.reconciliationErrorLabels)=%v ", len(m.reconciliationActionLabels), len(m.reconciliationErrorLabels))
   888  	}
   889  }
   890  
   891  func TestScaleUp(t *testing.T) {
   892  	tc := testCase{
   893  		minReplicas:             2,
   894  		maxReplicas:             6,
   895  		specReplicas:            3,
   896  		statusReplicas:          3,
   897  		expectedDesiredReplicas: 5,
   898  		CPUTarget:               30,
   899  		verifyCPUCurrent:        true,
   900  		reportedLevels:          []uint64{300, 500, 700},
   901  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
   902  		useMetricsAPI:           true,
   903  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
   904  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
   905  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
   906  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
   907  		},
   908  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
   909  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
   910  		},
   911  	}
   912  	tc.runTest(t)
   913  }
   914  
   915  func TestScaleUpContainer(t *testing.T) {
   916  	tc := testCase{
   917  		minReplicas:             2,
   918  		maxReplicas:             6,
   919  		specReplicas:            3,
   920  		statusReplicas:          3,
   921  		expectedDesiredReplicas: 5,
   922  		metricsTarget: []autoscalingv2.MetricSpec{{
   923  			Type: autoscalingv2.ContainerResourceMetricSourceType,
   924  			ContainerResource: &autoscalingv2.ContainerResourceMetricSource{
   925  				Name: v1.ResourceCPU,
   926  				Target: autoscalingv2.MetricTarget{
   927  					Type:               autoscalingv2.UtilizationMetricType,
   928  					AverageUtilization: pointer.Int32(30),
   929  				},
   930  				Container: "container1",
   931  			},
   932  		}},
   933  		reportedLevels:      []uint64{300, 500, 700},
   934  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
   935  		useMetricsAPI:       true,
   936  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
   937  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
   938  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
   939  			autoscalingv2.ContainerResourceMetricSourceType: monitor.ActionLabelScaleUp,
   940  		},
   941  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
   942  			autoscalingv2.ContainerResourceMetricSourceType: monitor.ErrorLabelNone,
   943  		},
   944  	}
   945  	tc.runTest(t)
   946  }
   947  
   948  func TestScaleUpUnreadyLessScale(t *testing.T) {
   949  	tc := testCase{
   950  		minReplicas:             2,
   951  		maxReplicas:             6,
   952  		specReplicas:            3,
   953  		statusReplicas:          3,
   954  		expectedDesiredReplicas: 4,
   955  		CPUTarget:               30,
   956  		CPUCurrent:              60,
   957  		verifyCPUCurrent:        true,
   958  		reportedLevels:          []uint64{300, 500, 700},
   959  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
   960  		reportedPodReadiness:    []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue},
   961  		useMetricsAPI:           true,
   962  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
   963  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
   964  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
   965  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
   966  		},
   967  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
   968  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
   969  		},
   970  	}
   971  	tc.runTest(t)
   972  }
   973  
   974  func TestScaleUpHotCpuLessScale(t *testing.T) {
   975  	tc := testCase{
   976  		minReplicas:             2,
   977  		maxReplicas:             6,
   978  		specReplicas:            3,
   979  		statusReplicas:          3,
   980  		expectedDesiredReplicas: 4,
   981  		CPUTarget:               30,
   982  		CPUCurrent:              60,
   983  		verifyCPUCurrent:        true,
   984  		reportedLevels:          []uint64{300, 500, 700},
   985  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
   986  		reportedPodStartTime:    []metav1.Time{hotCPUCreationTime(), coolCPUCreationTime(), coolCPUCreationTime()},
   987  		useMetricsAPI:           true,
   988  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
   989  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
   990  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
   991  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
   992  		},
   993  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
   994  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
   995  		},
   996  	}
   997  	tc.runTest(t)
   998  }
   999  
  1000  func TestScaleUpUnreadyNoScale(t *testing.T) {
  1001  	tc := testCase{
  1002  		minReplicas:             2,
  1003  		maxReplicas:             6,
  1004  		specReplicas:            3,
  1005  		statusReplicas:          3,
  1006  		expectedDesiredReplicas: 3,
  1007  		CPUTarget:               30,
  1008  		CPUCurrent:              40,
  1009  		verifyCPUCurrent:        true,
  1010  		reportedLevels:          []uint64{400, 500, 700},
  1011  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1012  		reportedPodReadiness:    []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1013  		useMetricsAPI:           true,
  1014  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1015  			Type:   autoscalingv2.AbleToScale,
  1016  			Status: v1.ConditionTrue,
  1017  			Reason: "ReadyForNewScale",
  1018  		}),
  1019  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  1020  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1021  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1022  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone,
  1023  		},
  1024  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1025  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1026  		},
  1027  	}
  1028  	tc.runTest(t)
  1029  }
  1030  
  1031  func TestScaleUpHotCpuNoScale(t *testing.T) {
  1032  	tc := testCase{
  1033  		minReplicas:             2,
  1034  		maxReplicas:             6,
  1035  		specReplicas:            3,
  1036  		statusReplicas:          3,
  1037  		expectedDesiredReplicas: 3,
  1038  		CPUTarget:               30,
  1039  		CPUCurrent:              40,
  1040  		verifyCPUCurrent:        true,
  1041  		reportedLevels:          []uint64{400, 500, 700},
  1042  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1043  		reportedPodReadiness:    []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1044  		reportedPodStartTime:    []metav1.Time{coolCPUCreationTime(), hotCPUCreationTime(), hotCPUCreationTime()},
  1045  		useMetricsAPI:           true,
  1046  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1047  			Type:   autoscalingv2.AbleToScale,
  1048  			Status: v1.ConditionTrue,
  1049  			Reason: "ReadyForNewScale",
  1050  		}),
  1051  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  1052  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1053  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1054  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone,
  1055  		},
  1056  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1057  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1058  		},
  1059  	}
  1060  	tc.runTest(t)
  1061  }
  1062  
  1063  func TestScaleUpIgnoresFailedPods(t *testing.T) {
  1064  	tc := testCase{
  1065  		minReplicas:             2,
  1066  		maxReplicas:             6,
  1067  		specReplicas:            2,
  1068  		statusReplicas:          2,
  1069  		expectedDesiredReplicas: 4,
  1070  		CPUTarget:               30,
  1071  		CPUCurrent:              60,
  1072  		verifyCPUCurrent:        true,
  1073  		reportedLevels:          []uint64{500, 700},
  1074  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1075  		reportedPodReadiness:    []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1076  		reportedPodPhase:        []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodFailed, v1.PodFailed},
  1077  		useMetricsAPI:           true,
  1078  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1079  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1080  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1081  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  1082  		},
  1083  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1084  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1085  		},
  1086  	}
  1087  	tc.runTest(t)
  1088  }
  1089  
  1090  func TestScaleUpIgnoresDeletionPods(t *testing.T) {
  1091  	tc := testCase{
  1092  		minReplicas:                  2,
  1093  		maxReplicas:                  6,
  1094  		specReplicas:                 2,
  1095  		statusReplicas:               2,
  1096  		expectedDesiredReplicas:      4,
  1097  		CPUTarget:                    30,
  1098  		CPUCurrent:                   60,
  1099  		verifyCPUCurrent:             true,
  1100  		reportedLevels:               []uint64{500, 700},
  1101  		reportedCPURequests:          []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1102  		reportedPodReadiness:         []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1103  		reportedPodPhase:             []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning},
  1104  		reportedPodDeletionTimestamp: []bool{false, false, true, true},
  1105  		useMetricsAPI:                true,
  1106  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1107  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1108  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1109  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  1110  		},
  1111  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1112  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1113  		},
  1114  	}
  1115  	tc.runTest(t)
  1116  }
  1117  
  1118  func TestScaleUpDeployment(t *testing.T) {
  1119  	tc := testCase{
  1120  		minReplicas:             2,
  1121  		maxReplicas:             6,
  1122  		specReplicas:            3,
  1123  		statusReplicas:          3,
  1124  		expectedDesiredReplicas: 5,
  1125  		CPUTarget:               30,
  1126  		verifyCPUCurrent:        true,
  1127  		reportedLevels:          []uint64{300, 500, 700},
  1128  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1129  		useMetricsAPI:           true,
  1130  		resource: &fakeResource{
  1131  			name:       "test-dep",
  1132  			apiVersion: "apps/v1",
  1133  			kind:       "Deployment",
  1134  		},
  1135  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1136  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1137  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1138  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  1139  		},
  1140  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1141  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1142  		},
  1143  	}
  1144  	tc.runTest(t)
  1145  }
  1146  
  1147  func TestScaleUpReplicaSet(t *testing.T) {
  1148  	tc := testCase{
  1149  		minReplicas:             2,
  1150  		maxReplicas:             6,
  1151  		specReplicas:            3,
  1152  		statusReplicas:          3,
  1153  		expectedDesiredReplicas: 5,
  1154  		CPUTarget:               30,
  1155  		verifyCPUCurrent:        true,
  1156  		reportedLevels:          []uint64{300, 500, 700},
  1157  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1158  		useMetricsAPI:           true,
  1159  		resource: &fakeResource{
  1160  			name:       "test-replicaset",
  1161  			apiVersion: "apps/v1",
  1162  			kind:       "ReplicaSet",
  1163  		},
  1164  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1165  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1166  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1167  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  1168  		},
  1169  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1170  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1171  		},
  1172  	}
  1173  	tc.runTest(t)
  1174  }
  1175  
  1176  func TestScaleUpCM(t *testing.T) {
  1177  	averageValue := resource.MustParse("15.0")
  1178  	tc := testCase{
  1179  		minReplicas:             2,
  1180  		maxReplicas:             6,
  1181  		specReplicas:            3,
  1182  		statusReplicas:          3,
  1183  		expectedDesiredReplicas: 4,
  1184  		CPUTarget:               0,
  1185  		metricsTarget: []autoscalingv2.MetricSpec{
  1186  			{
  1187  				Type: autoscalingv2.PodsMetricSourceType,
  1188  				Pods: &autoscalingv2.PodsMetricSource{
  1189  					Metric: autoscalingv2.MetricIdentifier{
  1190  						Name: "qps",
  1191  					},
  1192  					Target: autoscalingv2.MetricTarget{
  1193  						Type:         autoscalingv2.AverageValueMetricType,
  1194  						AverageValue: &averageValue,
  1195  					},
  1196  				},
  1197  			},
  1198  		},
  1199  		reportedLevels:      []uint64{20000, 10000, 30000},
  1200  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1201  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1202  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1203  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1204  			autoscalingv2.PodsMetricSourceType: monitor.ActionLabelScaleUp,
  1205  		},
  1206  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1207  			autoscalingv2.PodsMetricSourceType: monitor.ErrorLabelNone,
  1208  		},
  1209  	}
  1210  	tc.runTest(t)
  1211  }
  1212  
  1213  func TestScaleUpCMUnreadyAndHotCpuNoLessScale(t *testing.T) {
  1214  	averageValue := resource.MustParse("15.0")
  1215  	tc := testCase{
  1216  		minReplicas:             2,
  1217  		maxReplicas:             6,
  1218  		specReplicas:            3,
  1219  		statusReplicas:          3,
  1220  		expectedDesiredReplicas: 6,
  1221  		CPUTarget:               0,
  1222  		metricsTarget: []autoscalingv2.MetricSpec{
  1223  			{
  1224  				Type: autoscalingv2.PodsMetricSourceType,
  1225  				Pods: &autoscalingv2.PodsMetricSource{
  1226  					Metric: autoscalingv2.MetricIdentifier{
  1227  						Name: "qps",
  1228  					},
  1229  					Target: autoscalingv2.MetricTarget{
  1230  						Type:         autoscalingv2.AverageValueMetricType,
  1231  						AverageValue: &averageValue,
  1232  					},
  1233  				},
  1234  			},
  1235  		},
  1236  		reportedLevels:                            []uint64{50000, 10000, 30000},
  1237  		reportedPodReadiness:                      []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse},
  1238  		reportedPodStartTime:                      []metav1.Time{coolCPUCreationTime(), coolCPUCreationTime(), hotCPUCreationTime()},
  1239  		reportedCPURequests:                       []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1240  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1241  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1242  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1243  			autoscalingv2.PodsMetricSourceType: monitor.ActionLabelScaleUp,
  1244  		},
  1245  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1246  			autoscalingv2.PodsMetricSourceType: monitor.ErrorLabelNone,
  1247  		},
  1248  	}
  1249  	tc.runTest(t)
  1250  }
  1251  
  1252  func TestScaleUpCMUnreadyandCpuHot(t *testing.T) {
  1253  	averageValue := resource.MustParse("15.0")
  1254  	tc := testCase{
  1255  		minReplicas:             2,
  1256  		maxReplicas:             6,
  1257  		specReplicas:            3,
  1258  		statusReplicas:          3,
  1259  		expectedDesiredReplicas: 6,
  1260  		CPUTarget:               0,
  1261  		metricsTarget: []autoscalingv2.MetricSpec{
  1262  			{
  1263  				Type: autoscalingv2.PodsMetricSourceType,
  1264  				Pods: &autoscalingv2.PodsMetricSource{
  1265  					Metric: autoscalingv2.MetricIdentifier{
  1266  						Name: "qps",
  1267  					},
  1268  					Target: autoscalingv2.MetricTarget{
  1269  						Type:         autoscalingv2.AverageValueMetricType,
  1270  						AverageValue: &averageValue,
  1271  					},
  1272  				},
  1273  			},
  1274  		},
  1275  		reportedLevels:       []uint64{50000, 15000, 30000},
  1276  		reportedPodReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionFalse},
  1277  		reportedPodStartTime: []metav1.Time{hotCPUCreationTime(), coolCPUCreationTime(), hotCPUCreationTime()},
  1278  		reportedCPURequests:  []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1279  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1280  			Type:   autoscalingv2.AbleToScale,
  1281  			Status: v1.ConditionTrue,
  1282  			Reason: "SucceededRescale",
  1283  		}, autoscalingv2.HorizontalPodAutoscalerCondition{
  1284  			Type:   autoscalingv2.ScalingLimited,
  1285  			Status: v1.ConditionTrue,
  1286  			Reason: "TooManyReplicas",
  1287  		}),
  1288  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1289  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1290  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1291  			autoscalingv2.PodsMetricSourceType: monitor.ActionLabelScaleUp,
  1292  		},
  1293  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1294  			autoscalingv2.PodsMetricSourceType: monitor.ErrorLabelNone,
  1295  		},
  1296  	}
  1297  	tc.runTest(t)
  1298  }
  1299  
  1300  func TestScaleUpHotCpuNoScaleWouldScaleDown(t *testing.T) {
  1301  	averageValue := resource.MustParse("15.0")
  1302  	tc := testCase{
  1303  		minReplicas:             2,
  1304  		maxReplicas:             6,
  1305  		specReplicas:            3,
  1306  		statusReplicas:          3,
  1307  		expectedDesiredReplicas: 6,
  1308  		CPUTarget:               0,
  1309  		metricsTarget: []autoscalingv2.MetricSpec{
  1310  			{
  1311  				Type: autoscalingv2.PodsMetricSourceType,
  1312  				Pods: &autoscalingv2.PodsMetricSource{
  1313  					Metric: autoscalingv2.MetricIdentifier{
  1314  						Name: "qps",
  1315  					},
  1316  					Target: autoscalingv2.MetricTarget{
  1317  						Type:         autoscalingv2.AverageValueMetricType,
  1318  						AverageValue: &averageValue,
  1319  					},
  1320  				},
  1321  			},
  1322  		},
  1323  		reportedLevels:       []uint64{50000, 15000, 30000},
  1324  		reportedCPURequests:  []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1325  		reportedPodStartTime: []metav1.Time{hotCPUCreationTime(), coolCPUCreationTime(), hotCPUCreationTime()},
  1326  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1327  			Type:   autoscalingv2.AbleToScale,
  1328  			Status: v1.ConditionTrue,
  1329  			Reason: "SucceededRescale",
  1330  		}, autoscalingv2.HorizontalPodAutoscalerCondition{
  1331  			Type:   autoscalingv2.ScalingLimited,
  1332  			Status: v1.ConditionTrue,
  1333  			Reason: "TooManyReplicas",
  1334  		}),
  1335  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1336  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1337  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1338  			autoscalingv2.PodsMetricSourceType: monitor.ActionLabelScaleUp,
  1339  		},
  1340  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1341  			autoscalingv2.PodsMetricSourceType: monitor.ErrorLabelNone,
  1342  		},
  1343  	}
  1344  	tc.runTest(t)
  1345  }
  1346  
  1347  func TestScaleUpCMObject(t *testing.T) {
  1348  	targetValue := resource.MustParse("15.0")
  1349  	tc := testCase{
  1350  		minReplicas:             2,
  1351  		maxReplicas:             6,
  1352  		specReplicas:            3,
  1353  		statusReplicas:          3,
  1354  		expectedDesiredReplicas: 4,
  1355  		CPUTarget:               0,
  1356  		metricsTarget: []autoscalingv2.MetricSpec{
  1357  			{
  1358  				Type: autoscalingv2.ObjectMetricSourceType,
  1359  				Object: &autoscalingv2.ObjectMetricSource{
  1360  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1361  						APIVersion: "apps/v1",
  1362  						Kind:       "Deployment",
  1363  						Name:       "some-deployment",
  1364  					},
  1365  					Metric: autoscalingv2.MetricIdentifier{
  1366  						Name: "qps",
  1367  					},
  1368  					Target: autoscalingv2.MetricTarget{
  1369  						Type:  autoscalingv2.ValueMetricType,
  1370  						Value: &targetValue,
  1371  					},
  1372  				},
  1373  			},
  1374  		},
  1375  		reportedLevels: []uint64{20000},
  1376  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1377  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1378  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1379  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp,
  1380  		},
  1381  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1382  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  1383  		},
  1384  	}
  1385  	tc.runTest(t)
  1386  }
  1387  
  1388  func TestScaleUpFromZeroCMObject(t *testing.T) {
  1389  	targetValue := resource.MustParse("15.0")
  1390  	tc := testCase{
  1391  		minReplicas:             0,
  1392  		maxReplicas:             6,
  1393  		specReplicas:            0,
  1394  		statusReplicas:          0,
  1395  		expectedDesiredReplicas: 2,
  1396  		CPUTarget:               0,
  1397  		metricsTarget: []autoscalingv2.MetricSpec{
  1398  			{
  1399  				Type: autoscalingv2.ObjectMetricSourceType,
  1400  				Object: &autoscalingv2.ObjectMetricSource{
  1401  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1402  						APIVersion: "apps/v1",
  1403  						Kind:       "Deployment",
  1404  						Name:       "some-deployment",
  1405  					},
  1406  					Metric: autoscalingv2.MetricIdentifier{
  1407  						Name: "qps",
  1408  					},
  1409  					Target: autoscalingv2.MetricTarget{
  1410  						Type:  autoscalingv2.ValueMetricType,
  1411  						Value: &targetValue,
  1412  					},
  1413  				},
  1414  			},
  1415  		},
  1416  		reportedLevels: []uint64{20000},
  1417  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1418  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1419  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1420  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp,
  1421  		},
  1422  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1423  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  1424  		},
  1425  	}
  1426  	tc.runTest(t)
  1427  }
  1428  
  1429  func TestScaleUpFromZeroIgnoresToleranceCMObject(t *testing.T) {
  1430  	targetValue := resource.MustParse("1.0")
  1431  	tc := testCase{
  1432  		minReplicas:             0,
  1433  		maxReplicas:             6,
  1434  		specReplicas:            0,
  1435  		statusReplicas:          0,
  1436  		expectedDesiredReplicas: 1,
  1437  		CPUTarget:               0,
  1438  		metricsTarget: []autoscalingv2.MetricSpec{
  1439  			{
  1440  				Type: autoscalingv2.ObjectMetricSourceType,
  1441  				Object: &autoscalingv2.ObjectMetricSource{
  1442  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1443  						APIVersion: "apps/v1",
  1444  						Kind:       "Deployment",
  1445  						Name:       "some-deployment",
  1446  					},
  1447  					Metric: autoscalingv2.MetricIdentifier{
  1448  						Name: "qps",
  1449  					},
  1450  					Target: autoscalingv2.MetricTarget{
  1451  						Type:  autoscalingv2.ValueMetricType,
  1452  						Value: &targetValue,
  1453  					},
  1454  				},
  1455  			},
  1456  		},
  1457  		reportedLevels: []uint64{1000},
  1458  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1459  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1460  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1461  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp,
  1462  		},
  1463  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1464  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  1465  		},
  1466  	}
  1467  	tc.runTest(t)
  1468  }
  1469  
  1470  func TestScaleUpPerPodCMObject(t *testing.T) {
  1471  	targetAverageValue := resource.MustParse("10.0")
  1472  	tc := testCase{
  1473  		minReplicas:             2,
  1474  		maxReplicas:             6,
  1475  		specReplicas:            3,
  1476  		statusReplicas:          3,
  1477  		expectedDesiredReplicas: 4,
  1478  		CPUTarget:               0,
  1479  		metricsTarget: []autoscalingv2.MetricSpec{
  1480  			{
  1481  				Type: autoscalingv2.ObjectMetricSourceType,
  1482  				Object: &autoscalingv2.ObjectMetricSource{
  1483  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1484  						APIVersion: "apps/v1",
  1485  						Kind:       "Deployment",
  1486  						Name:       "some-deployment",
  1487  					},
  1488  					Metric: autoscalingv2.MetricIdentifier{
  1489  						Name: "qps",
  1490  					},
  1491  					Target: autoscalingv2.MetricTarget{
  1492  						Type:         autoscalingv2.AverageValueMetricType,
  1493  						AverageValue: &targetAverageValue,
  1494  					},
  1495  				},
  1496  			},
  1497  		},
  1498  		reportedLevels: []uint64{40000},
  1499  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1500  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1501  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1502  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleUp,
  1503  		},
  1504  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1505  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  1506  		},
  1507  	}
  1508  	tc.runTest(t)
  1509  }
  1510  
  1511  func TestScaleUpCMExternal(t *testing.T) {
  1512  	tc := testCase{
  1513  		minReplicas:             2,
  1514  		maxReplicas:             6,
  1515  		specReplicas:            3,
  1516  		statusReplicas:          3,
  1517  		expectedDesiredReplicas: 4,
  1518  		metricsTarget: []autoscalingv2.MetricSpec{
  1519  			{
  1520  				Type: autoscalingv2.ExternalMetricSourceType,
  1521  				External: &autoscalingv2.ExternalMetricSource{
  1522  					Metric: autoscalingv2.MetricIdentifier{
  1523  						Name:     "qps",
  1524  						Selector: &metav1.LabelSelector{},
  1525  					},
  1526  					Target: autoscalingv2.MetricTarget{
  1527  						Type:  autoscalingv2.ValueMetricType,
  1528  						Value: resource.NewMilliQuantity(6666, resource.DecimalSI),
  1529  					},
  1530  				},
  1531  			},
  1532  		},
  1533  		reportedLevels: []uint64{8600},
  1534  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1535  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1536  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1537  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelScaleUp,
  1538  		},
  1539  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1540  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone,
  1541  		},
  1542  	}
  1543  	tc.runTest(t)
  1544  }
  1545  
  1546  func TestScaleUpPerPodCMExternal(t *testing.T) {
  1547  	tc := testCase{
  1548  		minReplicas:             2,
  1549  		maxReplicas:             6,
  1550  		specReplicas:            3,
  1551  		statusReplicas:          3,
  1552  		expectedDesiredReplicas: 4,
  1553  		metricsTarget: []autoscalingv2.MetricSpec{
  1554  			{
  1555  				Type: autoscalingv2.ExternalMetricSourceType,
  1556  				External: &autoscalingv2.ExternalMetricSource{
  1557  					Metric: autoscalingv2.MetricIdentifier{
  1558  						Name:     "qps",
  1559  						Selector: &metav1.LabelSelector{},
  1560  					},
  1561  					Target: autoscalingv2.MetricTarget{
  1562  						Type:         autoscalingv2.AverageValueMetricType,
  1563  						AverageValue: resource.NewMilliQuantity(2222, resource.DecimalSI),
  1564  					},
  1565  				},
  1566  			},
  1567  		},
  1568  		reportedLevels: []uint64{8600},
  1569  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1570  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1571  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1572  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelScaleUp,
  1573  		},
  1574  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1575  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone,
  1576  		},
  1577  	}
  1578  	tc.runTest(t)
  1579  }
  1580  
  1581  func TestScaleDown(t *testing.T) {
  1582  	tc := testCase{
  1583  		minReplicas:             2,
  1584  		maxReplicas:             6,
  1585  		specReplicas:            5,
  1586  		statusReplicas:          5,
  1587  		expectedDesiredReplicas: 3,
  1588  		CPUTarget:               50,
  1589  		verifyCPUCurrent:        true,
  1590  		reportedLevels:          []uint64{100, 300, 500, 250, 250},
  1591  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1592  		useMetricsAPI:           true,
  1593  		recommendations:         []timestampedRecommendation{},
  1594  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  1595  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1596  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1597  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  1598  		},
  1599  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1600  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1601  		},
  1602  	}
  1603  	tc.runTest(t)
  1604  }
  1605  
  1606  func TestScaleDownContainerResource(t *testing.T) {
  1607  	tc := testCase{
  1608  		minReplicas:             2,
  1609  		maxReplicas:             6,
  1610  		specReplicas:            5,
  1611  		statusReplicas:          5,
  1612  		expectedDesiredReplicas: 3,
  1613  		reportedLevels:          []uint64{100, 300, 500, 250, 250},
  1614  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1615  		metricsTarget: []autoscalingv2.MetricSpec{{
  1616  			Type: autoscalingv2.ContainerResourceMetricSourceType,
  1617  			ContainerResource: &autoscalingv2.ContainerResourceMetricSource{
  1618  				Container: "container2",
  1619  				Name:      v1.ResourceCPU,
  1620  				Target: autoscalingv2.MetricTarget{
  1621  					Type:               autoscalingv2.UtilizationMetricType,
  1622  					AverageUtilization: pointer.Int32(50),
  1623  				},
  1624  			},
  1625  		}},
  1626  		useMetricsAPI:   true,
  1627  		recommendations: []timestampedRecommendation{},
  1628  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  1629  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1630  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1631  			autoscalingv2.ContainerResourceMetricSourceType: monitor.ActionLabelScaleDown,
  1632  		},
  1633  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1634  			autoscalingv2.ContainerResourceMetricSourceType: monitor.ErrorLabelNone,
  1635  		},
  1636  	}
  1637  	tc.runTest(t)
  1638  }
  1639  
  1640  func TestScaleDownWithScalingRules(t *testing.T) {
  1641  	tc := testCase{
  1642  		minReplicas:             2,
  1643  		maxReplicas:             6,
  1644  		scaleUpRules:            generateScalingRules(0, 0, 100, 15, 30),
  1645  		specReplicas:            5,
  1646  		statusReplicas:          5,
  1647  		expectedDesiredReplicas: 3,
  1648  		CPUTarget:               50,
  1649  		verifyCPUCurrent:        true,
  1650  		reportedLevels:          []uint64{100, 300, 500, 250, 250},
  1651  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1652  		useMetricsAPI:           true,
  1653  		recommendations:         []timestampedRecommendation{},
  1654  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  1655  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1656  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1657  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  1658  		},
  1659  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1660  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1661  		},
  1662  	}
  1663  	tc.runTest(t)
  1664  }
  1665  
  1666  func TestScaleUpOneMetricInvalid(t *testing.T) {
  1667  	tc := testCase{
  1668  		minReplicas:             2,
  1669  		maxReplicas:             6,
  1670  		specReplicas:            3,
  1671  		statusReplicas:          3,
  1672  		expectedDesiredReplicas: 4,
  1673  		CPUTarget:               30,
  1674  		verifyCPUCurrent:        true,
  1675  		metricsTarget: []autoscalingv2.MetricSpec{
  1676  			{
  1677  				Type: "CheddarCheese",
  1678  			},
  1679  		},
  1680  		reportedLevels:      []uint64{300, 400, 500},
  1681  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1682  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1683  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  1684  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1685  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  1686  			// Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded.
  1687  			"CheddarCheese": monitor.ActionLabelNone,
  1688  		},
  1689  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1690  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1691  			// Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded.
  1692  			"CheddarCheese": monitor.ErrorLabelSpec,
  1693  		},
  1694  	}
  1695  	tc.runTest(t)
  1696  }
  1697  
  1698  func TestScaleUpFromZeroOneMetricInvalid(t *testing.T) {
  1699  	tc := testCase{
  1700  		minReplicas:             0,
  1701  		maxReplicas:             6,
  1702  		specReplicas:            0,
  1703  		statusReplicas:          0,
  1704  		expectedDesiredReplicas: 4,
  1705  		CPUTarget:               30,
  1706  		verifyCPUCurrent:        true,
  1707  		metricsTarget: []autoscalingv2.MetricSpec{
  1708  			{
  1709  				Type: "CheddarCheese",
  1710  			},
  1711  		},
  1712  		reportedLevels:      []uint64{300, 400, 500},
  1713  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1714  		recommendations:     []timestampedRecommendation{},
  1715  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  1716  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  1717  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1718  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  1719  			// Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded.
  1720  			"CheddarCheese": monitor.ActionLabelNone,
  1721  		},
  1722  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1723  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1724  			// Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded.
  1725  			"CheddarCheese": monitor.ErrorLabelSpec,
  1726  		},
  1727  	}
  1728  	tc.runTest(t)
  1729  }
  1730  
  1731  func TestScaleUpBothMetricsEmpty(t *testing.T) { // Switch to missing
  1732  	tc := testCase{
  1733  		minReplicas:             2,
  1734  		maxReplicas:             6,
  1735  		specReplicas:            3,
  1736  		statusReplicas:          3,
  1737  		expectedDesiredReplicas: 3,
  1738  		CPUTarget:               0,
  1739  		metricsTarget: []autoscalingv2.MetricSpec{
  1740  			{
  1741  				Type: "CheddarCheese",
  1742  			},
  1743  		},
  1744  		reportedLevels:      []uint64{},
  1745  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1746  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  1747  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1748  			{Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "InvalidMetricSourceType"},
  1749  		},
  1750  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  1751  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  1752  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1753  			// Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded.
  1754  			"CheddarCheese": monitor.ActionLabelNone,
  1755  		},
  1756  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1757  			// Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded.
  1758  			"CheddarCheese": monitor.ErrorLabelSpec,
  1759  		},
  1760  	}
  1761  	tc.runTest(t)
  1762  }
  1763  
  1764  func TestScaleDownStabilizeInitialSize(t *testing.T) {
  1765  	tc := testCase{
  1766  		minReplicas:             2,
  1767  		maxReplicas:             6,
  1768  		specReplicas:            5,
  1769  		statusReplicas:          5,
  1770  		expectedDesiredReplicas: 5,
  1771  		CPUTarget:               50,
  1772  		verifyCPUCurrent:        true,
  1773  		reportedLevels:          []uint64{100, 300, 500, 250, 250},
  1774  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1775  		useMetricsAPI:           true,
  1776  		recommendations:         nil,
  1777  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1778  			Type:   autoscalingv2.AbleToScale,
  1779  			Status: v1.ConditionTrue,
  1780  			Reason: "ReadyForNewScale",
  1781  		}, autoscalingv2.HorizontalPodAutoscalerCondition{
  1782  			Type:   autoscalingv2.AbleToScale,
  1783  			Status: v1.ConditionTrue,
  1784  			Reason: "ScaleDownStabilized",
  1785  		}),
  1786  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  1787  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1788  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1789  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  1790  		},
  1791  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1792  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  1793  		},
  1794  	}
  1795  	tc.runTest(t)
  1796  }
  1797  
  1798  func TestScaleDownCM(t *testing.T) {
  1799  	averageValue := resource.MustParse("20.0")
  1800  	tc := testCase{
  1801  		minReplicas:             2,
  1802  		maxReplicas:             6,
  1803  		specReplicas:            5,
  1804  		statusReplicas:          5,
  1805  		expectedDesiredReplicas: 3,
  1806  		CPUTarget:               0,
  1807  		metricsTarget: []autoscalingv2.MetricSpec{
  1808  			{
  1809  				Type: autoscalingv2.PodsMetricSourceType,
  1810  				Pods: &autoscalingv2.PodsMetricSource{
  1811  					Metric: autoscalingv2.MetricIdentifier{
  1812  						Name: "qps",
  1813  					},
  1814  					Target: autoscalingv2.MetricTarget{
  1815  						Type:         autoscalingv2.AverageValueMetricType,
  1816  						AverageValue: &averageValue,
  1817  					},
  1818  				},
  1819  			},
  1820  		},
  1821  		reportedLevels:      []uint64{12000, 12000, 12000, 12000, 12000},
  1822  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1823  		recommendations:     []timestampedRecommendation{},
  1824  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  1825  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1826  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1827  			autoscalingv2.PodsMetricSourceType: monitor.ActionLabelScaleDown,
  1828  		},
  1829  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1830  			autoscalingv2.PodsMetricSourceType: monitor.ErrorLabelNone,
  1831  		},
  1832  	}
  1833  	tc.runTest(t)
  1834  }
  1835  
  1836  func TestScaleDownCMObject(t *testing.T) {
  1837  	targetValue := resource.MustParse("20.0")
  1838  	tc := testCase{
  1839  		minReplicas:             2,
  1840  		maxReplicas:             6,
  1841  		specReplicas:            5,
  1842  		statusReplicas:          5,
  1843  		expectedDesiredReplicas: 3,
  1844  		CPUTarget:               0,
  1845  		metricsTarget: []autoscalingv2.MetricSpec{
  1846  			{
  1847  				Type: autoscalingv2.ObjectMetricSourceType,
  1848  				Object: &autoscalingv2.ObjectMetricSource{
  1849  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1850  						APIVersion: "apps/v1",
  1851  						Kind:       "Deployment",
  1852  						Name:       "some-deployment",
  1853  					},
  1854  					Metric: autoscalingv2.MetricIdentifier{
  1855  						Name: "qps",
  1856  					},
  1857  					Target: autoscalingv2.MetricTarget{
  1858  						Type:  autoscalingv2.ValueMetricType,
  1859  						Value: &targetValue,
  1860  					},
  1861  				},
  1862  			},
  1863  		},
  1864  		reportedLevels:      []uint64{12000},
  1865  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1866  		recommendations:     []timestampedRecommendation{},
  1867  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  1868  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1869  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1870  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleDown,
  1871  		},
  1872  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1873  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  1874  		},
  1875  	}
  1876  	tc.runTest(t)
  1877  }
  1878  
  1879  func TestScaleDownToZeroCMObject(t *testing.T) {
  1880  	targetValue := resource.MustParse("20.0")
  1881  	tc := testCase{
  1882  		minReplicas:             0,
  1883  		maxReplicas:             6,
  1884  		specReplicas:            5,
  1885  		statusReplicas:          5,
  1886  		expectedDesiredReplicas: 0,
  1887  		CPUTarget:               0,
  1888  		metricsTarget: []autoscalingv2.MetricSpec{
  1889  			{
  1890  				Type: autoscalingv2.ObjectMetricSourceType,
  1891  				Object: &autoscalingv2.ObjectMetricSource{
  1892  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1893  						APIVersion: "apps/v1",
  1894  						Kind:       "Deployment",
  1895  						Name:       "some-deployment",
  1896  					},
  1897  					Metric: autoscalingv2.MetricIdentifier{
  1898  						Name: "qps",
  1899  					},
  1900  					Target: autoscalingv2.MetricTarget{
  1901  						Type:  autoscalingv2.ValueMetricType,
  1902  						Value: &targetValue,
  1903  					},
  1904  				},
  1905  			},
  1906  		},
  1907  		reportedLevels:      []uint64{0},
  1908  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1909  		recommendations:     []timestampedRecommendation{},
  1910  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  1911  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1912  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1913  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleDown,
  1914  		},
  1915  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1916  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  1917  		},
  1918  	}
  1919  	tc.runTest(t)
  1920  }
  1921  
  1922  func TestScaleDownPerPodCMObject(t *testing.T) {
  1923  	targetAverageValue := resource.MustParse("20.0")
  1924  	tc := testCase{
  1925  		minReplicas:             2,
  1926  		maxReplicas:             6,
  1927  		specReplicas:            5,
  1928  		statusReplicas:          5,
  1929  		expectedDesiredReplicas: 3,
  1930  		CPUTarget:               0,
  1931  		metricsTarget: []autoscalingv2.MetricSpec{
  1932  			{
  1933  				Type: autoscalingv2.ObjectMetricSourceType,
  1934  				Object: &autoscalingv2.ObjectMetricSource{
  1935  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1936  						APIVersion: "apps/v1",
  1937  						Kind:       "Deployment",
  1938  						Name:       "some-deployment",
  1939  					},
  1940  					Metric: autoscalingv2.MetricIdentifier{
  1941  						Name: "qps",
  1942  					},
  1943  					Target: autoscalingv2.MetricTarget{
  1944  						Type:         autoscalingv2.AverageValueMetricType,
  1945  						AverageValue: &targetAverageValue,
  1946  					},
  1947  				},
  1948  			},
  1949  		},
  1950  		reportedLevels:      []uint64{60000},
  1951  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1952  		recommendations:     []timestampedRecommendation{},
  1953  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  1954  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1955  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1956  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelScaleDown,
  1957  		},
  1958  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1959  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  1960  		},
  1961  	}
  1962  	tc.runTest(t)
  1963  }
  1964  
  1965  func TestScaleDownCMExternal(t *testing.T) {
  1966  	tc := testCase{
  1967  		minReplicas:             2,
  1968  		maxReplicas:             6,
  1969  		specReplicas:            5,
  1970  		statusReplicas:          5,
  1971  		expectedDesiredReplicas: 3,
  1972  		metricsTarget: []autoscalingv2.MetricSpec{
  1973  			{
  1974  				Type: autoscalingv2.ExternalMetricSourceType,
  1975  				External: &autoscalingv2.ExternalMetricSource{
  1976  					Metric: autoscalingv2.MetricIdentifier{
  1977  						Name:     "qps",
  1978  						Selector: &metav1.LabelSelector{},
  1979  					},
  1980  					Target: autoscalingv2.MetricTarget{
  1981  						Type:  autoscalingv2.ValueMetricType,
  1982  						Value: resource.NewMilliQuantity(14400, resource.DecimalSI),
  1983  					},
  1984  				},
  1985  			},
  1986  		},
  1987  		reportedLevels:  []uint64{8600},
  1988  		recommendations: []timestampedRecommendation{},
  1989  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  1990  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  1991  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  1992  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelScaleDown,
  1993  		},
  1994  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  1995  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone,
  1996  		},
  1997  	}
  1998  	tc.runTest(t)
  1999  }
  2000  
  2001  func TestScaleDownToZeroCMExternal(t *testing.T) {
  2002  	tc := testCase{
  2003  		minReplicas:             0,
  2004  		maxReplicas:             6,
  2005  		specReplicas:            5,
  2006  		statusReplicas:          5,
  2007  		expectedDesiredReplicas: 0,
  2008  		metricsTarget: []autoscalingv2.MetricSpec{
  2009  			{
  2010  				Type: autoscalingv2.ExternalMetricSourceType,
  2011  				External: &autoscalingv2.ExternalMetricSource{
  2012  					Metric: autoscalingv2.MetricIdentifier{
  2013  						Name:     "qps",
  2014  						Selector: &metav1.LabelSelector{},
  2015  					},
  2016  					Target: autoscalingv2.MetricTarget{
  2017  						Type:  autoscalingv2.ValueMetricType,
  2018  						Value: resource.NewMilliQuantity(14400, resource.DecimalSI),
  2019  					},
  2020  				},
  2021  			},
  2022  		},
  2023  		reportedLevels:  []uint64{0},
  2024  		recommendations: []timestampedRecommendation{},
  2025  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2026  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2027  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2028  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelScaleDown,
  2029  		},
  2030  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2031  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone,
  2032  		},
  2033  	}
  2034  	tc.runTest(t)
  2035  }
  2036  
  2037  func TestScaleDownPerPodCMExternal(t *testing.T) {
  2038  	tc := testCase{
  2039  		minReplicas:             2,
  2040  		maxReplicas:             6,
  2041  		specReplicas:            5,
  2042  		statusReplicas:          5,
  2043  		expectedDesiredReplicas: 3,
  2044  		metricsTarget: []autoscalingv2.MetricSpec{
  2045  			{
  2046  				Type: autoscalingv2.ExternalMetricSourceType,
  2047  				External: &autoscalingv2.ExternalMetricSource{
  2048  					Metric: autoscalingv2.MetricIdentifier{
  2049  						Name:     "qps",
  2050  						Selector: &metav1.LabelSelector{},
  2051  					},
  2052  					Target: autoscalingv2.MetricTarget{
  2053  						Type:         autoscalingv2.AverageValueMetricType,
  2054  						AverageValue: resource.NewMilliQuantity(3000, resource.DecimalSI),
  2055  					},
  2056  				},
  2057  			},
  2058  		},
  2059  		reportedLevels:  []uint64{8600},
  2060  		recommendations: []timestampedRecommendation{},
  2061  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2062  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2063  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2064  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelScaleDown,
  2065  		},
  2066  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2067  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone,
  2068  		},
  2069  	}
  2070  	tc.runTest(t)
  2071  }
  2072  
  2073  func TestScaleDownIncludeUnreadyPods(t *testing.T) {
  2074  	tc := testCase{
  2075  		minReplicas:             2,
  2076  		maxReplicas:             6,
  2077  		specReplicas:            5,
  2078  		statusReplicas:          5,
  2079  		expectedDesiredReplicas: 2,
  2080  		CPUTarget:               50,
  2081  		CPUCurrent:              30,
  2082  		verifyCPUCurrent:        true,
  2083  		reportedLevels:          []uint64{100, 300, 500, 250, 250},
  2084  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2085  		useMetricsAPI:           true,
  2086  		reportedPodReadiness:    []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  2087  		recommendations:         []timestampedRecommendation{},
  2088  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2089  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2090  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2091  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2092  		},
  2093  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2094  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2095  		},
  2096  	}
  2097  	tc.runTest(t)
  2098  }
  2099  
  2100  func TestScaleDownIgnoreHotCpuPods(t *testing.T) {
  2101  	tc := testCase{
  2102  		minReplicas:             2,
  2103  		maxReplicas:             6,
  2104  		specReplicas:            5,
  2105  		statusReplicas:          5,
  2106  		expectedDesiredReplicas: 2,
  2107  		CPUTarget:               50,
  2108  		CPUCurrent:              30,
  2109  		verifyCPUCurrent:        true,
  2110  		reportedLevels:          []uint64{100, 300, 500, 250, 250},
  2111  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2112  		useMetricsAPI:           true,
  2113  		reportedPodStartTime:    []metav1.Time{coolCPUCreationTime(), coolCPUCreationTime(), coolCPUCreationTime(), hotCPUCreationTime(), hotCPUCreationTime()},
  2114  		recommendations:         []timestampedRecommendation{},
  2115  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2116  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2117  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2118  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2119  		},
  2120  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2121  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2122  		},
  2123  	}
  2124  	tc.runTest(t)
  2125  }
  2126  
  2127  func TestScaleDownIgnoresFailedPods(t *testing.T) {
  2128  	tc := testCase{
  2129  		minReplicas:             2,
  2130  		maxReplicas:             6,
  2131  		specReplicas:            5,
  2132  		statusReplicas:          5,
  2133  		expectedDesiredReplicas: 3,
  2134  		CPUTarget:               50,
  2135  		CPUCurrent:              28,
  2136  		verifyCPUCurrent:        true,
  2137  		reportedLevels:          []uint64{100, 300, 500, 250, 250},
  2138  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2139  		useMetricsAPI:           true,
  2140  		reportedPodReadiness:    []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  2141  		reportedPodPhase:        []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodFailed, v1.PodFailed},
  2142  		recommendations:         []timestampedRecommendation{},
  2143  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2144  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2145  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2146  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2147  		},
  2148  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2149  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2150  		},
  2151  	}
  2152  	tc.runTest(t)
  2153  }
  2154  
  2155  func TestScaleDownIgnoresDeletionPods(t *testing.T) {
  2156  	tc := testCase{
  2157  		minReplicas:                  2,
  2158  		maxReplicas:                  6,
  2159  		specReplicas:                 5,
  2160  		statusReplicas:               5,
  2161  		expectedDesiredReplicas:      3,
  2162  		CPUTarget:                    50,
  2163  		CPUCurrent:                   28,
  2164  		verifyCPUCurrent:             true,
  2165  		reportedLevels:               []uint64{100, 300, 500, 250, 250},
  2166  		reportedCPURequests:          []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2167  		useMetricsAPI:                true,
  2168  		reportedPodReadiness:         []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  2169  		reportedPodPhase:             []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning},
  2170  		reportedPodDeletionTimestamp: []bool{false, false, false, false, false, true, true},
  2171  		recommendations:              []timestampedRecommendation{},
  2172  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2173  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2174  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2175  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2176  		},
  2177  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2178  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2179  		},
  2180  	}
  2181  	tc.runTest(t)
  2182  }
  2183  
  2184  func TestTolerance(t *testing.T) {
  2185  	tc := testCase{
  2186  		minReplicas:             1,
  2187  		maxReplicas:             5,
  2188  		specReplicas:            3,
  2189  		statusReplicas:          3,
  2190  		expectedDesiredReplicas: 3,
  2191  		CPUTarget:               100,
  2192  		reportedLevels:          []uint64{1010, 1030, 1020},
  2193  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2194  		useMetricsAPI:           true,
  2195  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2196  			Type:   autoscalingv2.AbleToScale,
  2197  			Status: v1.ConditionTrue,
  2198  			Reason: "ReadyForNewScale",
  2199  		}),
  2200  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2201  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2202  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2203  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone,
  2204  		},
  2205  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2206  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2207  		},
  2208  	}
  2209  	tc.runTest(t)
  2210  }
  2211  
  2212  func TestToleranceCM(t *testing.T) {
  2213  	averageValue := resource.MustParse("20.0")
  2214  	tc := testCase{
  2215  		minReplicas:             1,
  2216  		maxReplicas:             5,
  2217  		specReplicas:            3,
  2218  		statusReplicas:          3,
  2219  		expectedDesiredReplicas: 3,
  2220  		metricsTarget: []autoscalingv2.MetricSpec{
  2221  			{
  2222  				Type: autoscalingv2.PodsMetricSourceType,
  2223  				Pods: &autoscalingv2.PodsMetricSource{
  2224  					Metric: autoscalingv2.MetricIdentifier{
  2225  						Name: "qps",
  2226  					},
  2227  					Target: autoscalingv2.MetricTarget{
  2228  						Type:         autoscalingv2.AverageValueMetricType,
  2229  						AverageValue: &averageValue,
  2230  					},
  2231  				},
  2232  			},
  2233  		},
  2234  		reportedLevels:      []uint64{20000, 20001, 21000},
  2235  		reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2236  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2237  			Type:   autoscalingv2.AbleToScale,
  2238  			Status: v1.ConditionTrue,
  2239  			Reason: "ReadyForNewScale",
  2240  		}),
  2241  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2242  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2243  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2244  			autoscalingv2.PodsMetricSourceType: monitor.ActionLabelNone,
  2245  		},
  2246  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2247  			autoscalingv2.PodsMetricSourceType: monitor.ErrorLabelNone,
  2248  		},
  2249  	}
  2250  	tc.runTest(t)
  2251  }
  2252  
  2253  func TestToleranceCMObject(t *testing.T) {
  2254  	targetValue := resource.MustParse("20.0")
  2255  	tc := testCase{
  2256  		minReplicas:             1,
  2257  		maxReplicas:             5,
  2258  		specReplicas:            3,
  2259  		statusReplicas:          3,
  2260  		expectedDesiredReplicas: 3,
  2261  		metricsTarget: []autoscalingv2.MetricSpec{
  2262  			{
  2263  				Type: autoscalingv2.ObjectMetricSourceType,
  2264  				Object: &autoscalingv2.ObjectMetricSource{
  2265  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  2266  						APIVersion: "apps/v1",
  2267  						Kind:       "Deployment",
  2268  						Name:       "some-deployment",
  2269  					},
  2270  					Metric: autoscalingv2.MetricIdentifier{
  2271  						Name: "qps",
  2272  					},
  2273  					Target: autoscalingv2.MetricTarget{
  2274  						Type:  autoscalingv2.ValueMetricType,
  2275  						Value: &targetValue,
  2276  					},
  2277  				},
  2278  			},
  2279  		},
  2280  		reportedLevels:      []uint64{20050},
  2281  		reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2282  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2283  			Type:   autoscalingv2.AbleToScale,
  2284  			Status: v1.ConditionTrue,
  2285  			Reason: "ReadyForNewScale",
  2286  		}),
  2287  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2288  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2289  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2290  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelNone,
  2291  		},
  2292  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2293  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  2294  		},
  2295  	}
  2296  	tc.runTest(t)
  2297  }
  2298  
  2299  func TestToleranceCMExternal(t *testing.T) {
  2300  	tc := testCase{
  2301  		minReplicas:             2,
  2302  		maxReplicas:             6,
  2303  		specReplicas:            4,
  2304  		statusReplicas:          4,
  2305  		expectedDesiredReplicas: 4,
  2306  		metricsTarget: []autoscalingv2.MetricSpec{
  2307  			{
  2308  				Type: autoscalingv2.ExternalMetricSourceType,
  2309  				External: &autoscalingv2.ExternalMetricSource{
  2310  					Metric: autoscalingv2.MetricIdentifier{
  2311  						Name:     "qps",
  2312  						Selector: &metav1.LabelSelector{},
  2313  					},
  2314  					Target: autoscalingv2.MetricTarget{
  2315  						Type:  autoscalingv2.ValueMetricType,
  2316  						Value: resource.NewMilliQuantity(8666, resource.DecimalSI),
  2317  					},
  2318  				},
  2319  			},
  2320  		},
  2321  		reportedLevels: []uint64{8600},
  2322  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2323  			Type:   autoscalingv2.AbleToScale,
  2324  			Status: v1.ConditionTrue,
  2325  			Reason: "ReadyForNewScale",
  2326  		}),
  2327  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2328  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2329  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2330  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelNone,
  2331  		},
  2332  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2333  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone,
  2334  		},
  2335  	}
  2336  	tc.runTest(t)
  2337  }
  2338  
  2339  func TestTolerancePerPodCMObject(t *testing.T) {
  2340  	tc := testCase{
  2341  		minReplicas:             2,
  2342  		maxReplicas:             6,
  2343  		specReplicas:            4,
  2344  		statusReplicas:          4,
  2345  		expectedDesiredReplicas: 4,
  2346  		metricsTarget: []autoscalingv2.MetricSpec{
  2347  			{
  2348  				Type: autoscalingv2.ObjectMetricSourceType,
  2349  				Object: &autoscalingv2.ObjectMetricSource{
  2350  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  2351  						APIVersion: "apps/v1",
  2352  						Kind:       "Deployment",
  2353  						Name:       "some-deployment",
  2354  					},
  2355  					Metric: autoscalingv2.MetricIdentifier{
  2356  						Name:     "qps",
  2357  						Selector: &metav1.LabelSelector{},
  2358  					},
  2359  					Target: autoscalingv2.MetricTarget{
  2360  						Type:         autoscalingv2.AverageValueMetricType,
  2361  						AverageValue: resource.NewMilliQuantity(2200, resource.DecimalSI),
  2362  					},
  2363  				},
  2364  			},
  2365  		},
  2366  		reportedLevels: []uint64{8600},
  2367  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2368  			Type:   autoscalingv2.AbleToScale,
  2369  			Status: v1.ConditionTrue,
  2370  			Reason: "ReadyForNewScale",
  2371  		}),
  2372  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2373  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2374  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2375  			autoscalingv2.ObjectMetricSourceType: monitor.ActionLabelNone,
  2376  		},
  2377  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2378  			autoscalingv2.ObjectMetricSourceType: monitor.ErrorLabelNone,
  2379  		},
  2380  	}
  2381  	tc.runTest(t)
  2382  }
  2383  
  2384  func TestTolerancePerPodCMExternal(t *testing.T) {
  2385  	tc := testCase{
  2386  		minReplicas:             2,
  2387  		maxReplicas:             6,
  2388  		specReplicas:            4,
  2389  		statusReplicas:          4,
  2390  		expectedDesiredReplicas: 4,
  2391  		metricsTarget: []autoscalingv2.MetricSpec{
  2392  			{
  2393  				Type: autoscalingv2.ExternalMetricSourceType,
  2394  				External: &autoscalingv2.ExternalMetricSource{
  2395  					Metric: autoscalingv2.MetricIdentifier{
  2396  						Name:     "qps",
  2397  						Selector: &metav1.LabelSelector{},
  2398  					},
  2399  					Target: autoscalingv2.MetricTarget{
  2400  						Type:         autoscalingv2.AverageValueMetricType,
  2401  						AverageValue: resource.NewMilliQuantity(2200, resource.DecimalSI),
  2402  					},
  2403  				},
  2404  			},
  2405  		},
  2406  		reportedLevels: []uint64{8600},
  2407  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2408  			Type:   autoscalingv2.AbleToScale,
  2409  			Status: v1.ConditionTrue,
  2410  			Reason: "ReadyForNewScale",
  2411  		}),
  2412  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2413  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2414  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2415  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelNone,
  2416  		},
  2417  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2418  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelNone,
  2419  		},
  2420  	}
  2421  	tc.runTest(t)
  2422  }
  2423  
  2424  func TestMinReplicas(t *testing.T) {
  2425  	tc := testCase{
  2426  		minReplicas:             2,
  2427  		maxReplicas:             5,
  2428  		specReplicas:            3,
  2429  		statusReplicas:          3,
  2430  		expectedDesiredReplicas: 2,
  2431  		CPUTarget:               90,
  2432  		reportedLevels:          []uint64{10, 95, 10},
  2433  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2434  		useMetricsAPI:           true,
  2435  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2436  			Type:   autoscalingv2.ScalingLimited,
  2437  			Status: v1.ConditionTrue,
  2438  			Reason: "TooFewReplicas",
  2439  		}),
  2440  		recommendations: []timestampedRecommendation{},
  2441  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2442  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2443  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2444  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2445  		},
  2446  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2447  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2448  		},
  2449  	}
  2450  	tc.runTest(t)
  2451  }
  2452  
  2453  func TestZeroMinReplicasDesiredZero(t *testing.T) {
  2454  	tc := testCase{
  2455  		minReplicas:             0,
  2456  		maxReplicas:             5,
  2457  		specReplicas:            3,
  2458  		statusReplicas:          3,
  2459  		expectedDesiredReplicas: 0,
  2460  		CPUTarget:               90,
  2461  		reportedLevels:          []uint64{0, 0, 0},
  2462  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2463  		useMetricsAPI:           true,
  2464  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2465  			Type:   autoscalingv2.ScalingLimited,
  2466  			Status: v1.ConditionFalse,
  2467  			Reason: "DesiredWithinRange",
  2468  		}),
  2469  		recommendations: []timestampedRecommendation{},
  2470  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2471  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2472  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2473  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2474  		},
  2475  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2476  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2477  		},
  2478  	}
  2479  	tc.runTest(t)
  2480  }
  2481  
  2482  func TestMinReplicasDesiredZero(t *testing.T) {
  2483  	tc := testCase{
  2484  		minReplicas:             2,
  2485  		maxReplicas:             5,
  2486  		specReplicas:            3,
  2487  		statusReplicas:          3,
  2488  		expectedDesiredReplicas: 2,
  2489  		CPUTarget:               90,
  2490  		reportedLevels:          []uint64{0, 0, 0},
  2491  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2492  		useMetricsAPI:           true,
  2493  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2494  			Type:   autoscalingv2.ScalingLimited,
  2495  			Status: v1.ConditionTrue,
  2496  			Reason: "TooFewReplicas",
  2497  		}),
  2498  		recommendations: []timestampedRecommendation{},
  2499  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2500  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2501  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2502  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2503  		},
  2504  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2505  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2506  		},
  2507  	}
  2508  	tc.runTest(t)
  2509  }
  2510  
  2511  func TestZeroReplicas(t *testing.T) {
  2512  	tc := testCase{
  2513  		minReplicas:             3,
  2514  		maxReplicas:             5,
  2515  		specReplicas:            0,
  2516  		statusReplicas:          0,
  2517  		expectedDesiredReplicas: 0,
  2518  		CPUTarget:               90,
  2519  		reportedLevels:          []uint64{},
  2520  		reportedCPURequests:     []resource.Quantity{},
  2521  		useMetricsAPI:           true,
  2522  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2523  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  2524  			{Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"},
  2525  		},
  2526  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelNone,
  2527  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelNone,
  2528  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  2529  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  2530  	}
  2531  	tc.runTest(t)
  2532  }
  2533  
  2534  func TestTooFewReplicas(t *testing.T) {
  2535  	tc := testCase{
  2536  		minReplicas:             3,
  2537  		maxReplicas:             5,
  2538  		specReplicas:            2,
  2539  		statusReplicas:          2,
  2540  		expectedDesiredReplicas: 3,
  2541  		CPUTarget:               90,
  2542  		reportedLevels:          []uint64{},
  2543  		reportedCPURequests:     []resource.Quantity{},
  2544  		useMetricsAPI:           true,
  2545  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2546  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  2547  		},
  2548  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelScaleUp,
  2549  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelNone,
  2550  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  2551  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  2552  	}
  2553  	tc.runTest(t)
  2554  }
  2555  
  2556  func TestTooManyReplicas(t *testing.T) {
  2557  	tc := testCase{
  2558  		minReplicas:             3,
  2559  		maxReplicas:             5,
  2560  		specReplicas:            10,
  2561  		statusReplicas:          10,
  2562  		expectedDesiredReplicas: 5,
  2563  		CPUTarget:               90,
  2564  		reportedLevels:          []uint64{},
  2565  		reportedCPURequests:     []resource.Quantity{},
  2566  		useMetricsAPI:           true,
  2567  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2568  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  2569  		},
  2570  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelScaleDown,
  2571  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelNone,
  2572  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  2573  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  2574  	}
  2575  	tc.runTest(t)
  2576  }
  2577  
  2578  func TestMaxReplicas(t *testing.T) {
  2579  	tc := testCase{
  2580  		minReplicas:             2,
  2581  		maxReplicas:             5,
  2582  		specReplicas:            3,
  2583  		statusReplicas:          3,
  2584  		expectedDesiredReplicas: 5,
  2585  		CPUTarget:               90,
  2586  		reportedLevels:          []uint64{8000, 9500, 1000},
  2587  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2588  		useMetricsAPI:           true,
  2589  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2590  			Type:   autoscalingv2.ScalingLimited,
  2591  			Status: v1.ConditionTrue,
  2592  			Reason: "TooManyReplicas",
  2593  		}),
  2594  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  2595  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2596  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2597  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  2598  		},
  2599  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2600  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2601  		},
  2602  	}
  2603  	tc.runTest(t)
  2604  }
  2605  
  2606  func TestSuperfluousMetrics(t *testing.T) {
  2607  	tc := testCase{
  2608  		minReplicas:             2,
  2609  		maxReplicas:             6,
  2610  		specReplicas:            4,
  2611  		statusReplicas:          4,
  2612  		expectedDesiredReplicas: 6,
  2613  		CPUTarget:               100,
  2614  		reportedLevels:          []uint64{4000, 9500, 3000, 7000, 3200, 2000},
  2615  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2616  		useMetricsAPI:           true,
  2617  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2618  			Type:   autoscalingv2.ScalingLimited,
  2619  			Status: v1.ConditionTrue,
  2620  			Reason: "TooManyReplicas",
  2621  		}),
  2622  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  2623  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2624  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2625  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  2626  		},
  2627  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2628  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2629  		},
  2630  	}
  2631  	tc.runTest(t)
  2632  }
  2633  
  2634  func TestMissingMetrics(t *testing.T) {
  2635  	tc := testCase{
  2636  		minReplicas:             2,
  2637  		maxReplicas:             6,
  2638  		specReplicas:            4,
  2639  		statusReplicas:          4,
  2640  		expectedDesiredReplicas: 3,
  2641  		CPUTarget:               100,
  2642  		reportedLevels:          []uint64{400, 95},
  2643  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2644  		useMetricsAPI:           true,
  2645  		recommendations:         []timestampedRecommendation{},
  2646  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2647  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2648  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2649  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2650  		},
  2651  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2652  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2653  		},
  2654  	}
  2655  	tc.runTest(t)
  2656  }
  2657  
  2658  func TestEmptyMetrics(t *testing.T) {
  2659  	tc := testCase{
  2660  		minReplicas:             2,
  2661  		maxReplicas:             6,
  2662  		specReplicas:            4,
  2663  		statusReplicas:          4,
  2664  		expectedDesiredReplicas: 4,
  2665  		CPUTarget:               100,
  2666  		reportedLevels:          []uint64{},
  2667  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2668  		useMetricsAPI:           true,
  2669  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2670  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  2671  			{Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetResourceMetric"},
  2672  		},
  2673  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2674  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  2675  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2676  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone,
  2677  		},
  2678  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2679  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelInternal,
  2680  		},
  2681  	}
  2682  	tc.runTest(t)
  2683  }
  2684  
  2685  func TestEmptyCPURequest(t *testing.T) {
  2686  	tc := testCase{
  2687  		minReplicas:             1,
  2688  		maxReplicas:             5,
  2689  		specReplicas:            1,
  2690  		statusReplicas:          1,
  2691  		expectedDesiredReplicas: 1,
  2692  		CPUTarget:               100,
  2693  		reportedLevels:          []uint64{200},
  2694  		reportedCPURequests:     []resource.Quantity{},
  2695  		useMetricsAPI:           true,
  2696  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2697  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  2698  			{Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetResourceMetric"},
  2699  		},
  2700  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2701  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  2702  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2703  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone,
  2704  		},
  2705  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2706  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelInternal,
  2707  		},
  2708  	}
  2709  	tc.runTest(t)
  2710  }
  2711  
  2712  func TestEventCreated(t *testing.T) {
  2713  	tc := testCase{
  2714  		minReplicas:             1,
  2715  		maxReplicas:             5,
  2716  		specReplicas:            1,
  2717  		statusReplicas:          1,
  2718  		expectedDesiredReplicas: 2,
  2719  		CPUTarget:               50,
  2720  		reportedLevels:          []uint64{200},
  2721  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.2")},
  2722  		verifyEvents:            true,
  2723  		useMetricsAPI:           true,
  2724  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  2725  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2726  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2727  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  2728  		},
  2729  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2730  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2731  		},
  2732  	}
  2733  	tc.runTest(t)
  2734  }
  2735  
  2736  func TestEventNotCreated(t *testing.T) {
  2737  	tc := testCase{
  2738  		minReplicas:             1,
  2739  		maxReplicas:             5,
  2740  		specReplicas:            2,
  2741  		statusReplicas:          2,
  2742  		expectedDesiredReplicas: 2,
  2743  		CPUTarget:               50,
  2744  		reportedLevels:          []uint64{200, 200},
  2745  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.4"), resource.MustParse("0.4")},
  2746  		verifyEvents:            true,
  2747  		useMetricsAPI:           true,
  2748  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2749  			Type:   autoscalingv2.AbleToScale,
  2750  			Status: v1.ConditionTrue,
  2751  			Reason: "ReadyForNewScale",
  2752  		}),
  2753  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2754  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2755  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2756  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone,
  2757  		},
  2758  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2759  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2760  		},
  2761  	}
  2762  	tc.runTest(t)
  2763  }
  2764  
  2765  func TestMissingReports(t *testing.T) {
  2766  	tc := testCase{
  2767  		minReplicas:             1,
  2768  		maxReplicas:             5,
  2769  		specReplicas:            4,
  2770  		statusReplicas:          4,
  2771  		expectedDesiredReplicas: 2,
  2772  		CPUTarget:               50,
  2773  		reportedLevels:          []uint64{200},
  2774  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.2")},
  2775  		useMetricsAPI:           true,
  2776  		recommendations:         []timestampedRecommendation{},
  2777  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  2778  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2779  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2780  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  2781  		},
  2782  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2783  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2784  		},
  2785  	}
  2786  	tc.runTest(t)
  2787  }
  2788  
  2789  func TestUpscaleCap(t *testing.T) {
  2790  	tc := testCase{
  2791  		minReplicas:             1,
  2792  		maxReplicas:             100,
  2793  		specReplicas:            3,
  2794  		statusReplicas:          3,
  2795  		scaleUpRules:            generateScalingRules(0, 0, 700, 60, 0),
  2796  		initialReplicas:         3,
  2797  		expectedDesiredReplicas: 24,
  2798  		CPUTarget:               10,
  2799  		reportedLevels:          []uint64{100, 200, 300},
  2800  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2801  		useMetricsAPI:           true,
  2802  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2803  			Type:   autoscalingv2.ScalingLimited,
  2804  			Status: v1.ConditionTrue,
  2805  			Reason: "ScaleUpLimit",
  2806  		}),
  2807  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  2808  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2809  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2810  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  2811  		},
  2812  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2813  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2814  		},
  2815  	}
  2816  	tc.runTest(t)
  2817  }
  2818  
  2819  func TestUpscaleCapGreaterThanMaxReplicas(t *testing.T) {
  2820  	// TODO: Remove skip once this issue is resolved: https://github.com/kubernetes/kubernetes/issues/124083
  2821  	if goruntime.GOOS == "windows" {
  2822  		t.Skip("Skip flaking test on Windows.")
  2823  	}
  2824  	tc := testCase{
  2825  		minReplicas:     1,
  2826  		maxReplicas:     20,
  2827  		specReplicas:    3,
  2828  		statusReplicas:  3,
  2829  		scaleUpRules:    generateScalingRules(0, 0, 700, 60, 0),
  2830  		initialReplicas: 3,
  2831  		// expectedDesiredReplicas would be 24 without maxReplicas
  2832  		expectedDesiredReplicas: 20,
  2833  		CPUTarget:               10,
  2834  		reportedLevels:          []uint64{100, 200, 300},
  2835  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2836  		useMetricsAPI:           true,
  2837  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2838  			Type:   autoscalingv2.ScalingLimited,
  2839  			Status: v1.ConditionTrue,
  2840  			Reason: "TooManyReplicas",
  2841  		}),
  2842  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  2843  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2844  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2845  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  2846  		},
  2847  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2848  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2849  		},
  2850  	}
  2851  	tc.runTest(t)
  2852  }
  2853  
  2854  func TestMoreReplicasThanSpecNoScale(t *testing.T) {
  2855  	// TODO: Remove skip once this issue is resolved: https://github.com/kubernetes/kubernetes/issues/124083
  2856  	if goruntime.GOOS == "windows" {
  2857  		t.Skip("Skip flaking test on Windows.")
  2858  	}
  2859  	tc := testCase{
  2860  		minReplicas:             1,
  2861  		maxReplicas:             8,
  2862  		specReplicas:            4,
  2863  		statusReplicas:          5, // Deployment update with 25% surge.
  2864  		expectedDesiredReplicas: 4,
  2865  		CPUTarget:               50,
  2866  		reportedLevels:          []uint64{500, 500, 500, 500, 500},
  2867  		reportedCPURequests: []resource.Quantity{
  2868  			resource.MustParse("1"),
  2869  			resource.MustParse("1"),
  2870  			resource.MustParse("1"),
  2871  			resource.MustParse("1"),
  2872  			resource.MustParse("1"),
  2873  		},
  2874  		useMetricsAPI: true,
  2875  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2876  			Type:   autoscalingv2.AbleToScale,
  2877  			Status: v1.ConditionTrue,
  2878  			Reason: "ReadyForNewScale",
  2879  		}),
  2880  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  2881  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  2882  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  2883  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone,
  2884  		},
  2885  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  2886  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  2887  		},
  2888  	}
  2889  	tc.runTest(t)
  2890  }
  2891  
  2892  func TestConditionInvalidSelectorMissing(t *testing.T) {
  2893  	tc := testCase{
  2894  		minReplicas:             1,
  2895  		maxReplicas:             100,
  2896  		specReplicas:            3,
  2897  		statusReplicas:          3,
  2898  		expectedDesiredReplicas: 3,
  2899  		CPUTarget:               10,
  2900  		reportedLevels:          []uint64{100, 200, 300},
  2901  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2902  		useMetricsAPI:           true,
  2903  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2904  			{
  2905  				Type:   autoscalingv2.AbleToScale,
  2906  				Status: v1.ConditionTrue,
  2907  				Reason: "SucceededGetScale",
  2908  			},
  2909  			{
  2910  				Type:   autoscalingv2.ScalingActive,
  2911  				Status: v1.ConditionFalse,
  2912  				Reason: "InvalidSelector",
  2913  			},
  2914  		},
  2915  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelNone,
  2916  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelInternal,
  2917  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  2918  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  2919  	}
  2920  
  2921  	_, _, _, _, testScaleClient := tc.prepareTestClient(t)
  2922  	tc.testScaleClient = testScaleClient
  2923  
  2924  	testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2925  		obj := &autoscalingv1.Scale{
  2926  			ObjectMeta: metav1.ObjectMeta{
  2927  				Name: tc.resource.name,
  2928  			},
  2929  			Spec: autoscalingv1.ScaleSpec{
  2930  				Replicas: tc.specReplicas,
  2931  			},
  2932  			Status: autoscalingv1.ScaleStatus{
  2933  				Replicas: tc.specReplicas,
  2934  			},
  2935  		}
  2936  		return true, obj, nil
  2937  	})
  2938  
  2939  	tc.runTest(t)
  2940  }
  2941  
  2942  func TestConditionInvalidSelectorUnparsable(t *testing.T) {
  2943  	tc := testCase{
  2944  		minReplicas:             1,
  2945  		maxReplicas:             100,
  2946  		specReplicas:            3,
  2947  		statusReplicas:          3,
  2948  		expectedDesiredReplicas: 3,
  2949  		CPUTarget:               10,
  2950  		reportedLevels:          []uint64{100, 200, 300},
  2951  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2952  		useMetricsAPI:           true,
  2953  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2954  			{
  2955  				Type:   autoscalingv2.AbleToScale,
  2956  				Status: v1.ConditionTrue,
  2957  				Reason: "SucceededGetScale",
  2958  			},
  2959  			{
  2960  				Type:   autoscalingv2.ScalingActive,
  2961  				Status: v1.ConditionFalse,
  2962  				Reason: "InvalidSelector",
  2963  			},
  2964  		},
  2965  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelNone,
  2966  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelInternal,
  2967  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  2968  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  2969  	}
  2970  
  2971  	_, _, _, _, testScaleClient := tc.prepareTestClient(t)
  2972  	tc.testScaleClient = testScaleClient
  2973  
  2974  	testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2975  		obj := &autoscalingv1.Scale{
  2976  			ObjectMeta: metav1.ObjectMeta{
  2977  				Name: tc.resource.name,
  2978  			},
  2979  			Spec: autoscalingv1.ScaleSpec{
  2980  				Replicas: tc.specReplicas,
  2981  			},
  2982  			Status: autoscalingv1.ScaleStatus{
  2983  				Replicas: tc.specReplicas,
  2984  				Selector: "cheddar cheese",
  2985  			},
  2986  		}
  2987  		return true, obj, nil
  2988  	})
  2989  
  2990  	tc.runTest(t)
  2991  }
  2992  
  2993  func TestConditionNoAmbiguousSelectorWhenNoSelectorOverlapBetweenHPAs(t *testing.T) {
  2994  	hpaSelectors := selectors.NewBiMultimap()
  2995  	hpaSelectors.PutSelector(selectors.Key{Name: "test-hpa-2", Namespace: testNamespace}, labels.SelectorFromSet(labels.Set{"cheddar": "cheese"}))
  2996  
  2997  	tc := testCase{
  2998  		minReplicas:             2,
  2999  		maxReplicas:             6,
  3000  		specReplicas:            3,
  3001  		statusReplicas:          3,
  3002  		expectedDesiredReplicas: 5,
  3003  		CPUTarget:               30,
  3004  		reportedLevels:          []uint64{300, 500, 700},
  3005  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3006  		useMetricsAPI:           true,
  3007  		hpaSelectors:            hpaSelectors,
  3008  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  3009  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  3010  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3011  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  3012  		},
  3013  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3014  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  3015  		},
  3016  	}
  3017  	tc.runTest(t)
  3018  }
  3019  
  3020  func TestConditionAmbiguousSelectorWhenFullSelectorOverlapBetweenHPAs(t *testing.T) {
  3021  	hpaSelectors := selectors.NewBiMultimap()
  3022  	hpaSelectors.PutSelector(selectors.Key{Name: "test-hpa-2", Namespace: testNamespace}, labels.SelectorFromSet(labels.Set{"name": podNamePrefix}))
  3023  
  3024  	tc := testCase{
  3025  		minReplicas:             2,
  3026  		maxReplicas:             6,
  3027  		specReplicas:            3,
  3028  		statusReplicas:          3,
  3029  		expectedDesiredReplicas: 3,
  3030  		CPUTarget:               30,
  3031  		reportedLevels:          []uint64{300, 500, 700},
  3032  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3033  		useMetricsAPI:           true,
  3034  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  3035  			{
  3036  				Type:   autoscalingv2.AbleToScale,
  3037  				Status: v1.ConditionTrue,
  3038  				Reason: "SucceededGetScale",
  3039  			},
  3040  			{
  3041  				Type:   autoscalingv2.ScalingActive,
  3042  				Status: v1.ConditionFalse,
  3043  				Reason: "AmbiguousSelector",
  3044  			},
  3045  		},
  3046  		hpaSelectors: hpaSelectors,
  3047  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelNone,
  3048  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelInternal,
  3049  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  3050  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  3051  	}
  3052  	tc.runTest(t)
  3053  }
  3054  
  3055  func TestConditionAmbiguousSelectorWhenPartialSelectorOverlapBetweenHPAs(t *testing.T) {
  3056  	hpaSelectors := selectors.NewBiMultimap()
  3057  	hpaSelectors.PutSelector(selectors.Key{Name: "test-hpa-2", Namespace: testNamespace}, labels.SelectorFromSet(labels.Set{"cheddar": "cheese"}))
  3058  
  3059  	tc := testCase{
  3060  		minReplicas:             2,
  3061  		maxReplicas:             6,
  3062  		specReplicas:            3,
  3063  		statusReplicas:          3,
  3064  		expectedDesiredReplicas: 3,
  3065  		CPUTarget:               30,
  3066  		reportedLevels:          []uint64{300, 500, 700},
  3067  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3068  		useMetricsAPI:           true,
  3069  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  3070  			{
  3071  				Type:   autoscalingv2.AbleToScale,
  3072  				Status: v1.ConditionTrue,
  3073  				Reason: "SucceededGetScale",
  3074  			},
  3075  			{
  3076  				Type:   autoscalingv2.ScalingActive,
  3077  				Status: v1.ConditionFalse,
  3078  				Reason: "AmbiguousSelector",
  3079  			},
  3080  		},
  3081  		hpaSelectors: hpaSelectors,
  3082  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelNone,
  3083  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelInternal,
  3084  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  3085  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  3086  	}
  3087  
  3088  	testClient, _, _, _, _ := tc.prepareTestClient(t)
  3089  	tc.testClient = testClient
  3090  
  3091  	testClient.PrependReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3092  		tc.Lock()
  3093  		defer tc.Unlock()
  3094  
  3095  		obj := &v1.PodList{}
  3096  		for i := range tc.reportedCPURequests {
  3097  			pod := v1.Pod{
  3098  				ObjectMeta: metav1.ObjectMeta{
  3099  					Name:      fmt.Sprintf("%s-%d", podNamePrefix, i),
  3100  					Namespace: testNamespace,
  3101  					Labels: map[string]string{
  3102  						"name":    podNamePrefix, // selected by the original HPA
  3103  						"cheddar": "cheese",      // selected by test-hpa-2
  3104  					},
  3105  				},
  3106  			}
  3107  			obj.Items = append(obj.Items, pod)
  3108  		}
  3109  		return true, obj, nil
  3110  	})
  3111  
  3112  	tc.runTest(t)
  3113  }
  3114  
  3115  func TestConditionFailedGetMetrics(t *testing.T) {
  3116  	targetValue := resource.MustParse("15.0")
  3117  	averageValue := resource.MustParse("15.0")
  3118  	metricsTargets := map[string][]autoscalingv2.MetricSpec{
  3119  		"FailedGetResourceMetric": nil,
  3120  		"FailedGetPodsMetric": {
  3121  			{
  3122  				Type: autoscalingv2.PodsMetricSourceType,
  3123  				Pods: &autoscalingv2.PodsMetricSource{
  3124  					Metric: autoscalingv2.MetricIdentifier{
  3125  						Name: "qps",
  3126  					},
  3127  					Target: autoscalingv2.MetricTarget{
  3128  						Type:         autoscalingv2.AverageValueMetricType,
  3129  						AverageValue: &averageValue,
  3130  					},
  3131  				},
  3132  			},
  3133  		},
  3134  		"FailedGetObjectMetric": {
  3135  			{
  3136  				Type: autoscalingv2.ObjectMetricSourceType,
  3137  				Object: &autoscalingv2.ObjectMetricSource{
  3138  					DescribedObject: autoscalingv2.CrossVersionObjectReference{
  3139  						APIVersion: "apps/v1",
  3140  						Kind:       "Deployment",
  3141  						Name:       "some-deployment",
  3142  					},
  3143  					Metric: autoscalingv2.MetricIdentifier{
  3144  						Name: "qps",
  3145  					},
  3146  					Target: autoscalingv2.MetricTarget{
  3147  						Type:  autoscalingv2.ValueMetricType,
  3148  						Value: &targetValue,
  3149  					},
  3150  				},
  3151  			},
  3152  		},
  3153  		"FailedGetExternalMetric": {
  3154  			{
  3155  				Type: autoscalingv2.ExternalMetricSourceType,
  3156  				External: &autoscalingv2.ExternalMetricSource{
  3157  					Metric: autoscalingv2.MetricIdentifier{
  3158  						Name:     "qps",
  3159  						Selector: &metav1.LabelSelector{},
  3160  					},
  3161  					Target: autoscalingv2.MetricTarget{
  3162  						Type:  autoscalingv2.ValueMetricType,
  3163  						Value: resource.NewMilliQuantity(300, resource.DecimalSI),
  3164  					},
  3165  				},
  3166  			},
  3167  		},
  3168  	}
  3169  
  3170  	for reason, specs := range metricsTargets {
  3171  		metricType := autoscalingv2.ResourceMetricSourceType
  3172  		if specs != nil {
  3173  			metricType = specs[0].Type
  3174  		}
  3175  		tc := testCase{
  3176  			minReplicas:             1,
  3177  			maxReplicas:             100,
  3178  			specReplicas:            3,
  3179  			statusReplicas:          3,
  3180  			expectedDesiredReplicas: 3,
  3181  			CPUTarget:               10,
  3182  			reportedLevels:          []uint64{100, 200, 300},
  3183  			reportedCPURequests:     []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  3184  			useMetricsAPI:           true,
  3185  			expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  3186  			expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  3187  			expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3188  				metricType: monitor.ActionLabelNone,
  3189  			},
  3190  			expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3191  				metricType: monitor.ErrorLabelInternal,
  3192  			},
  3193  		}
  3194  		_, testMetricsClient, testCMClient, testEMClient, _ := tc.prepareTestClient(t)
  3195  		tc.testMetricsClient = testMetricsClient
  3196  		tc.testCMClient = testCMClient
  3197  		tc.testEMClient = testEMClient
  3198  
  3199  		testMetricsClient.PrependReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3200  			return true, &metricsapi.PodMetricsList{}, fmt.Errorf("something went wrong")
  3201  		})
  3202  		testCMClient.PrependReactor("get", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3203  			return true, &cmapi.MetricValueList{}, fmt.Errorf("something went wrong")
  3204  		})
  3205  		testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3206  			return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  3207  		})
  3208  
  3209  		tc.expectedConditions = []autoscalingv2.HorizontalPodAutoscalerCondition{
  3210  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  3211  			{Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: reason},
  3212  		}
  3213  		if specs != nil {
  3214  			tc.CPUTarget = 0
  3215  		} else {
  3216  			tc.CPUTarget = 10
  3217  		}
  3218  		tc.metricsTarget = specs
  3219  		tc.runTest(t)
  3220  	}
  3221  }
  3222  
  3223  func TestConditionInvalidSourceType(t *testing.T) {
  3224  	tc := testCase{
  3225  		minReplicas:             2,
  3226  		maxReplicas:             6,
  3227  		specReplicas:            3,
  3228  		statusReplicas:          3,
  3229  		expectedDesiredReplicas: 3,
  3230  		CPUTarget:               0,
  3231  		metricsTarget: []autoscalingv2.MetricSpec{
  3232  			{
  3233  				Type: "CheddarCheese",
  3234  			},
  3235  		},
  3236  		reportedLevels: []uint64{20000},
  3237  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  3238  			{
  3239  				Type:   autoscalingv2.AbleToScale,
  3240  				Status: v1.ConditionTrue,
  3241  				Reason: "SucceededGetScale",
  3242  			},
  3243  			{
  3244  				Type:   autoscalingv2.ScalingActive,
  3245  				Status: v1.ConditionFalse,
  3246  				Reason: "InvalidMetricSourceType",
  3247  			},
  3248  		},
  3249  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  3250  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  3251  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3252  			// Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded.
  3253  			"CheddarCheese": monitor.ActionLabelNone,
  3254  		},
  3255  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3256  			// Actually, such an invalid type should be validated in the kube-apiserver and invalid metric type shouldn't be recorded.
  3257  			"CheddarCheese": monitor.ErrorLabelSpec,
  3258  		},
  3259  	}
  3260  	tc.runTest(t)
  3261  }
  3262  
  3263  func TestConditionFailedGetScale(t *testing.T) {
  3264  	tc := testCase{
  3265  		minReplicas:             1,
  3266  		maxReplicas:             100,
  3267  		specReplicas:            3,
  3268  		statusReplicas:          3,
  3269  		expectedDesiredReplicas: 3,
  3270  		CPUTarget:               10,
  3271  		reportedLevels:          []uint64{100, 200, 300},
  3272  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  3273  		useMetricsAPI:           true,
  3274  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  3275  			{
  3276  				Type:   autoscalingv2.AbleToScale,
  3277  				Status: v1.ConditionFalse,
  3278  				Reason: "FailedGetScale",
  3279  			},
  3280  		},
  3281  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelNone,
  3282  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelInternal,
  3283  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  3284  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  3285  	}
  3286  
  3287  	_, _, _, _, testScaleClient := tc.prepareTestClient(t)
  3288  	tc.testScaleClient = testScaleClient
  3289  
  3290  	testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3291  		return true, &autoscalingv1.Scale{}, fmt.Errorf("something went wrong")
  3292  	})
  3293  
  3294  	tc.runTest(t)
  3295  }
  3296  
  3297  func TestConditionFailedUpdateScale(t *testing.T) {
  3298  	tc := testCase{
  3299  		minReplicas:             1,
  3300  		maxReplicas:             5,
  3301  		specReplicas:            3,
  3302  		statusReplicas:          3,
  3303  		expectedDesiredReplicas: 3,
  3304  		CPUTarget:               100,
  3305  		reportedLevels:          []uint64{150, 150, 150},
  3306  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  3307  		useMetricsAPI:           true,
  3308  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  3309  			Type:   autoscalingv2.AbleToScale,
  3310  			Status: v1.ConditionFalse,
  3311  			Reason: "FailedUpdateScale",
  3312  		}),
  3313  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  3314  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  3315  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3316  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  3317  		},
  3318  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3319  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  3320  		},
  3321  	}
  3322  
  3323  	_, _, _, _, testScaleClient := tc.prepareTestClient(t)
  3324  	tc.testScaleClient = testScaleClient
  3325  
  3326  	testScaleClient.PrependReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3327  		return true, &autoscalingv1.Scale{}, fmt.Errorf("something went wrong")
  3328  	})
  3329  
  3330  	tc.runTest(t)
  3331  }
  3332  
  3333  func TestNoBackoffUpscaleCM(t *testing.T) {
  3334  	averageValue := resource.MustParse("15.0")
  3335  	time := metav1.Time{Time: time.Now()}
  3336  	tc := testCase{
  3337  		minReplicas:             1,
  3338  		maxReplicas:             5,
  3339  		specReplicas:            3,
  3340  		statusReplicas:          3,
  3341  		expectedDesiredReplicas: 4,
  3342  		CPUTarget:               0,
  3343  		metricsTarget: []autoscalingv2.MetricSpec{
  3344  			{
  3345  				Type: autoscalingv2.PodsMetricSourceType,
  3346  				Pods: &autoscalingv2.PodsMetricSource{
  3347  					Metric: autoscalingv2.MetricIdentifier{
  3348  						Name: "qps",
  3349  					},
  3350  					Target: autoscalingv2.MetricTarget{
  3351  						Type:         autoscalingv2.AverageValueMetricType,
  3352  						AverageValue: &averageValue,
  3353  					},
  3354  				},
  3355  			},
  3356  		},
  3357  		reportedLevels:      []uint64{20000, 10000, 30000},
  3358  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3359  		//useMetricsAPI:       true,
  3360  		lastScaleTime: &time,
  3361  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  3362  			Type:   autoscalingv2.AbleToScale,
  3363  			Status: v1.ConditionTrue,
  3364  			Reason: "ReadyForNewScale",
  3365  		}, autoscalingv2.HorizontalPodAutoscalerCondition{
  3366  			Type:   autoscalingv2.AbleToScale,
  3367  			Status: v1.ConditionTrue,
  3368  			Reason: "SucceededRescale",
  3369  		}, autoscalingv2.HorizontalPodAutoscalerCondition{
  3370  			Type:   autoscalingv2.ScalingLimited,
  3371  			Status: v1.ConditionFalse,
  3372  			Reason: "DesiredWithinRange",
  3373  		}),
  3374  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  3375  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  3376  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3377  			autoscalingv2.PodsMetricSourceType: monitor.ActionLabelScaleUp,
  3378  		},
  3379  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3380  			autoscalingv2.PodsMetricSourceType: monitor.ErrorLabelNone,
  3381  		},
  3382  	}
  3383  	tc.runTest(t)
  3384  }
  3385  
  3386  func TestNoBackoffUpscaleCMNoBackoffCpu(t *testing.T) {
  3387  	averageValue := resource.MustParse("15.0")
  3388  	time := metav1.Time{Time: time.Now()}
  3389  	tc := testCase{
  3390  		minReplicas:             1,
  3391  		maxReplicas:             5,
  3392  		specReplicas:            3,
  3393  		statusReplicas:          3,
  3394  		expectedDesiredReplicas: 5,
  3395  		CPUTarget:               10,
  3396  		metricsTarget: []autoscalingv2.MetricSpec{
  3397  			{
  3398  				Type: autoscalingv2.PodsMetricSourceType,
  3399  				Pods: &autoscalingv2.PodsMetricSource{
  3400  					Metric: autoscalingv2.MetricIdentifier{
  3401  						Name: "qps",
  3402  					},
  3403  					Target: autoscalingv2.MetricTarget{
  3404  						Type:         autoscalingv2.AverageValueMetricType,
  3405  						AverageValue: &averageValue,
  3406  					},
  3407  				},
  3408  			},
  3409  		},
  3410  		reportedLevels:      []uint64{20000, 10000, 30000},
  3411  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3412  		useMetricsAPI:       true,
  3413  		lastScaleTime:       &time,
  3414  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  3415  			Type:   autoscalingv2.AbleToScale,
  3416  			Status: v1.ConditionTrue,
  3417  			Reason: "ReadyForNewScale",
  3418  		}, autoscalingv2.HorizontalPodAutoscalerCondition{
  3419  			Type:   autoscalingv2.AbleToScale,
  3420  			Status: v1.ConditionTrue,
  3421  			Reason: "SucceededRescale",
  3422  		}, autoscalingv2.HorizontalPodAutoscalerCondition{
  3423  			Type:   autoscalingv2.ScalingLimited,
  3424  			Status: v1.ConditionTrue,
  3425  			Reason: "TooManyReplicas",
  3426  		}),
  3427  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  3428  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  3429  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3430  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  3431  			autoscalingv2.PodsMetricSourceType:     monitor.ActionLabelScaleUp,
  3432  		},
  3433  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3434  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  3435  			autoscalingv2.PodsMetricSourceType:     monitor.ErrorLabelNone,
  3436  		},
  3437  	}
  3438  	tc.runTest(t)
  3439  }
  3440  
  3441  func TestStabilizeDownscale(t *testing.T) {
  3442  	tc := testCase{
  3443  		minReplicas:             1,
  3444  		maxReplicas:             5,
  3445  		specReplicas:            4,
  3446  		statusReplicas:          4,
  3447  		expectedDesiredReplicas: 4,
  3448  		CPUTarget:               100,
  3449  		reportedLevels:          []uint64{50, 50, 50},
  3450  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  3451  		useMetricsAPI:           true,
  3452  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  3453  			Type:   autoscalingv2.AbleToScale,
  3454  			Status: v1.ConditionTrue,
  3455  			Reason: "ReadyForNewScale",
  3456  		}, autoscalingv2.HorizontalPodAutoscalerCondition{
  3457  			Type:   autoscalingv2.AbleToScale,
  3458  			Status: v1.ConditionTrue,
  3459  			Reason: "ScaleDownStabilized",
  3460  		}),
  3461  		recommendations: []timestampedRecommendation{
  3462  			{10, time.Now().Add(-10 * time.Minute)},
  3463  			{4, time.Now().Add(-1 * time.Minute)},
  3464  		},
  3465  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  3466  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  3467  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3468  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  3469  		},
  3470  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3471  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  3472  		},
  3473  	}
  3474  	tc.runTest(t)
  3475  }
  3476  
  3477  // TestComputedToleranceAlgImplementation is a regression test which
  3478  // back-calculates a minimal percentage for downscaling based on a small percentage
  3479  // increase in pod utilization which is calibrated against the tolerance value.
  3480  func TestComputedToleranceAlgImplementation(t *testing.T) {
  3481  
  3482  	startPods := int32(10)
  3483  	// 150 mCPU per pod.
  3484  	totalUsedCPUOfAllPods := uint64(startPods * 150)
  3485  	// Each pod starts out asking for 2X what is really needed.
  3486  	// This means we will have a 50% ratio of used/requested
  3487  	totalRequestedCPUOfAllPods := int32(2 * totalUsedCPUOfAllPods)
  3488  	requestedToUsed := float64(totalRequestedCPUOfAllPods / int32(totalUsedCPUOfAllPods))
  3489  	// Spread the amount we ask over 10 pods.  We can add some jitter later in reportedLevels.
  3490  	perPodRequested := totalRequestedCPUOfAllPods / startPods
  3491  
  3492  	// Force a minimal scaling event by satisfying  (tolerance < 1 - resourcesUsedRatio).
  3493  	target := math.Abs(1/(requestedToUsed*(1-defaultTestingTolerance))) + .01
  3494  	finalCPUPercentTarget := int32(target * 100)
  3495  	resourcesUsedRatio := float64(totalUsedCPUOfAllPods) / float64(float64(totalRequestedCPUOfAllPods)*target)
  3496  
  3497  	// i.e. .60 * 20 -> scaled down expectation.
  3498  	finalPods := int32(math.Ceil(resourcesUsedRatio * float64(startPods)))
  3499  
  3500  	// To breach tolerance we will create a utilization ratio difference of tolerance to usageRatioToleranceValue)
  3501  	tc1 := testCase{
  3502  		minReplicas:             0,
  3503  		maxReplicas:             1000,
  3504  		specReplicas:            startPods,
  3505  		statusReplicas:          startPods,
  3506  		expectedDesiredReplicas: finalPods,
  3507  		CPUTarget:               finalCPUPercentTarget,
  3508  		reportedLevels: []uint64{
  3509  			totalUsedCPUOfAllPods / 10,
  3510  			totalUsedCPUOfAllPods / 10,
  3511  			totalUsedCPUOfAllPods / 10,
  3512  			totalUsedCPUOfAllPods / 10,
  3513  			totalUsedCPUOfAllPods / 10,
  3514  			totalUsedCPUOfAllPods / 10,
  3515  			totalUsedCPUOfAllPods / 10,
  3516  			totalUsedCPUOfAllPods / 10,
  3517  			totalUsedCPUOfAllPods / 10,
  3518  			totalUsedCPUOfAllPods / 10,
  3519  		},
  3520  		reportedCPURequests: []resource.Quantity{
  3521  			resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"),
  3522  			resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"),
  3523  			resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"),
  3524  			resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"),
  3525  			resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"),
  3526  			resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"),
  3527  			resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"),
  3528  			resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"),
  3529  			resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  3530  			resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  3531  		},
  3532  		useMetricsAPI:   true,
  3533  		recommendations: []timestampedRecommendation{},
  3534  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleDown,
  3535  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  3536  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3537  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  3538  		},
  3539  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3540  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  3541  		},
  3542  	}
  3543  	tc1.runTest(t)
  3544  
  3545  	target = math.Abs(1/(requestedToUsed*(1-defaultTestingTolerance))) + .004
  3546  	finalCPUPercentTarget = int32(target * 100)
  3547  	tc2 := testCase{
  3548  		minReplicas:             0,
  3549  		maxReplicas:             1000,
  3550  		specReplicas:            startPods,
  3551  		statusReplicas:          startPods,
  3552  		expectedDesiredReplicas: startPods,
  3553  		CPUTarget:               finalCPUPercentTarget,
  3554  		reportedLevels: []uint64{
  3555  			totalUsedCPUOfAllPods / 10,
  3556  			totalUsedCPUOfAllPods / 10,
  3557  			totalUsedCPUOfAllPods / 10,
  3558  			totalUsedCPUOfAllPods / 10,
  3559  			totalUsedCPUOfAllPods / 10,
  3560  			totalUsedCPUOfAllPods / 10,
  3561  			totalUsedCPUOfAllPods / 10,
  3562  			totalUsedCPUOfAllPods / 10,
  3563  			totalUsedCPUOfAllPods / 10,
  3564  			totalUsedCPUOfAllPods / 10,
  3565  		},
  3566  		reportedCPURequests: []resource.Quantity{
  3567  			resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"),
  3568  			resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"),
  3569  			resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"),
  3570  			resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"),
  3571  			resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"),
  3572  			resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"),
  3573  			resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"),
  3574  			resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"),
  3575  			resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  3576  			resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  3577  		},
  3578  		useMetricsAPI:   true,
  3579  		recommendations: []timestampedRecommendation{},
  3580  		expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  3581  			Type:   autoscalingv2.AbleToScale,
  3582  			Status: v1.ConditionTrue,
  3583  			Reason: "ReadyForNewScale",
  3584  		}),
  3585  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  3586  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  3587  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3588  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelNone,
  3589  		},
  3590  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3591  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  3592  		},
  3593  	}
  3594  	tc2.runTest(t)
  3595  }
  3596  
  3597  func TestScaleUpRCImmediately(t *testing.T) {
  3598  	time := metav1.Time{Time: time.Now()}
  3599  	tc := testCase{
  3600  		minReplicas:             2,
  3601  		maxReplicas:             6,
  3602  		specReplicas:            1,
  3603  		statusReplicas:          1,
  3604  		expectedDesiredReplicas: 2,
  3605  		verifyCPUCurrent:        false,
  3606  		reportedLevels:          []uint64{0, 0, 0, 0},
  3607  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3608  		useMetricsAPI:           true,
  3609  		lastScaleTime:           &time,
  3610  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  3611  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  3612  		},
  3613  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelScaleUp,
  3614  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelNone,
  3615  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  3616  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  3617  	}
  3618  	tc.runTest(t)
  3619  }
  3620  
  3621  func TestScaleDownRCImmediately(t *testing.T) {
  3622  	time := metav1.Time{Time: time.Now()}
  3623  	tc := testCase{
  3624  		minReplicas:             2,
  3625  		maxReplicas:             5,
  3626  		specReplicas:            6,
  3627  		statusReplicas:          6,
  3628  		expectedDesiredReplicas: 5,
  3629  		CPUTarget:               50,
  3630  		reportedLevels:          []uint64{8000, 9500, 1000},
  3631  		reportedCPURequests:     []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  3632  		useMetricsAPI:           true,
  3633  		lastScaleTime:           &time,
  3634  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  3635  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  3636  		},
  3637  		expectedReportedReconciliationActionLabel:     monitor.ActionLabelScaleDown,
  3638  		expectedReportedReconciliationErrorLabel:      monitor.ErrorLabelNone,
  3639  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{},
  3640  		expectedReportedMetricComputationErrorLabels:  map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{},
  3641  	}
  3642  	tc.runTest(t)
  3643  }
  3644  
  3645  func TestAvoidUnnecessaryUpdates(t *testing.T) {
  3646  	now := metav1.Time{Time: time.Now().Add(-time.Hour)}
  3647  	tc := testCase{
  3648  		minReplicas:             2,
  3649  		maxReplicas:             6,
  3650  		specReplicas:            2,
  3651  		statusReplicas:          2,
  3652  		expectedDesiredReplicas: 2,
  3653  		CPUTarget:               30,
  3654  		CPUCurrent:              40,
  3655  		verifyCPUCurrent:        true,
  3656  		reportedLevels:          []uint64{400, 500, 700},
  3657  		reportedCPURequests:     []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3658  		reportedPodStartTime:    []metav1.Time{coolCPUCreationTime(), hotCPUCreationTime(), hotCPUCreationTime()},
  3659  		useMetricsAPI:           true,
  3660  		lastScaleTime:           &now,
  3661  		recommendations:         []timestampedRecommendation{},
  3662  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  3663  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelNone,
  3664  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  3665  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  3666  		},
  3667  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  3668  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  3669  		},
  3670  	}
  3671  	testClient, _, _, _, _ := tc.prepareTestClient(t)
  3672  	tc.testClient = testClient
  3673  	testClient.PrependReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3674  		tc.Lock()
  3675  		defer tc.Unlock()
  3676  		// fake out the verification logic and mark that we're done processing
  3677  		go func() {
  3678  			// wait a tick and then mark that we're finished (otherwise, we have no
  3679  			// way to indicate that we're finished, because the function decides not to do anything)
  3680  			time.Sleep(1 * time.Second)
  3681  			tc.Lock()
  3682  			tc.statusUpdated = true
  3683  			tc.Unlock()
  3684  			tc.processed <- "test-hpa"
  3685  		}()
  3686  
  3687  		var eighty int32 = 80
  3688  
  3689  		quantity := resource.MustParse("400m")
  3690  		obj := &autoscalingv2.HorizontalPodAutoscalerList{
  3691  			Items: []autoscalingv2.HorizontalPodAutoscaler{
  3692  				{
  3693  					ObjectMeta: metav1.ObjectMeta{
  3694  						Name:      "test-hpa",
  3695  						Namespace: "test-namespace",
  3696  					},
  3697  					Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
  3698  						ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
  3699  							Kind:       "ReplicationController",
  3700  							Name:       "test-rc",
  3701  							APIVersion: "v1",
  3702  						},
  3703  						Metrics: []autoscalingv2.MetricSpec{{
  3704  							Type: autoscalingv2.ResourceMetricSourceType,
  3705  							Resource: &autoscalingv2.ResourceMetricSource{
  3706  								Name: v1.ResourceCPU,
  3707  								Target: autoscalingv2.MetricTarget{
  3708  									Type: autoscalingv2.UtilizationMetricType,
  3709  									// TODO: Change this to &tc.CPUTarget and the expected ScaleLimited
  3710  									//       condition to False. This test incorrectly leaves the v1
  3711  									//       HPA field TargetCPUUtilizization field blank and the
  3712  									//       controller defaults to a target of 80. So the test relies
  3713  									//       on downscale stabilization to prevent a scale change.
  3714  									AverageUtilization: &eighty,
  3715  								},
  3716  							},
  3717  						}},
  3718  						MinReplicas: &tc.minReplicas,
  3719  						MaxReplicas: tc.maxReplicas,
  3720  					},
  3721  					Status: autoscalingv2.HorizontalPodAutoscalerStatus{
  3722  						CurrentReplicas: tc.specReplicas,
  3723  						DesiredReplicas: tc.specReplicas,
  3724  						LastScaleTime:   tc.lastScaleTime,
  3725  						CurrentMetrics: []autoscalingv2.MetricStatus{
  3726  							{
  3727  								Type: autoscalingv2.ResourceMetricSourceType,
  3728  								Resource: &autoscalingv2.ResourceMetricStatus{
  3729  									Name: v1.ResourceCPU,
  3730  									Current: autoscalingv2.MetricValueStatus{
  3731  										AverageValue:       &quantity,
  3732  										AverageUtilization: &tc.CPUCurrent,
  3733  									},
  3734  								},
  3735  							},
  3736  						},
  3737  						Conditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  3738  							{
  3739  								Type:               autoscalingv2.AbleToScale,
  3740  								Status:             v1.ConditionTrue,
  3741  								LastTransitionTime: *tc.lastScaleTime,
  3742  								Reason:             "ReadyForNewScale",
  3743  								Message:            "recommended size matches current size",
  3744  							},
  3745  							{
  3746  								Type:               autoscalingv2.ScalingActive,
  3747  								Status:             v1.ConditionTrue,
  3748  								LastTransitionTime: *tc.lastScaleTime,
  3749  								Reason:             "ValidMetricFound",
  3750  								Message:            "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)",
  3751  							},
  3752  							{
  3753  								Type:               autoscalingv2.ScalingLimited,
  3754  								Status:             v1.ConditionTrue,
  3755  								LastTransitionTime: *tc.lastScaleTime,
  3756  								Reason:             "TooFewReplicas",
  3757  								Message:            "the desired replica count is less than the minimum replica count",
  3758  							},
  3759  						},
  3760  					},
  3761  				},
  3762  			},
  3763  		}
  3764  
  3765  		return true, obj, nil
  3766  	})
  3767  	testClient.PrependReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3768  		assert.Fail(t, "should not have attempted to update the HPA when nothing changed")
  3769  		// mark that we've processed this HPA
  3770  		tc.processed <- ""
  3771  		return true, nil, fmt.Errorf("unexpected call")
  3772  	})
  3773  
  3774  	controller, informerFactory := tc.setupController(t)
  3775  	tc.runTestWithController(t, controller, informerFactory)
  3776  }
  3777  
  3778  func TestConvertDesiredReplicasWithRules(t *testing.T) {
  3779  	conversionTestCases := []struct {
  3780  		currentReplicas                  int32
  3781  		expectedDesiredReplicas          int32
  3782  		hpaMinReplicas                   int32
  3783  		hpaMaxReplicas                   int32
  3784  		expectedConvertedDesiredReplicas int32
  3785  		expectedCondition                string
  3786  		annotation                       string
  3787  	}{
  3788  		{
  3789  			currentReplicas:                  5,
  3790  			expectedDesiredReplicas:          7,
  3791  			hpaMinReplicas:                   3,
  3792  			hpaMaxReplicas:                   8,
  3793  			expectedConvertedDesiredReplicas: 7,
  3794  			expectedCondition:                "DesiredWithinRange",
  3795  			annotation:                       "prenormalized desired replicas within range",
  3796  		},
  3797  		{
  3798  			currentReplicas:                  3,
  3799  			expectedDesiredReplicas:          1,
  3800  			hpaMinReplicas:                   2,
  3801  			hpaMaxReplicas:                   8,
  3802  			expectedConvertedDesiredReplicas: 2,
  3803  			expectedCondition:                "TooFewReplicas",
  3804  			annotation:                       "prenormalized desired replicas < minReplicas",
  3805  		},
  3806  		{
  3807  			currentReplicas:                  1,
  3808  			expectedDesiredReplicas:          0,
  3809  			hpaMinReplicas:                   0,
  3810  			hpaMaxReplicas:                   10,
  3811  			expectedConvertedDesiredReplicas: 0,
  3812  			expectedCondition:                "DesiredWithinRange",
  3813  			annotation:                       "prenormalized desired zeroed replicas within range",
  3814  		},
  3815  		{
  3816  			currentReplicas:                  20,
  3817  			expectedDesiredReplicas:          1000,
  3818  			hpaMinReplicas:                   1,
  3819  			hpaMaxReplicas:                   10,
  3820  			expectedConvertedDesiredReplicas: 10,
  3821  			expectedCondition:                "TooManyReplicas",
  3822  			annotation:                       "maxReplicas is the limit because maxReplicas < scaleUpLimit",
  3823  		},
  3824  		{
  3825  			currentReplicas:                  3,
  3826  			expectedDesiredReplicas:          1000,
  3827  			hpaMinReplicas:                   1,
  3828  			hpaMaxReplicas:                   2000,
  3829  			expectedConvertedDesiredReplicas: calculateScaleUpLimit(3),
  3830  			expectedCondition:                "ScaleUpLimit",
  3831  			annotation:                       "scaleUpLimit is the limit because scaleUpLimit < maxReplicas",
  3832  		},
  3833  	}
  3834  
  3835  	for _, ctc := range conversionTestCases {
  3836  		t.Run(ctc.annotation, func(t *testing.T) {
  3837  			actualConvertedDesiredReplicas, actualCondition, _ := convertDesiredReplicasWithRules(
  3838  				ctc.currentReplicas, ctc.expectedDesiredReplicas, ctc.hpaMinReplicas, ctc.hpaMaxReplicas,
  3839  			)
  3840  
  3841  			assert.Equal(t, ctc.expectedConvertedDesiredReplicas, actualConvertedDesiredReplicas, ctc.annotation)
  3842  			assert.Equal(t, ctc.expectedCondition, actualCondition, ctc.annotation)
  3843  		})
  3844  	}
  3845  }
  3846  
  3847  func TestCalculateScaleUpLimitWithScalingRules(t *testing.T) {
  3848  	policy := autoscalingv2.MinChangePolicySelect
  3849  
  3850  	calculated := calculateScaleUpLimitWithScalingRules(1, []timestampedScaleEvent{}, []timestampedScaleEvent{}, &autoscalingv2.HPAScalingRules{
  3851  		StabilizationWindowSeconds: pointer.Int32(300),
  3852  		SelectPolicy:               &policy,
  3853  		Policies: []autoscalingv2.HPAScalingPolicy{
  3854  			{
  3855  				Type:          autoscalingv2.PodsScalingPolicy,
  3856  				Value:         2,
  3857  				PeriodSeconds: 60,
  3858  			},
  3859  			{
  3860  				Type:          autoscalingv2.PercentScalingPolicy,
  3861  				Value:         50,
  3862  				PeriodSeconds: 60,
  3863  			},
  3864  		},
  3865  	})
  3866  	assert.Equal(t, calculated, int32(2))
  3867  }
  3868  
  3869  func TestCalculateScaleDownLimitWithBehaviors(t *testing.T) {
  3870  	policy := autoscalingv2.MinChangePolicySelect
  3871  
  3872  	calculated := calculateScaleDownLimitWithBehaviors(5, []timestampedScaleEvent{}, []timestampedScaleEvent{}, &autoscalingv2.HPAScalingRules{
  3873  		StabilizationWindowSeconds: pointer.Int32(300),
  3874  		SelectPolicy:               &policy,
  3875  		Policies: []autoscalingv2.HPAScalingPolicy{
  3876  			{
  3877  				Type:          autoscalingv2.PodsScalingPolicy,
  3878  				Value:         2,
  3879  				PeriodSeconds: 60,
  3880  			},
  3881  			{
  3882  				Type:          autoscalingv2.PercentScalingPolicy,
  3883  				Value:         50,
  3884  				PeriodSeconds: 60,
  3885  			},
  3886  		},
  3887  	})
  3888  	assert.Equal(t, calculated, int32(3))
  3889  }
  3890  
  3891  func generateScalingRules(pods, podsPeriod, percent, percentPeriod, stabilizationWindow int32) *autoscalingv2.HPAScalingRules {
  3892  	policy := autoscalingv2.MaxChangePolicySelect
  3893  	directionBehavior := autoscalingv2.HPAScalingRules{
  3894  		StabilizationWindowSeconds: pointer.Int32(stabilizationWindow),
  3895  		SelectPolicy:               &policy,
  3896  	}
  3897  	if pods != 0 {
  3898  		directionBehavior.Policies = append(directionBehavior.Policies,
  3899  			autoscalingv2.HPAScalingPolicy{Type: autoscalingv2.PodsScalingPolicy, Value: pods, PeriodSeconds: podsPeriod})
  3900  	}
  3901  	if percent != 0 {
  3902  		directionBehavior.Policies = append(directionBehavior.Policies,
  3903  			autoscalingv2.HPAScalingPolicy{Type: autoscalingv2.PercentScalingPolicy, Value: percent, PeriodSeconds: percentPeriod})
  3904  	}
  3905  	return &directionBehavior
  3906  }
  3907  
  3908  // generateEventsUniformDistribution generates events that uniformly spread in the time window
  3909  //
  3910  //	time.Now()-periodSeconds  ; time.Now()
  3911  //
  3912  // It split the time window into several segments (by the number of events) and put the event in the center of the segment
  3913  // it is needed if you want to create events for several policies (to check how "outdated" flag is set).
  3914  // E.g. generateEventsUniformDistribution([]int{1,2,3,4}, 120) will spread events uniformly for the last 120 seconds:
  3915  //
  3916  //	1          2          3          4
  3917  //
  3918  // -----------------------------------------------
  3919  //
  3920  //	^          ^          ^          ^          ^
  3921  //
  3922  // -120s      -90s       -60s       -30s       now()
  3923  // And we can safely have two different stabilizationWindows:
  3924  //   - 60s (guaranteed to have last half of events)
  3925  //   - 120s (guaranteed to have all events)
  3926  func generateEventsUniformDistribution(rawEvents []int, periodSeconds int) []timestampedScaleEvent {
  3927  	events := make([]timestampedScaleEvent, len(rawEvents))
  3928  	segmentDuration := float64(periodSeconds) / float64(len(rawEvents))
  3929  	for idx, event := range rawEvents {
  3930  		segmentBoundary := time.Duration(float64(periodSeconds) - segmentDuration*float64(idx+1) + segmentDuration/float64(2))
  3931  		events[idx] = timestampedScaleEvent{
  3932  			replicaChange: int32(event),
  3933  			timestamp:     time.Now().Add(-time.Second * segmentBoundary),
  3934  		}
  3935  	}
  3936  	return events
  3937  }
  3938  
  3939  func TestNormalizeDesiredReplicas(t *testing.T) {
  3940  	tests := []struct {
  3941  		name                         string
  3942  		key                          string
  3943  		recommendations              []timestampedRecommendation
  3944  		prenormalizedDesiredReplicas int32
  3945  		expectedStabilizedReplicas   int32
  3946  		expectedLogLength            int
  3947  	}{
  3948  		{
  3949  			"empty log",
  3950  			"",
  3951  			[]timestampedRecommendation{},
  3952  			5,
  3953  			5,
  3954  			1,
  3955  		},
  3956  		{
  3957  			"stabilize",
  3958  			"",
  3959  			[]timestampedRecommendation{
  3960  				{4, time.Now().Add(-2 * time.Minute)},
  3961  				{5, time.Now().Add(-1 * time.Minute)},
  3962  			},
  3963  			3,
  3964  			5,
  3965  			3,
  3966  		},
  3967  		{
  3968  			"no stabilize",
  3969  			"",
  3970  			[]timestampedRecommendation{
  3971  				{1, time.Now().Add(-2 * time.Minute)},
  3972  				{2, time.Now().Add(-1 * time.Minute)},
  3973  			},
  3974  			3,
  3975  			3,
  3976  			3,
  3977  		},
  3978  		{
  3979  			"no stabilize - old recommendations",
  3980  			"",
  3981  			[]timestampedRecommendation{
  3982  				{10, time.Now().Add(-10 * time.Minute)},
  3983  				{9, time.Now().Add(-9 * time.Minute)},
  3984  			},
  3985  			3,
  3986  			3,
  3987  			2,
  3988  		},
  3989  		{
  3990  			"stabilize - old recommendations",
  3991  			"",
  3992  			[]timestampedRecommendation{
  3993  				{10, time.Now().Add(-10 * time.Minute)},
  3994  				{4, time.Now().Add(-1 * time.Minute)},
  3995  				{5, time.Now().Add(-2 * time.Minute)},
  3996  				{9, time.Now().Add(-9 * time.Minute)},
  3997  			},
  3998  			3,
  3999  			5,
  4000  			4,
  4001  		},
  4002  	}
  4003  	for _, tc := range tests {
  4004  		hc := HorizontalController{
  4005  			downscaleStabilisationWindow: 5 * time.Minute,
  4006  			recommendations: map[string][]timestampedRecommendation{
  4007  				tc.key: tc.recommendations,
  4008  			},
  4009  		}
  4010  		r := hc.stabilizeRecommendation(tc.key, tc.prenormalizedDesiredReplicas)
  4011  		if r != tc.expectedStabilizedReplicas {
  4012  			t.Errorf("[%s] got %d stabilized replicas, expected %d", tc.name, r, tc.expectedStabilizedReplicas)
  4013  		}
  4014  		if len(hc.recommendations[tc.key]) != tc.expectedLogLength {
  4015  			t.Errorf("[%s] after  stabilization recommendations log has %d entries, expected %d", tc.name, len(hc.recommendations[tc.key]), tc.expectedLogLength)
  4016  		}
  4017  	}
  4018  }
  4019  
  4020  func TestScalingWithRules(t *testing.T) {
  4021  	type TestCase struct {
  4022  		name string
  4023  		key  string
  4024  		// controller arguments
  4025  		scaleUpEvents   []timestampedScaleEvent
  4026  		scaleDownEvents []timestampedScaleEvent
  4027  		// HPA Spec arguments
  4028  		specMinReplicas int32
  4029  		specMaxReplicas int32
  4030  		scaleUpRules    *autoscalingv2.HPAScalingRules
  4031  		scaleDownRules  *autoscalingv2.HPAScalingRules
  4032  		// external world state
  4033  		currentReplicas              int32
  4034  		prenormalizedDesiredReplicas int32
  4035  		// test expected result
  4036  		expectedReplicas  int32
  4037  		expectedCondition string
  4038  
  4039  		testThis bool
  4040  	}
  4041  
  4042  	tests := []TestCase{
  4043  		{
  4044  			currentReplicas:              5,
  4045  			prenormalizedDesiredReplicas: 7,
  4046  			specMinReplicas:              3,
  4047  			specMaxReplicas:              8,
  4048  			expectedReplicas:             7,
  4049  			expectedCondition:            "DesiredWithinRange",
  4050  			name:                         "prenormalized desired replicas within range",
  4051  		},
  4052  		{
  4053  			currentReplicas:              3,
  4054  			prenormalizedDesiredReplicas: 1,
  4055  			specMinReplicas:              2,
  4056  			specMaxReplicas:              8,
  4057  			expectedReplicas:             2,
  4058  			expectedCondition:            "TooFewReplicas",
  4059  			name:                         "prenormalized desired replicas < minReplicas",
  4060  		},
  4061  		{
  4062  			currentReplicas:              1,
  4063  			prenormalizedDesiredReplicas: 0,
  4064  			specMinReplicas:              0,
  4065  			specMaxReplicas:              10,
  4066  			expectedReplicas:             0,
  4067  			expectedCondition:            "DesiredWithinRange",
  4068  			name:                         "prenormalized desired replicas within range when minReplicas is 0",
  4069  		},
  4070  		{
  4071  			currentReplicas:              20,
  4072  			prenormalizedDesiredReplicas: 1000,
  4073  			specMinReplicas:              1,
  4074  			specMaxReplicas:              10,
  4075  			expectedReplicas:             10,
  4076  			expectedCondition:            "TooManyReplicas",
  4077  			name:                         "maxReplicas is the limit because maxReplicas < scaleUpLimit",
  4078  		},
  4079  		{
  4080  			currentReplicas:              100,
  4081  			prenormalizedDesiredReplicas: 1000,
  4082  			specMinReplicas:              100,
  4083  			specMaxReplicas:              150,
  4084  			expectedReplicas:             150,
  4085  			expectedCondition:            "TooManyReplicas",
  4086  			name:                         "desired replica count is more than the maximum replica count",
  4087  		},
  4088  		{
  4089  			currentReplicas:              3,
  4090  			prenormalizedDesiredReplicas: 1000,
  4091  			specMinReplicas:              1,
  4092  			specMaxReplicas:              2000,
  4093  			expectedReplicas:             4,
  4094  			expectedCondition:            "ScaleUpLimit",
  4095  			scaleUpRules:                 generateScalingRules(0, 0, 1, 60, 0),
  4096  			name:                         "scaleUpLimit is the limit because scaleUpLimit < maxReplicas with user policies",
  4097  		},
  4098  		{
  4099  			currentReplicas:              1000,
  4100  			prenormalizedDesiredReplicas: 3,
  4101  			specMinReplicas:              3,
  4102  			specMaxReplicas:              2000,
  4103  			scaleDownRules:               generateScalingRules(20, 60, 0, 0, 0),
  4104  			expectedReplicas:             980,
  4105  			expectedCondition:            "ScaleDownLimit",
  4106  			name:                         "scaleDownLimit is the limit because scaleDownLimit > minReplicas with user defined policies",
  4107  			testThis:                     true,
  4108  		},
  4109  		// ScaleUp without PeriodSeconds usage
  4110  		{
  4111  			name:                         "scaleUp with default behavior",
  4112  			specMinReplicas:              1,
  4113  			specMaxReplicas:              1000,
  4114  			currentReplicas:              10,
  4115  			prenormalizedDesiredReplicas: 50,
  4116  			expectedReplicas:             20,
  4117  			expectedCondition:            "ScaleUpLimit",
  4118  		},
  4119  		{
  4120  			name:                         "scaleUp with pods policy larger than percent policy",
  4121  			specMinReplicas:              1,
  4122  			specMaxReplicas:              1000,
  4123  			scaleUpRules:                 generateScalingRules(100, 60, 100, 60, 0),
  4124  			currentReplicas:              10,
  4125  			prenormalizedDesiredReplicas: 500,
  4126  			expectedReplicas:             110,
  4127  			expectedCondition:            "ScaleUpLimit",
  4128  		},
  4129  		{
  4130  			name:                         "scaleUp with percent policy larger than pods policy",
  4131  			specMinReplicas:              1,
  4132  			specMaxReplicas:              1000,
  4133  			scaleUpRules:                 generateScalingRules(2, 60, 100, 60, 0),
  4134  			currentReplicas:              10,
  4135  			prenormalizedDesiredReplicas: 500,
  4136  			expectedReplicas:             20,
  4137  			expectedCondition:            "ScaleUpLimit",
  4138  		},
  4139  		{
  4140  			name:                         "scaleUp with spec MaxReplicas limitation with large pod policy",
  4141  			specMinReplicas:              1,
  4142  			specMaxReplicas:              1000,
  4143  			scaleUpRules:                 generateScalingRules(100, 60, 0, 0, 0),
  4144  			currentReplicas:              10,
  4145  			prenormalizedDesiredReplicas: 50,
  4146  			expectedReplicas:             50,
  4147  			expectedCondition:            "DesiredWithinRange",
  4148  		},
  4149  		{
  4150  			name:                         "scaleUp with spec MaxReplicas limitation with large percent policy",
  4151  			specMinReplicas:              1,
  4152  			specMaxReplicas:              1000,
  4153  			scaleUpRules:                 generateScalingRules(10000, 60, 0, 0, 0),
  4154  			currentReplicas:              10,
  4155  			prenormalizedDesiredReplicas: 50,
  4156  			expectedReplicas:             50,
  4157  			expectedCondition:            "DesiredWithinRange",
  4158  		},
  4159  		{
  4160  			name:                         "scaleUp with pod policy limitation",
  4161  			specMinReplicas:              1,
  4162  			specMaxReplicas:              1000,
  4163  			scaleUpRules:                 generateScalingRules(30, 60, 0, 0, 0),
  4164  			currentReplicas:              10,
  4165  			prenormalizedDesiredReplicas: 50,
  4166  			expectedReplicas:             40,
  4167  			expectedCondition:            "ScaleUpLimit",
  4168  		},
  4169  		{
  4170  			name:                         "scaleUp with percent policy limitation",
  4171  			specMinReplicas:              1,
  4172  			specMaxReplicas:              1000,
  4173  			scaleUpRules:                 generateScalingRules(0, 0, 200, 60, 0),
  4174  			currentReplicas:              10,
  4175  			prenormalizedDesiredReplicas: 50,
  4176  			expectedReplicas:             30,
  4177  			expectedCondition:            "ScaleUpLimit",
  4178  		},
  4179  		{
  4180  			name:                         "scaleDown with percent policy larger than pod policy",
  4181  			specMinReplicas:              1,
  4182  			specMaxReplicas:              1000,
  4183  			scaleDownRules:               generateScalingRules(20, 60, 1, 60, 300),
  4184  			currentReplicas:              100,
  4185  			prenormalizedDesiredReplicas: 2,
  4186  			expectedReplicas:             80,
  4187  			expectedCondition:            "ScaleDownLimit",
  4188  		},
  4189  		{
  4190  			name:                         "scaleDown with pod policy larger than percent policy",
  4191  			specMinReplicas:              1,
  4192  			specMaxReplicas:              1000,
  4193  			scaleDownRules:               generateScalingRules(2, 60, 1, 60, 300),
  4194  			currentReplicas:              100,
  4195  			prenormalizedDesiredReplicas: 2,
  4196  			expectedReplicas:             98,
  4197  			expectedCondition:            "ScaleDownLimit",
  4198  		},
  4199  		{
  4200  			name:                         "scaleDown with spec MinReplicas=nil limitation with large pod policy",
  4201  			specMinReplicas:              1,
  4202  			specMaxReplicas:              1000,
  4203  			scaleDownRules:               generateScalingRules(100, 60, 0, 0, 300),
  4204  			currentReplicas:              10,
  4205  			prenormalizedDesiredReplicas: 0,
  4206  			expectedReplicas:             1,
  4207  			expectedCondition:            "TooFewReplicas",
  4208  		},
  4209  		{
  4210  			name:                         "scaleDown with spec MinReplicas limitation with large pod policy",
  4211  			specMinReplicas:              1,
  4212  			specMaxReplicas:              1000,
  4213  			scaleDownRules:               generateScalingRules(100, 60, 0, 0, 300),
  4214  			currentReplicas:              10,
  4215  			prenormalizedDesiredReplicas: 0,
  4216  			expectedReplicas:             1,
  4217  			expectedCondition:            "TooFewReplicas",
  4218  		},
  4219  		{
  4220  			name:                         "scaleDown with spec MinReplicas limitation with large percent policy",
  4221  			specMinReplicas:              5,
  4222  			specMaxReplicas:              1000,
  4223  			scaleDownRules:               generateScalingRules(0, 0, 100, 60, 300),
  4224  			currentReplicas:              10,
  4225  			prenormalizedDesiredReplicas: 2,
  4226  			expectedReplicas:             5,
  4227  			expectedCondition:            "TooFewReplicas",
  4228  		},
  4229  		{
  4230  			name:                         "scaleDown with pod policy limitation",
  4231  			specMinReplicas:              1,
  4232  			specMaxReplicas:              1000,
  4233  			scaleDownRules:               generateScalingRules(5, 60, 0, 0, 300),
  4234  			currentReplicas:              10,
  4235  			prenormalizedDesiredReplicas: 2,
  4236  			expectedReplicas:             5,
  4237  			expectedCondition:            "ScaleDownLimit",
  4238  		},
  4239  		{
  4240  			name:                         "scaleDown with percent policy limitation",
  4241  			specMinReplicas:              1,
  4242  			specMaxReplicas:              1000,
  4243  			scaleDownRules:               generateScalingRules(0, 0, 50, 60, 300),
  4244  			currentReplicas:              10,
  4245  			prenormalizedDesiredReplicas: 5,
  4246  			expectedReplicas:             5,
  4247  			expectedCondition:            "DesiredWithinRange",
  4248  		},
  4249  		{
  4250  			name:                         "scaleUp with spec MaxReplicas limitation with large pod policy and events",
  4251  			scaleUpEvents:                generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4252  			specMinReplicas:              1,
  4253  			specMaxReplicas:              200,
  4254  			scaleUpRules:                 generateScalingRules(300, 60, 0, 0, 0),
  4255  			currentReplicas:              100,
  4256  			prenormalizedDesiredReplicas: 500,
  4257  			expectedReplicas:             200, // 200 < 100 - 15 + 300
  4258  			expectedCondition:            "TooManyReplicas",
  4259  		},
  4260  		{
  4261  			name:                         "scaleUp with spec MaxReplicas limitation with large percent policy and events",
  4262  			scaleUpEvents:                generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4263  			specMinReplicas:              1,
  4264  			specMaxReplicas:              200,
  4265  			scaleUpRules:                 generateScalingRules(0, 0, 10000, 60, 0),
  4266  			currentReplicas:              100,
  4267  			prenormalizedDesiredReplicas: 500,
  4268  			expectedReplicas:             200,
  4269  			expectedCondition:            "TooManyReplicas",
  4270  		},
  4271  		{
  4272  			// corner case for calculating the scaleUpLimit, when we changed pod policy after a lot of scaleUp events
  4273  			// in this case we shouldn't allow scale up, though, the naive formula will suggest that scaleUplimit is less then CurrentReplicas (100-15+5 < 100)
  4274  			name:                         "scaleUp with currentReplicas limitation with rate.PeriodSeconds with a lot of recent scale up events",
  4275  			scaleUpEvents:                generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4276  			specMinReplicas:              1,
  4277  			specMaxReplicas:              1000,
  4278  			scaleUpRules:                 generateScalingRules(5, 120, 0, 0, 0),
  4279  			currentReplicas:              100,
  4280  			prenormalizedDesiredReplicas: 500,
  4281  			expectedReplicas:             100, // 120 seconds ago we had (100 - 15) replicas, now the rate.Pods = 5,
  4282  			expectedCondition:            "ScaleUpLimit",
  4283  		},
  4284  		{
  4285  			name:                         "scaleUp with pod policy and previous scale up events",
  4286  			scaleUpEvents:                generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4287  			specMinReplicas:              1,
  4288  			specMaxReplicas:              1000,
  4289  			scaleUpRules:                 generateScalingRules(150, 120, 0, 0, 0),
  4290  			currentReplicas:              100,
  4291  			prenormalizedDesiredReplicas: 500,
  4292  			expectedReplicas:             235, // 100 - 15 + 150
  4293  			expectedCondition:            "ScaleUpLimit",
  4294  		},
  4295  		{
  4296  			name:                         "scaleUp with percent policy and previous scale up events",
  4297  			scaleUpEvents:                generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4298  			specMinReplicas:              1,
  4299  			specMaxReplicas:              1000,
  4300  			scaleUpRules:                 generateScalingRules(0, 0, 200, 120, 0),
  4301  			currentReplicas:              100,
  4302  			prenormalizedDesiredReplicas: 500,
  4303  			expectedReplicas:             255, // (100 - 15) + 200%
  4304  			expectedCondition:            "ScaleUpLimit",
  4305  		},
  4306  		{
  4307  			name:                         "scaleUp with percent policy and previous scale up and down events",
  4308  			scaleUpEvents:                generateEventsUniformDistribution([]int{4}, 120),
  4309  			scaleDownEvents:              generateEventsUniformDistribution([]int{2}, 120),
  4310  			specMinReplicas:              1,
  4311  			specMaxReplicas:              1000,
  4312  			scaleUpRules:                 generateScalingRules(0, 0, 300, 300, 0),
  4313  			currentReplicas:              6,
  4314  			prenormalizedDesiredReplicas: 24,
  4315  			expectedReplicas:             16,
  4316  			expectedCondition:            "ScaleUpLimit",
  4317  		},
  4318  		// ScaleDown with PeriodSeconds usage
  4319  		{
  4320  			name:                         "scaleDown with default policy and previous events",
  4321  			scaleDownEvents:              generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4322  			specMinReplicas:              1,
  4323  			specMaxReplicas:              1000,
  4324  			currentReplicas:              10,
  4325  			prenormalizedDesiredReplicas: 5,
  4326  			expectedReplicas:             5, // without scaleDown rate limitations the PeriodSeconds does not influence anything
  4327  			expectedCondition:            "DesiredWithinRange",
  4328  		},
  4329  		{
  4330  			name:                         "scaleDown with spec MinReplicas=nil limitation with large pod policy and previous events",
  4331  			scaleDownEvents:              generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4332  			specMinReplicas:              1,
  4333  			specMaxReplicas:              1000,
  4334  			scaleDownRules:               generateScalingRules(115, 120, 0, 0, 300),
  4335  			currentReplicas:              100,
  4336  			prenormalizedDesiredReplicas: 0,
  4337  			expectedReplicas:             1,
  4338  			expectedCondition:            "TooFewReplicas",
  4339  		},
  4340  		{
  4341  			name:                         "scaleDown with spec MinReplicas limitation with large pod policy and previous events",
  4342  			scaleDownEvents:              generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4343  			specMinReplicas:              5,
  4344  			specMaxReplicas:              1000,
  4345  			scaleDownRules:               generateScalingRules(130, 120, 0, 0, 300),
  4346  			currentReplicas:              100,
  4347  			prenormalizedDesiredReplicas: 0,
  4348  			expectedReplicas:             5,
  4349  			expectedCondition:            "TooFewReplicas",
  4350  		},
  4351  		{
  4352  			name:                         "scaleDown with spec MinReplicas limitation with large percent policy and previous events",
  4353  			scaleDownEvents:              generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4354  			specMinReplicas:              5,
  4355  			specMaxReplicas:              1000,
  4356  			scaleDownRules:               generateScalingRules(0, 0, 100, 120, 300), // 100% removal - is always to 0 => limited by MinReplicas
  4357  			currentReplicas:              100,
  4358  			prenormalizedDesiredReplicas: 2,
  4359  			expectedReplicas:             5,
  4360  			expectedCondition:            "TooFewReplicas",
  4361  		},
  4362  		{
  4363  			name:                         "scaleDown with pod policy limitation and previous events",
  4364  			scaleDownEvents:              generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  4365  			specMinReplicas:              1,
  4366  			specMaxReplicas:              1000,
  4367  			scaleDownRules:               generateScalingRules(5, 120, 0, 0, 300),
  4368  			currentReplicas:              100,
  4369  			prenormalizedDesiredReplicas: 2,
  4370  			expectedReplicas:             100, // 100 + 15 - 5
  4371  			expectedCondition:            "ScaleDownLimit",
  4372  		},
  4373  		{
  4374  			name:                         "scaleDown with percent policy limitation and previous events",
  4375  			scaleDownEvents:              generateEventsUniformDistribution([]int{2, 4, 6}, 120),
  4376  			specMinReplicas:              1,
  4377  			specMaxReplicas:              1000,
  4378  			scaleDownRules:               generateScalingRules(0, 0, 50, 120, 300),
  4379  			currentReplicas:              100,
  4380  			prenormalizedDesiredReplicas: 0,
  4381  			expectedReplicas:             56, // (100 + 12) - 50%
  4382  			expectedCondition:            "ScaleDownLimit",
  4383  		},
  4384  		{
  4385  			name:                         "scaleDown with percent policy and previous scale up and down events",
  4386  			scaleUpEvents:                generateEventsUniformDistribution([]int{2}, 120),
  4387  			scaleDownEvents:              generateEventsUniformDistribution([]int{4}, 120),
  4388  			specMinReplicas:              1,
  4389  			specMaxReplicas:              1000,
  4390  			scaleDownRules:               generateScalingRules(0, 0, 50, 180, 0),
  4391  			currentReplicas:              10,
  4392  			prenormalizedDesiredReplicas: 1,
  4393  			expectedReplicas:             6,
  4394  			expectedCondition:            "ScaleDownLimit",
  4395  		},
  4396  		{
  4397  			// corner case for calculating the scaleDownLimit, when we changed pod or percent policy after a lot of scaleDown events
  4398  			// in this case we shouldn't allow scale down, though, the naive formula will suggest that scaleDownlimit is more then CurrentReplicas (100+30-10% > 100)
  4399  			name:                         "scaleDown with previous events preventing further scale down",
  4400  			scaleDownEvents:              generateEventsUniformDistribution([]int{10, 10, 10}, 120),
  4401  			specMinReplicas:              1,
  4402  			specMaxReplicas:              1000,
  4403  			scaleDownRules:               generateScalingRules(0, 0, 10, 120, 300),
  4404  			currentReplicas:              100,
  4405  			prenormalizedDesiredReplicas: 0,
  4406  			expectedReplicas:             100, // (100 + 30) - 10% = 117 is more then 100 (currentReplicas), keep 100
  4407  			expectedCondition:            "ScaleDownLimit",
  4408  		},
  4409  		{
  4410  			// corner case, the same as above, but calculation shows that we should go below zero
  4411  			name:                         "scaleDown with with previous events still allowing more scale down",
  4412  			scaleDownEvents:              generateEventsUniformDistribution([]int{10, 10, 10}, 120),
  4413  			specMinReplicas:              1,
  4414  			specMaxReplicas:              1000,
  4415  			scaleDownRules:               generateScalingRules(0, 0, 1000, 120, 300),
  4416  			currentReplicas:              10,
  4417  			prenormalizedDesiredReplicas: 5,
  4418  			expectedReplicas:             5, // (10 + 30) - 1000% = -360 is less than 0 and less then 5 (desired by metrics), set 5
  4419  			expectedCondition:            "DesiredWithinRange",
  4420  		},
  4421  		{
  4422  			name:                         "check 'outdated' flag for events for one behavior for up",
  4423  			scaleUpEvents:                generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  4424  			specMinReplicas:              1,
  4425  			specMaxReplicas:              1000,
  4426  			scaleUpRules:                 generateScalingRules(1000, 60, 0, 0, 0),
  4427  			currentReplicas:              100,
  4428  			prenormalizedDesiredReplicas: 200,
  4429  			expectedReplicas:             200,
  4430  			expectedCondition:            "DesiredWithinRange",
  4431  		},
  4432  		{
  4433  			name:                         "check that events were not marked 'outdated' for two different policies in the behavior for up",
  4434  			scaleUpEvents:                generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  4435  			specMinReplicas:              1,
  4436  			specMaxReplicas:              1000,
  4437  			scaleUpRules:                 generateScalingRules(1000, 120, 100, 60, 0),
  4438  			currentReplicas:              100,
  4439  			prenormalizedDesiredReplicas: 200,
  4440  			expectedReplicas:             200,
  4441  			expectedCondition:            "DesiredWithinRange",
  4442  		},
  4443  		{
  4444  			name:                         "check that events were marked 'outdated' for two different policies in the behavior for up",
  4445  			scaleUpEvents:                generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  4446  			specMinReplicas:              1,
  4447  			specMaxReplicas:              1000,
  4448  			scaleUpRules:                 generateScalingRules(1000, 30, 100, 60, 0),
  4449  			currentReplicas:              100,
  4450  			prenormalizedDesiredReplicas: 200,
  4451  			expectedReplicas:             200,
  4452  			expectedCondition:            "DesiredWithinRange",
  4453  		},
  4454  		{
  4455  			name:                         "check 'outdated' flag for events for one behavior for down",
  4456  			scaleDownEvents:              generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  4457  			specMinReplicas:              1,
  4458  			specMaxReplicas:              1000,
  4459  			scaleDownRules:               generateScalingRules(1000, 60, 0, 0, 300),
  4460  			currentReplicas:              100,
  4461  			prenormalizedDesiredReplicas: 5,
  4462  			expectedReplicas:             5,
  4463  			expectedCondition:            "DesiredWithinRange",
  4464  		},
  4465  		{
  4466  			name:                         "check that events were not marked 'outdated' for two different policies in the behavior for down",
  4467  			scaleDownEvents:              generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  4468  			specMinReplicas:              1,
  4469  			specMaxReplicas:              1000,
  4470  			scaleDownRules:               generateScalingRules(1000, 120, 100, 60, 300),
  4471  			currentReplicas:              100,
  4472  			prenormalizedDesiredReplicas: 5,
  4473  			expectedReplicas:             5,
  4474  			expectedCondition:            "DesiredWithinRange",
  4475  		},
  4476  		{
  4477  			name:                         "check that events were marked 'outdated' for two different policies in the behavior for down",
  4478  			scaleDownEvents:              generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  4479  			specMinReplicas:              1,
  4480  			specMaxReplicas:              1000,
  4481  			scaleDownRules:               generateScalingRules(1000, 30, 100, 60, 300),
  4482  			currentReplicas:              100,
  4483  			prenormalizedDesiredReplicas: 5,
  4484  			expectedReplicas:             5,
  4485  			expectedCondition:            "DesiredWithinRange",
  4486  		},
  4487  	}
  4488  
  4489  	for _, tc := range tests {
  4490  		t.Run(tc.name, func(t *testing.T) {
  4491  
  4492  			if tc.testThis {
  4493  				return
  4494  			}
  4495  			hc := HorizontalController{
  4496  				scaleUpEvents: map[string][]timestampedScaleEvent{
  4497  					tc.key: tc.scaleUpEvents,
  4498  				},
  4499  				scaleDownEvents: map[string][]timestampedScaleEvent{
  4500  					tc.key: tc.scaleDownEvents,
  4501  				},
  4502  			}
  4503  			arg := NormalizationArg{
  4504  				Key:               tc.key,
  4505  				ScaleUpBehavior:   autoscalingapiv2.GenerateHPAScaleUpRules(tc.scaleUpRules),
  4506  				ScaleDownBehavior: autoscalingapiv2.GenerateHPAScaleDownRules(tc.scaleDownRules),
  4507  				MinReplicas:       tc.specMinReplicas,
  4508  				MaxReplicas:       tc.specMaxReplicas,
  4509  				DesiredReplicas:   tc.prenormalizedDesiredReplicas,
  4510  				CurrentReplicas:   tc.currentReplicas,
  4511  			}
  4512  
  4513  			replicas, condition, _ := hc.convertDesiredReplicasWithBehaviorRate(arg)
  4514  			assert.Equal(t, tc.expectedReplicas, replicas, "expected replicas do not match with converted replicas")
  4515  			assert.Equal(t, tc.expectedCondition, condition, "HPA condition does not match with expected condition")
  4516  		})
  4517  	}
  4518  
  4519  }
  4520  
  4521  // TestStoreScaleEvents tests events storage and usage
  4522  func TestStoreScaleEvents(t *testing.T) {
  4523  	type TestCase struct {
  4524  		name                   string
  4525  		key                    string
  4526  		replicaChange          int32
  4527  		prevScaleEvents        []timestampedScaleEvent
  4528  		newScaleEvents         []timestampedScaleEvent
  4529  		scalingRules           *autoscalingv2.HPAScalingRules
  4530  		expectedReplicasChange int32
  4531  	}
  4532  	tests := []TestCase{
  4533  		{
  4534  			name:                   "empty entries with default behavior",
  4535  			replicaChange:          5,
  4536  			prevScaleEvents:        []timestampedScaleEvent{}, // no history -> 0 replica change
  4537  			newScaleEvents:         []timestampedScaleEvent{}, // no behavior -> no events are stored
  4538  			expectedReplicasChange: 0,
  4539  		},
  4540  		{
  4541  			name:                   "empty entries with two-policy-behavior",
  4542  			replicaChange:          5,
  4543  			prevScaleEvents:        []timestampedScaleEvent{}, // no history -> 0 replica change
  4544  			newScaleEvents:         []timestampedScaleEvent{{5, time.Now(), false}},
  4545  			scalingRules:           generateScalingRules(10, 60, 100, 60, 0),
  4546  			expectedReplicasChange: 0,
  4547  		},
  4548  		{
  4549  			name:          "one outdated entry to be kept untouched without behavior",
  4550  			replicaChange: 5,
  4551  			prevScaleEvents: []timestampedScaleEvent{
  4552  				{7, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be replaced
  4553  			},
  4554  			newScaleEvents: []timestampedScaleEvent{
  4555  				{7, time.Now(), false}, // no behavior -> we don't touch stored events
  4556  			},
  4557  			expectedReplicasChange: 0,
  4558  		},
  4559  		{
  4560  			name:          "one outdated entry to be replaced with behavior",
  4561  			replicaChange: 5,
  4562  			prevScaleEvents: []timestampedScaleEvent{
  4563  				{7, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be replaced
  4564  			},
  4565  			newScaleEvents: []timestampedScaleEvent{
  4566  				{5, time.Now(), false},
  4567  			},
  4568  			scalingRules:           generateScalingRules(10, 60, 100, 60, 0),
  4569  			expectedReplicasChange: 0,
  4570  		},
  4571  		{
  4572  			name:          "one actual entry to be not touched with behavior",
  4573  			replicaChange: 5,
  4574  			prevScaleEvents: []timestampedScaleEvent{
  4575  				{7, time.Now().Add(-time.Second * time.Duration(58)), false},
  4576  			},
  4577  			newScaleEvents: []timestampedScaleEvent{
  4578  				{7, time.Now(), false},
  4579  				{5, time.Now(), false},
  4580  			},
  4581  			scalingRules:           generateScalingRules(10, 60, 100, 60, 0),
  4582  			expectedReplicasChange: 7,
  4583  		},
  4584  		{
  4585  			name:          "two entries, one of them to be replaced",
  4586  			replicaChange: 5,
  4587  			prevScaleEvents: []timestampedScaleEvent{
  4588  				{7, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be replaced
  4589  				{6, time.Now().Add(-time.Second * time.Duration(59)), false},
  4590  			},
  4591  			newScaleEvents: []timestampedScaleEvent{
  4592  				{5, time.Now(), false},
  4593  				{6, time.Now(), false},
  4594  			},
  4595  			scalingRules:           generateScalingRules(10, 60, 0, 0, 0),
  4596  			expectedReplicasChange: 6,
  4597  		},
  4598  		{
  4599  			name:          "replace one entry, use policies with different periods",
  4600  			replicaChange: 5,
  4601  			prevScaleEvents: []timestampedScaleEvent{
  4602  				{8, time.Now().Add(-time.Second * time.Duration(29)), false},
  4603  				{6, time.Now().Add(-time.Second * time.Duration(59)), false},
  4604  				{7, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be marked as outdated
  4605  				{9, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be replaced
  4606  			},
  4607  			newScaleEvents: []timestampedScaleEvent{
  4608  				{8, time.Now(), false},
  4609  				{6, time.Now(), false},
  4610  				{7, time.Now(), true},
  4611  				{5, time.Now(), false},
  4612  			},
  4613  			scalingRules:           generateScalingRules(10, 60, 100, 30, 0),
  4614  			expectedReplicasChange: 14,
  4615  		},
  4616  		{
  4617  			name:          "two entries, both actual",
  4618  			replicaChange: 5,
  4619  			prevScaleEvents: []timestampedScaleEvent{
  4620  				{7, time.Now().Add(-time.Second * time.Duration(58)), false},
  4621  				{6, time.Now().Add(-time.Second * time.Duration(59)), false},
  4622  			},
  4623  			newScaleEvents: []timestampedScaleEvent{
  4624  				{7, time.Now(), false},
  4625  				{6, time.Now(), false},
  4626  				{5, time.Now(), false},
  4627  			},
  4628  			scalingRules:           generateScalingRules(10, 120, 100, 30, 0),
  4629  			expectedReplicasChange: 13,
  4630  		},
  4631  	}
  4632  
  4633  	for _, tc := range tests {
  4634  		t.Run(tc.name, func(t *testing.T) {
  4635  			// testing scale up
  4636  			var behaviorUp *autoscalingv2.HorizontalPodAutoscalerBehavior
  4637  			if tc.scalingRules != nil {
  4638  				behaviorUp = &autoscalingv2.HorizontalPodAutoscalerBehavior{
  4639  					ScaleUp: tc.scalingRules,
  4640  				}
  4641  			}
  4642  			hcUp := HorizontalController{
  4643  				scaleUpEvents: map[string][]timestampedScaleEvent{
  4644  					tc.key: append([]timestampedScaleEvent{}, tc.prevScaleEvents...),
  4645  				},
  4646  			}
  4647  			gotReplicasChangeUp := getReplicasChangePerPeriod(60, hcUp.scaleUpEvents[tc.key])
  4648  			assert.Equal(t, tc.expectedReplicasChange, gotReplicasChangeUp)
  4649  			hcUp.storeScaleEvent(behaviorUp, tc.key, 10, 10+tc.replicaChange)
  4650  			if !assert.Len(t, hcUp.scaleUpEvents[tc.key], len(tc.newScaleEvents), "up: scale events differ in length") {
  4651  				return
  4652  			}
  4653  			for i, gotEvent := range hcUp.scaleUpEvents[tc.key] {
  4654  				expEvent := tc.newScaleEvents[i]
  4655  				assert.Equal(t, expEvent.replicaChange, gotEvent.replicaChange, "up: idx:%v replicaChange", i)
  4656  				assert.Equal(t, expEvent.outdated, gotEvent.outdated, "up: idx:%v outdated", i)
  4657  			}
  4658  			// testing scale down
  4659  			var behaviorDown *autoscalingv2.HorizontalPodAutoscalerBehavior
  4660  			if tc.scalingRules != nil {
  4661  				behaviorDown = &autoscalingv2.HorizontalPodAutoscalerBehavior{
  4662  					ScaleDown: tc.scalingRules,
  4663  				}
  4664  			}
  4665  			hcDown := HorizontalController{
  4666  				scaleDownEvents: map[string][]timestampedScaleEvent{
  4667  					tc.key: append([]timestampedScaleEvent{}, tc.prevScaleEvents...),
  4668  				},
  4669  			}
  4670  			gotReplicasChangeDown := getReplicasChangePerPeriod(60, hcDown.scaleDownEvents[tc.key])
  4671  			assert.Equal(t, tc.expectedReplicasChange, gotReplicasChangeDown)
  4672  			hcDown.storeScaleEvent(behaviorDown, tc.key, 10, 10-tc.replicaChange)
  4673  			if !assert.Len(t, hcDown.scaleDownEvents[tc.key], len(tc.newScaleEvents), "down: scale events differ in length") {
  4674  				return
  4675  			}
  4676  			for i, gotEvent := range hcDown.scaleDownEvents[tc.key] {
  4677  				expEvent := tc.newScaleEvents[i]
  4678  				assert.Equal(t, expEvent.replicaChange, gotEvent.replicaChange, "down: idx:%v replicaChange", i)
  4679  				assert.Equal(t, expEvent.outdated, gotEvent.outdated, "down: idx:%v outdated", i)
  4680  			}
  4681  		})
  4682  	}
  4683  }
  4684  
  4685  func TestNormalizeDesiredReplicasWithBehavior(t *testing.T) {
  4686  	now := time.Now()
  4687  	type TestCase struct {
  4688  		name                                string
  4689  		key                                 string
  4690  		recommendations                     []timestampedRecommendation
  4691  		currentReplicas                     int32
  4692  		prenormalizedDesiredReplicas        int32
  4693  		expectedStabilizedReplicas          int32
  4694  		expectedRecommendations             []timestampedRecommendation
  4695  		scaleUpStabilizationWindowSeconds   int32
  4696  		scaleDownStabilizationWindowSeconds int32
  4697  	}
  4698  	tests := []TestCase{
  4699  		{
  4700  			name:                         "empty recommendations for scaling down",
  4701  			key:                          "",
  4702  			recommendations:              []timestampedRecommendation{},
  4703  			currentReplicas:              100,
  4704  			prenormalizedDesiredReplicas: 5,
  4705  			expectedStabilizedReplicas:   5,
  4706  			expectedRecommendations: []timestampedRecommendation{
  4707  				{5, now},
  4708  			},
  4709  		},
  4710  		{
  4711  			name: "simple scale down stabilization",
  4712  			key:  "",
  4713  			recommendations: []timestampedRecommendation{
  4714  				{4, now.Add(-2 * time.Minute)},
  4715  				{5, now.Add(-1 * time.Minute)}},
  4716  			currentReplicas:              100,
  4717  			prenormalizedDesiredReplicas: 3,
  4718  			expectedStabilizedReplicas:   5,
  4719  			expectedRecommendations: []timestampedRecommendation{
  4720  				{4, now},
  4721  				{5, now},
  4722  				{3, now},
  4723  			},
  4724  			scaleDownStabilizationWindowSeconds: 60 * 3,
  4725  		},
  4726  		{
  4727  			name: "simple scale up stabilization",
  4728  			key:  "",
  4729  			recommendations: []timestampedRecommendation{
  4730  				{4, now.Add(-2 * time.Minute)},
  4731  				{5, now.Add(-1 * time.Minute)}},
  4732  			currentReplicas:              1,
  4733  			prenormalizedDesiredReplicas: 7,
  4734  			expectedStabilizedReplicas:   4,
  4735  			expectedRecommendations: []timestampedRecommendation{
  4736  				{4, now},
  4737  				{5, now},
  4738  				{7, now},
  4739  			},
  4740  			scaleUpStabilizationWindowSeconds: 60 * 5,
  4741  		},
  4742  		{
  4743  			name: "no scale down stabilization",
  4744  			key:  "",
  4745  			recommendations: []timestampedRecommendation{
  4746  				{1, now.Add(-2 * time.Minute)},
  4747  				{2, now.Add(-1 * time.Minute)}},
  4748  			currentReplicas:              100, // to apply scaleDown delay we should have current > desired
  4749  			prenormalizedDesiredReplicas: 3,
  4750  			expectedStabilizedReplicas:   3,
  4751  			expectedRecommendations: []timestampedRecommendation{
  4752  				{1, now},
  4753  				{2, now},
  4754  				{3, now},
  4755  			},
  4756  			scaleUpStabilizationWindowSeconds: 60 * 5,
  4757  		},
  4758  		{
  4759  			name: "no scale up stabilization",
  4760  			key:  "",
  4761  			recommendations: []timestampedRecommendation{
  4762  				{4, now.Add(-2 * time.Minute)},
  4763  				{5, now.Add(-1 * time.Minute)}},
  4764  			currentReplicas:              1, // to apply scaleDown delay we should have current > desired
  4765  			prenormalizedDesiredReplicas: 3,
  4766  			expectedStabilizedReplicas:   3,
  4767  			expectedRecommendations: []timestampedRecommendation{
  4768  				{4, now},
  4769  				{5, now},
  4770  				{3, now},
  4771  			},
  4772  			scaleDownStabilizationWindowSeconds: 60 * 5,
  4773  		},
  4774  		{
  4775  			name: "no scale down stabilization, reuse recommendation element",
  4776  			key:  "",
  4777  			recommendations: []timestampedRecommendation{
  4778  				{10, now.Add(-10 * time.Minute)},
  4779  				{9, now.Add(-9 * time.Minute)}},
  4780  			currentReplicas:              100, // to apply scaleDown delay we should have current > desired
  4781  			prenormalizedDesiredReplicas: 3,
  4782  			expectedStabilizedReplicas:   3,
  4783  			expectedRecommendations: []timestampedRecommendation{
  4784  				{10, now},
  4785  				{3, now},
  4786  			},
  4787  		},
  4788  		{
  4789  			name: "no scale up stabilization, reuse recommendation element",
  4790  			key:  "",
  4791  			recommendations: []timestampedRecommendation{
  4792  				{10, now.Add(-10 * time.Minute)},
  4793  				{9, now.Add(-9 * time.Minute)}},
  4794  			currentReplicas:              1,
  4795  			prenormalizedDesiredReplicas: 100,
  4796  			expectedStabilizedReplicas:   100,
  4797  			expectedRecommendations: []timestampedRecommendation{
  4798  				{10, now},
  4799  				{100, now},
  4800  			},
  4801  		},
  4802  		{
  4803  			name: "scale down stabilization, reuse one of obsolete recommendation element",
  4804  			key:  "",
  4805  			recommendations: []timestampedRecommendation{
  4806  				{10, now.Add(-10 * time.Minute)},
  4807  				{4, now.Add(-1 * time.Minute)},
  4808  				{5, now.Add(-2 * time.Minute)},
  4809  				{9, now.Add(-9 * time.Minute)}},
  4810  			currentReplicas:              100,
  4811  			prenormalizedDesiredReplicas: 3,
  4812  			expectedStabilizedReplicas:   5,
  4813  			expectedRecommendations: []timestampedRecommendation{
  4814  				{10, now},
  4815  				{4, now},
  4816  				{5, now},
  4817  				{3, now},
  4818  			},
  4819  			scaleDownStabilizationWindowSeconds: 3 * 60,
  4820  		},
  4821  		{
  4822  			// we can reuse only the first recommendation element
  4823  			// as the scale up delay = 150 (set in test), scale down delay = 300 (by default)
  4824  			// hence, only the first recommendation is obsolete for both scale up and scale down
  4825  			name: "scale up stabilization, reuse one of obsolete recommendation element",
  4826  			key:  "",
  4827  			recommendations: []timestampedRecommendation{
  4828  				{10, now.Add(-100 * time.Minute)},
  4829  				{6, now.Add(-1 * time.Minute)},
  4830  				{5, now.Add(-2 * time.Minute)},
  4831  				{9, now.Add(-3 * time.Minute)}},
  4832  			currentReplicas:              1,
  4833  			prenormalizedDesiredReplicas: 100,
  4834  			expectedStabilizedReplicas:   5,
  4835  			expectedRecommendations: []timestampedRecommendation{
  4836  				{100, now},
  4837  				{6, now},
  4838  				{5, now},
  4839  				{9, now},
  4840  			},
  4841  			scaleUpStabilizationWindowSeconds: 300,
  4842  		}, {
  4843  			name: "scale up and down stabilization, do not scale up when prenormalized rec goes down",
  4844  			key:  "",
  4845  			recommendations: []timestampedRecommendation{
  4846  				{2, now.Add(-100 * time.Minute)},
  4847  				{3, now.Add(-3 * time.Minute)},
  4848  			},
  4849  			currentReplicas:                     2,
  4850  			prenormalizedDesiredReplicas:        1,
  4851  			expectedStabilizedReplicas:          2,
  4852  			scaleUpStabilizationWindowSeconds:   300,
  4853  			scaleDownStabilizationWindowSeconds: 300,
  4854  		}, {
  4855  			name: "scale up and down stabilization, do not scale down when prenormalized rec goes up",
  4856  			key:  "",
  4857  			recommendations: []timestampedRecommendation{
  4858  				{2, now.Add(-100 * time.Minute)},
  4859  				{1, now.Add(-3 * time.Minute)},
  4860  			},
  4861  			currentReplicas:                     2,
  4862  			prenormalizedDesiredReplicas:        3,
  4863  			expectedStabilizedReplicas:          2,
  4864  			scaleUpStabilizationWindowSeconds:   300,
  4865  			scaleDownStabilizationWindowSeconds: 300,
  4866  		},
  4867  	}
  4868  	for _, tc := range tests {
  4869  		t.Run(tc.name, func(t *testing.T) {
  4870  			hc := HorizontalController{
  4871  				recommendations: map[string][]timestampedRecommendation{
  4872  					tc.key: tc.recommendations,
  4873  				},
  4874  			}
  4875  			arg := NormalizationArg{
  4876  				Key:             tc.key,
  4877  				DesiredReplicas: tc.prenormalizedDesiredReplicas,
  4878  				CurrentReplicas: tc.currentReplicas,
  4879  				ScaleUpBehavior: &autoscalingv2.HPAScalingRules{
  4880  					StabilizationWindowSeconds: &tc.scaleUpStabilizationWindowSeconds,
  4881  				},
  4882  				ScaleDownBehavior: &autoscalingv2.HPAScalingRules{
  4883  					StabilizationWindowSeconds: &tc.scaleDownStabilizationWindowSeconds,
  4884  				},
  4885  			}
  4886  			r, _, _ := hc.stabilizeRecommendationWithBehaviors(arg)
  4887  			assert.Equal(t, tc.expectedStabilizedReplicas, r, "expected replicas do not match")
  4888  			if tc.expectedRecommendations != nil {
  4889  				if !assert.Len(t, hc.recommendations[tc.key], len(tc.expectedRecommendations), "stored recommendations differ in length") {
  4890  					return
  4891  				}
  4892  				for i, r := range hc.recommendations[tc.key] {
  4893  					expectedRecommendation := tc.expectedRecommendations[i]
  4894  					assert.Equal(t, expectedRecommendation.recommendation, r.recommendation, "stored recommendation differs at position %d", i)
  4895  				}
  4896  			}
  4897  		})
  4898  	}
  4899  }
  4900  
  4901  func TestScaleUpOneMetricEmpty(t *testing.T) {
  4902  	tc := testCase{
  4903  		minReplicas:             2,
  4904  		maxReplicas:             6,
  4905  		specReplicas:            3,
  4906  		statusReplicas:          3,
  4907  		expectedDesiredReplicas: 4,
  4908  		CPUTarget:               30,
  4909  		verifyCPUCurrent:        true,
  4910  		metricsTarget: []autoscalingv2.MetricSpec{
  4911  			{
  4912  				Type: autoscalingv2.ExternalMetricSourceType,
  4913  				External: &autoscalingv2.ExternalMetricSource{
  4914  					Metric: autoscalingv2.MetricIdentifier{
  4915  						Name:     "qps",
  4916  						Selector: &metav1.LabelSelector{},
  4917  					},
  4918  					Target: autoscalingv2.MetricTarget{
  4919  						Type:  autoscalingv2.ValueMetricType,
  4920  						Value: resource.NewMilliQuantity(100, resource.DecimalSI),
  4921  					},
  4922  				},
  4923  			},
  4924  		},
  4925  		reportedLevels:      []uint64{300, 400, 500},
  4926  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  4927  		expectedReportedReconciliationActionLabel: monitor.ActionLabelScaleUp,
  4928  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  4929  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  4930  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleUp,
  4931  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelNone,
  4932  		},
  4933  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  4934  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  4935  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelInternal,
  4936  		},
  4937  	}
  4938  	_, _, _, testEMClient, _ := tc.prepareTestClient(t)
  4939  	testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  4940  		return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  4941  	})
  4942  	tc.testEMClient = testEMClient
  4943  	tc.runTest(t)
  4944  }
  4945  
  4946  func TestNoScaleDownOneMetricInvalid(t *testing.T) {
  4947  	tc := testCase{
  4948  		minReplicas:             2,
  4949  		maxReplicas:             6,
  4950  		specReplicas:            5,
  4951  		statusReplicas:          5,
  4952  		expectedDesiredReplicas: 5,
  4953  		CPUTarget:               50,
  4954  		metricsTarget: []autoscalingv2.MetricSpec{
  4955  			{
  4956  				Type: "CheddarCheese",
  4957  			},
  4958  		},
  4959  		reportedLevels:      []uint64{100, 300, 500, 250, 250},
  4960  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  4961  		useMetricsAPI:       true,
  4962  		recommendations:     []timestampedRecommendation{},
  4963  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  4964  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  4965  			{Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "InvalidMetricSourceType"},
  4966  		},
  4967  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  4968  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  4969  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  4970  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  4971  			"CheddarCheese":                        monitor.ActionLabelNone,
  4972  		},
  4973  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  4974  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  4975  			"CheddarCheese":                        monitor.ErrorLabelSpec,
  4976  		},
  4977  	}
  4978  
  4979  	tc.runTest(t)
  4980  }
  4981  
  4982  func TestNoScaleDownOneMetricEmpty(t *testing.T) {
  4983  	tc := testCase{
  4984  		minReplicas:             2,
  4985  		maxReplicas:             6,
  4986  		specReplicas:            5,
  4987  		statusReplicas:          5,
  4988  		expectedDesiredReplicas: 5,
  4989  		CPUTarget:               50,
  4990  		metricsTarget: []autoscalingv2.MetricSpec{
  4991  			{
  4992  				Type: autoscalingv2.ExternalMetricSourceType,
  4993  				External: &autoscalingv2.ExternalMetricSource{
  4994  					Metric: autoscalingv2.MetricIdentifier{
  4995  						Name:     "qps",
  4996  						Selector: &metav1.LabelSelector{},
  4997  					},
  4998  					Target: autoscalingv2.MetricTarget{
  4999  						Type:  autoscalingv2.ValueMetricType,
  5000  						Value: resource.NewMilliQuantity(1000, resource.DecimalSI),
  5001  					},
  5002  				},
  5003  			},
  5004  		},
  5005  		reportedLevels:      []uint64{100, 300, 500, 250, 250},
  5006  		reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  5007  		useMetricsAPI:       true,
  5008  		recommendations:     []timestampedRecommendation{},
  5009  		expectedConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  5010  			{Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  5011  			{Type: autoscalingv2.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetExternalMetric"},
  5012  		},
  5013  		expectedReportedReconciliationActionLabel: monitor.ActionLabelNone,
  5014  		expectedReportedReconciliationErrorLabel:  monitor.ErrorLabelInternal,
  5015  		expectedReportedMetricComputationActionLabels: map[autoscalingv2.MetricSourceType]monitor.ActionLabel{
  5016  			autoscalingv2.ResourceMetricSourceType: monitor.ActionLabelScaleDown,
  5017  			autoscalingv2.ExternalMetricSourceType: monitor.ActionLabelNone,
  5018  		},
  5019  		expectedReportedMetricComputationErrorLabels: map[autoscalingv2.MetricSourceType]monitor.ErrorLabel{
  5020  			autoscalingv2.ResourceMetricSourceType: monitor.ErrorLabelNone,
  5021  			autoscalingv2.ExternalMetricSourceType: monitor.ErrorLabelInternal,
  5022  		},
  5023  	}
  5024  	_, _, _, testEMClient, _ := tc.prepareTestClient(t)
  5025  	testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  5026  		return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  5027  	})
  5028  	tc.testEMClient = testEMClient
  5029  	tc.runTest(t)
  5030  }
  5031  
  5032  func TestMultipleHPAs(t *testing.T) {
  5033  	const hpaCount = 1000
  5034  	const testNamespace = "dummy-namespace"
  5035  
  5036  	processed := make(chan string, hpaCount)
  5037  
  5038  	testClient := &fake.Clientset{}
  5039  	testScaleClient := &scalefake.FakeScaleClient{}
  5040  	testMetricsClient := &metricsfake.Clientset{}
  5041  
  5042  	hpaList := [hpaCount]autoscalingv2.HorizontalPodAutoscaler{}
  5043  	scaleUpEventsMap := map[string][]timestampedScaleEvent{}
  5044  	scaleDownEventsMap := map[string][]timestampedScaleEvent{}
  5045  	scaleList := map[string]*autoscalingv1.Scale{}
  5046  	podList := map[string]*v1.Pod{}
  5047  
  5048  	var minReplicas int32 = 1
  5049  	var cpuTarget int32 = 10
  5050  
  5051  	// generate resources (HPAs, Scales, Pods...)
  5052  	for i := 0; i < hpaCount; i++ {
  5053  		hpaName := fmt.Sprintf("dummy-hpa-%v", i)
  5054  		deploymentName := fmt.Sprintf("dummy-target-%v", i)
  5055  		labelSet := map[string]string{"name": deploymentName}
  5056  		selector := labels.SelectorFromSet(labelSet).String()
  5057  
  5058  		// generate HPAs
  5059  		h := autoscalingv2.HorizontalPodAutoscaler{
  5060  			ObjectMeta: metav1.ObjectMeta{
  5061  				Name:      hpaName,
  5062  				Namespace: testNamespace,
  5063  			},
  5064  			Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
  5065  				ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
  5066  					APIVersion: "apps/v1",
  5067  					Kind:       "Deployment",
  5068  					Name:       deploymentName,
  5069  				},
  5070  				MinReplicas: &minReplicas,
  5071  				MaxReplicas: 10,
  5072  				Behavior: &autoscalingv2.HorizontalPodAutoscalerBehavior{
  5073  					ScaleUp:   generateScalingRules(100, 60, 0, 0, 0),
  5074  					ScaleDown: generateScalingRules(2, 60, 1, 60, 300),
  5075  				},
  5076  				Metrics: []autoscalingv2.MetricSpec{
  5077  					{
  5078  						Type: autoscalingv2.ResourceMetricSourceType,
  5079  						Resource: &autoscalingv2.ResourceMetricSource{
  5080  							Name: v1.ResourceCPU,
  5081  							Target: autoscalingv2.MetricTarget{
  5082  								Type:               autoscalingv2.UtilizationMetricType,
  5083  								AverageUtilization: &cpuTarget,
  5084  							},
  5085  						},
  5086  					},
  5087  				},
  5088  			},
  5089  			Status: autoscalingv2.HorizontalPodAutoscalerStatus{
  5090  				CurrentReplicas: 1,
  5091  				DesiredReplicas: 5,
  5092  				LastScaleTime:   &metav1.Time{Time: time.Now()},
  5093  			},
  5094  		}
  5095  		hpaList[i] = h
  5096  
  5097  		// generate Scale
  5098  		scaleList[deploymentName] = &autoscalingv1.Scale{
  5099  			ObjectMeta: metav1.ObjectMeta{
  5100  				Name:      deploymentName,
  5101  				Namespace: testNamespace,
  5102  			},
  5103  			Spec: autoscalingv1.ScaleSpec{
  5104  				Replicas: 1,
  5105  			},
  5106  			Status: autoscalingv1.ScaleStatus{
  5107  				Replicas: 1,
  5108  				Selector: selector,
  5109  			},
  5110  		}
  5111  
  5112  		// generate Pods
  5113  		cpuRequest := resource.MustParse("1.0")
  5114  		pod := v1.Pod{
  5115  			Status: v1.PodStatus{
  5116  				Phase: v1.PodRunning,
  5117  				Conditions: []v1.PodCondition{
  5118  					{
  5119  						Type:   v1.PodReady,
  5120  						Status: v1.ConditionTrue,
  5121  					},
  5122  				},
  5123  				StartTime: &metav1.Time{Time: time.Now().Add(-10 * time.Minute)},
  5124  			},
  5125  			ObjectMeta: metav1.ObjectMeta{
  5126  				Name:      fmt.Sprintf("%s-0", deploymentName),
  5127  				Namespace: testNamespace,
  5128  				Labels:    labelSet,
  5129  			},
  5130  
  5131  			Spec: v1.PodSpec{
  5132  				Containers: []v1.Container{
  5133  					{
  5134  						Name: "container1",
  5135  						Resources: v1.ResourceRequirements{
  5136  							Requests: v1.ResourceList{
  5137  								v1.ResourceCPU: *resource.NewMilliQuantity(cpuRequest.MilliValue()/2, resource.DecimalSI),
  5138  							},
  5139  						},
  5140  					},
  5141  					{
  5142  						Name: "container2",
  5143  						Resources: v1.ResourceRequirements{
  5144  							Requests: v1.ResourceList{
  5145  								v1.ResourceCPU: *resource.NewMilliQuantity(cpuRequest.MilliValue()/2, resource.DecimalSI),
  5146  							},
  5147  						},
  5148  					},
  5149  				},
  5150  			},
  5151  		}
  5152  		podList[deploymentName] = &pod
  5153  
  5154  		scaleUpEventsMap[fmt.Sprintf("%s/%s", testNamespace, hpaName)] = generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120)
  5155  		scaleDownEventsMap[fmt.Sprintf("%s/%s", testNamespace, hpaName)] = generateEventsUniformDistribution([]int{10, 10, 10}, 120)
  5156  	}
  5157  
  5158  	testMetricsClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  5159  		podNamePrefix := ""
  5160  		labelSet := map[string]string{}
  5161  
  5162  		// selector should be in form: "name=dummy-target-X" where X is the number of resource
  5163  		selector := action.(core.ListAction).GetListRestrictions().Labels
  5164  		parsedSelector := strings.Split(selector.String(), "=")
  5165  		if len(parsedSelector) > 1 {
  5166  			labelSet[parsedSelector[0]] = parsedSelector[1]
  5167  			podNamePrefix = parsedSelector[1]
  5168  		}
  5169  
  5170  		podMetric := metricsapi.PodMetrics{
  5171  			ObjectMeta: metav1.ObjectMeta{
  5172  				Name:      fmt.Sprintf("%s-0", podNamePrefix),
  5173  				Namespace: testNamespace,
  5174  				Labels:    labelSet,
  5175  			},
  5176  			Timestamp: metav1.Time{Time: time.Now()},
  5177  			Window:    metav1.Duration{Duration: time.Minute},
  5178  			Containers: []metricsapi.ContainerMetrics{
  5179  				{
  5180  					Name: "container1",
  5181  					Usage: v1.ResourceList{
  5182  						v1.ResourceCPU: *resource.NewMilliQuantity(
  5183  							int64(200),
  5184  							resource.DecimalSI),
  5185  						v1.ResourceMemory: *resource.NewQuantity(
  5186  							int64(1024*1024/2),
  5187  							resource.BinarySI),
  5188  					},
  5189  				},
  5190  				{
  5191  					Name: "container2",
  5192  					Usage: v1.ResourceList{
  5193  						v1.ResourceCPU: *resource.NewMilliQuantity(
  5194  							int64(300),
  5195  							resource.DecimalSI),
  5196  						v1.ResourceMemory: *resource.NewQuantity(
  5197  							int64(1024*1024/2),
  5198  							resource.BinarySI),
  5199  					},
  5200  				},
  5201  			},
  5202  		}
  5203  		metrics := &metricsapi.PodMetricsList{}
  5204  		metrics.Items = append(metrics.Items, podMetric)
  5205  
  5206  		return true, metrics, nil
  5207  	})
  5208  
  5209  	metricsClient := metrics.NewRESTMetricsClient(
  5210  		testMetricsClient.MetricsV1beta1(),
  5211  		&cmfake.FakeCustomMetricsClient{},
  5212  		&emfake.FakeExternalMetricsClient{},
  5213  	)
  5214  
  5215  	testScaleClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  5216  		deploymentName := action.(core.GetAction).GetName()
  5217  		obj := scaleList[deploymentName]
  5218  		return true, obj, nil
  5219  	})
  5220  
  5221  	testClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  5222  		obj := &v1.PodList{}
  5223  
  5224  		// selector should be in form: "name=dummy-target-X" where X is the number of resource
  5225  		selector := action.(core.ListAction).GetListRestrictions().Labels
  5226  		parsedSelector := strings.Split(selector.String(), "=")
  5227  
  5228  		// list with filter
  5229  		if len(parsedSelector) > 1 {
  5230  			obj.Items = append(obj.Items, *podList[parsedSelector[1]])
  5231  		} else {
  5232  			// no filter - return all pods
  5233  			for _, p := range podList {
  5234  				obj.Items = append(obj.Items, *p)
  5235  			}
  5236  		}
  5237  
  5238  		return true, obj, nil
  5239  	})
  5240  
  5241  	testClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  5242  		obj := &autoscalingv2.HorizontalPodAutoscalerList{
  5243  			Items: hpaList[:],
  5244  		}
  5245  		return true, obj, nil
  5246  	})
  5247  
  5248  	testClient.AddReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  5249  		handled, obj, err := func() (handled bool, ret *autoscalingv2.HorizontalPodAutoscaler, err error) {
  5250  			obj := action.(core.UpdateAction).GetObject().(*autoscalingv2.HorizontalPodAutoscaler)
  5251  			assert.Equal(t, testNamespace, obj.Namespace, "the HPA namespace should be as expected")
  5252  
  5253  			return true, obj, nil
  5254  		}()
  5255  		processed <- obj.Name
  5256  
  5257  		return handled, obj, err
  5258  	})
  5259  
  5260  	informerFactory := informers.NewSharedInformerFactory(testClient, controller.NoResyncPeriodFunc())
  5261  
  5262  	tCtx := ktesting.Init(t)
  5263  	hpaController := NewHorizontalController(
  5264  		tCtx,
  5265  		testClient.CoreV1(),
  5266  		testScaleClient,
  5267  		testClient.AutoscalingV2(),
  5268  		testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme),
  5269  		metricsClient,
  5270  		informerFactory.Autoscaling().V2().HorizontalPodAutoscalers(),
  5271  		informerFactory.Core().V1().Pods(),
  5272  		100*time.Millisecond,
  5273  		5*time.Minute,
  5274  		defaultTestingTolerance,
  5275  		defaultTestingCPUInitializationPeriod,
  5276  		defaultTestingDelayOfInitialReadinessStatus,
  5277  	)
  5278  	hpaController.scaleUpEvents = scaleUpEventsMap
  5279  	hpaController.scaleDownEvents = scaleDownEventsMap
  5280  
  5281  	informerFactory.Start(tCtx.Done())
  5282  	go hpaController.Run(tCtx, 5)
  5283  
  5284  	timeoutTime := time.After(15 * time.Second)
  5285  	timeout := false
  5286  	processedHPA := make(map[string]bool)
  5287  	for timeout == false && len(processedHPA) < hpaCount {
  5288  		select {
  5289  		case hpaName := <-processed:
  5290  			processedHPA[hpaName] = true
  5291  		case <-timeoutTime:
  5292  			timeout = true
  5293  		}
  5294  	}
  5295  
  5296  	assert.Equal(t, hpaCount, len(processedHPA), "Expected to process all HPAs")
  5297  }