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