horizontal_test.go 90 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package podautoscaler
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "math"
  18. "sync"
  19. "testing"
  20. "time"
  21. autoscalingv1 "k8s.io/api/autoscaling/v1"
  22. autoscalingv2 "k8s.io/api/autoscaling/v2beta2"
  23. v1 "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/api/meta/testrestmapper"
  25. "k8s.io/apimachinery/pkg/api/resource"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. "k8s.io/apimachinery/pkg/labels"
  28. "k8s.io/apimachinery/pkg/runtime"
  29. "k8s.io/apimachinery/pkg/runtime/schema"
  30. "k8s.io/apimachinery/pkg/watch"
  31. "k8s.io/client-go/informers"
  32. "k8s.io/client-go/kubernetes/fake"
  33. scalefake "k8s.io/client-go/scale/fake"
  34. core "k8s.io/client-go/testing"
  35. "k8s.io/kubernetes/pkg/api/legacyscheme"
  36. "k8s.io/kubernetes/pkg/apis/autoscaling"
  37. "k8s.io/kubernetes/pkg/controller"
  38. "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics"
  39. cmapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta2"
  40. emapi "k8s.io/metrics/pkg/apis/external_metrics/v1beta1"
  41. metricsapi "k8s.io/metrics/pkg/apis/metrics/v1beta1"
  42. metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake"
  43. cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake"
  44. emfake "k8s.io/metrics/pkg/client/external_metrics/fake"
  45. "github.com/stretchr/testify/assert"
  46. _ "k8s.io/kubernetes/pkg/apis/apps/install"
  47. _ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
  48. )
  49. var statusOk = []autoscalingv2.HorizontalPodAutoscalerCondition{
  50. {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  51. {Type: autoscalingv2.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"},
  52. {Type: autoscalingv2.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"},
  53. }
  54. // statusOkWithOverrides returns the "ok" status with the given conditions as overridden
  55. func statusOkWithOverrides(overrides ...autoscalingv2.HorizontalPodAutoscalerCondition) []autoscalingv1.HorizontalPodAutoscalerCondition {
  56. resv2 := make([]autoscalingv2.HorizontalPodAutoscalerCondition, len(statusOk))
  57. copy(resv2, statusOk)
  58. for _, override := range overrides {
  59. resv2 = setConditionInList(resv2, override.Type, override.Status, override.Reason, override.Message)
  60. }
  61. // copy to a v1 slice
  62. resv1 := make([]autoscalingv1.HorizontalPodAutoscalerCondition, len(resv2))
  63. for i, cond := range resv2 {
  64. resv1[i] = autoscalingv1.HorizontalPodAutoscalerCondition{
  65. Type: autoscalingv1.HorizontalPodAutoscalerConditionType(cond.Type),
  66. Status: cond.Status,
  67. Reason: cond.Reason,
  68. }
  69. }
  70. return resv1
  71. }
  72. func alwaysReady() bool { return true }
  73. type fakeResource struct {
  74. name string
  75. apiVersion string
  76. kind string
  77. }
  78. type testCase struct {
  79. sync.Mutex
  80. minReplicas int32
  81. maxReplicas int32
  82. initialReplicas int32
  83. // CPU target utilization as a percentage of the requested resources.
  84. CPUTarget int32
  85. CPUCurrent int32
  86. verifyCPUCurrent bool
  87. reportedLevels []uint64
  88. reportedCPURequests []resource.Quantity
  89. reportedPodReadiness []v1.ConditionStatus
  90. reportedPodStartTime []metav1.Time
  91. reportedPodPhase []v1.PodPhase
  92. reportedPodDeletionTimestamp []bool
  93. scaleUpdated bool
  94. statusUpdated bool
  95. eventCreated bool
  96. verifyEvents bool
  97. useMetricsAPI bool
  98. metricsTarget []autoscalingv2.MetricSpec
  99. expectedDesiredReplicas int32
  100. expectedConditions []autoscalingv1.HorizontalPodAutoscalerCondition
  101. // Channel with names of HPA objects which we have reconciled.
  102. processed chan string
  103. // Target resource information.
  104. resource *fakeResource
  105. // Last scale time
  106. lastScaleTime *metav1.Time
  107. // override the test clients
  108. testClient *fake.Clientset
  109. testMetricsClient *metricsfake.Clientset
  110. testCMClient *cmfake.FakeCustomMetricsClient
  111. testEMClient *emfake.FakeExternalMetricsClient
  112. testScaleClient *scalefake.FakeScaleClient
  113. recommendations []timestampedRecommendation
  114. }
  115. // Needs to be called under a lock.
  116. func (tc *testCase) computeCPUCurrent() {
  117. if len(tc.reportedLevels) != len(tc.reportedCPURequests) || len(tc.reportedLevels) == 0 {
  118. return
  119. }
  120. reported := 0
  121. for _, r := range tc.reportedLevels {
  122. reported += int(r)
  123. }
  124. requested := 0
  125. for _, req := range tc.reportedCPURequests {
  126. requested += int(req.MilliValue())
  127. }
  128. tc.CPUCurrent = int32(100 * reported / requested)
  129. }
  130. func init() {
  131. // set this high so we don't accidentally run into it when testing
  132. scaleUpLimitFactor = 8
  133. }
  134. func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfake.Clientset, *cmfake.FakeCustomMetricsClient, *emfake.FakeExternalMetricsClient, *scalefake.FakeScaleClient) {
  135. namespace := "test-namespace"
  136. hpaName := "test-hpa"
  137. podNamePrefix := "test-pod"
  138. labelSet := map[string]string{"name": podNamePrefix}
  139. selector := labels.SelectorFromSet(labelSet).String()
  140. tc.Lock()
  141. tc.scaleUpdated = false
  142. tc.statusUpdated = false
  143. tc.eventCreated = false
  144. tc.processed = make(chan string, 100)
  145. if tc.CPUCurrent == 0 {
  146. tc.computeCPUCurrent()
  147. }
  148. if tc.resource == nil {
  149. tc.resource = &fakeResource{
  150. name: "test-rc",
  151. apiVersion: "v1",
  152. kind: "ReplicationController",
  153. }
  154. }
  155. tc.Unlock()
  156. fakeClient := &fake.Clientset{}
  157. fakeClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  158. tc.Lock()
  159. defer tc.Unlock()
  160. obj := &autoscalingv2.HorizontalPodAutoscalerList{
  161. Items: []autoscalingv2.HorizontalPodAutoscaler{
  162. {
  163. ObjectMeta: metav1.ObjectMeta{
  164. Name: hpaName,
  165. Namespace: namespace,
  166. SelfLink: "experimental/v1/namespaces/" + namespace + "/horizontalpodautoscalers/" + hpaName,
  167. },
  168. Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
  169. ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
  170. Kind: tc.resource.kind,
  171. Name: tc.resource.name,
  172. APIVersion: tc.resource.apiVersion,
  173. },
  174. MinReplicas: &tc.minReplicas,
  175. MaxReplicas: tc.maxReplicas,
  176. },
  177. Status: autoscalingv2.HorizontalPodAutoscalerStatus{
  178. CurrentReplicas: tc.initialReplicas,
  179. DesiredReplicas: tc.initialReplicas,
  180. LastScaleTime: tc.lastScaleTime,
  181. },
  182. },
  183. },
  184. }
  185. if tc.CPUTarget > 0 {
  186. obj.Items[0].Spec.Metrics = []autoscalingv2.MetricSpec{
  187. {
  188. Type: autoscalingv2.ResourceMetricSourceType,
  189. Resource: &autoscalingv2.ResourceMetricSource{
  190. Name: v1.ResourceCPU,
  191. Target: autoscalingv2.MetricTarget{
  192. AverageUtilization: &tc.CPUTarget,
  193. },
  194. },
  195. },
  196. }
  197. }
  198. if len(tc.metricsTarget) > 0 {
  199. obj.Items[0].Spec.Metrics = append(obj.Items[0].Spec.Metrics, tc.metricsTarget...)
  200. }
  201. if len(obj.Items[0].Spec.Metrics) == 0 {
  202. // manually add in the defaulting logic
  203. obj.Items[0].Spec.Metrics = []autoscalingv2.MetricSpec{
  204. {
  205. Type: autoscalingv2.ResourceMetricSourceType,
  206. Resource: &autoscalingv2.ResourceMetricSource{
  207. Name: v1.ResourceCPU,
  208. },
  209. },
  210. }
  211. }
  212. // and... convert to autoscaling v1 to return the right type
  213. objv1, err := unsafeConvertToVersionVia(obj, autoscalingv1.SchemeGroupVersion)
  214. if err != nil {
  215. return true, nil, err
  216. }
  217. return true, objv1, nil
  218. })
  219. fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  220. tc.Lock()
  221. defer tc.Unlock()
  222. obj := &v1.PodList{}
  223. specifiedCPURequests := tc.reportedCPURequests != nil
  224. numPodsToCreate := int(tc.initialReplicas)
  225. if specifiedCPURequests {
  226. numPodsToCreate = len(tc.reportedCPURequests)
  227. }
  228. for i := 0; i < numPodsToCreate; i++ {
  229. podReadiness := v1.ConditionTrue
  230. if tc.reportedPodReadiness != nil {
  231. podReadiness = tc.reportedPodReadiness[i]
  232. }
  233. var podStartTime metav1.Time
  234. if tc.reportedPodStartTime != nil {
  235. podStartTime = tc.reportedPodStartTime[i]
  236. }
  237. podPhase := v1.PodRunning
  238. if tc.reportedPodPhase != nil {
  239. podPhase = tc.reportedPodPhase[i]
  240. }
  241. podDeletionTimestamp := false
  242. if tc.reportedPodDeletionTimestamp != nil {
  243. podDeletionTimestamp = tc.reportedPodDeletionTimestamp[i]
  244. }
  245. podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
  246. reportedCPURequest := resource.MustParse("1.0")
  247. if specifiedCPURequests {
  248. reportedCPURequest = tc.reportedCPURequests[i]
  249. }
  250. pod := v1.Pod{
  251. Status: v1.PodStatus{
  252. Phase: podPhase,
  253. Conditions: []v1.PodCondition{
  254. {
  255. Type: v1.PodReady,
  256. Status: podReadiness,
  257. LastTransitionTime: podStartTime,
  258. },
  259. },
  260. StartTime: &podStartTime,
  261. },
  262. ObjectMeta: metav1.ObjectMeta{
  263. Name: podName,
  264. Namespace: namespace,
  265. Labels: map[string]string{
  266. "name": podNamePrefix,
  267. },
  268. },
  269. Spec: v1.PodSpec{
  270. Containers: []v1.Container{
  271. {
  272. Resources: v1.ResourceRequirements{
  273. Requests: v1.ResourceList{
  274. v1.ResourceCPU: reportedCPURequest,
  275. },
  276. },
  277. },
  278. },
  279. },
  280. }
  281. if podDeletionTimestamp {
  282. pod.DeletionTimestamp = &metav1.Time{Time: time.Now()}
  283. }
  284. obj.Items = append(obj.Items, pod)
  285. }
  286. return true, obj, nil
  287. })
  288. fakeClient.AddReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  289. handled, obj, err := func() (handled bool, ret *autoscalingv1.HorizontalPodAutoscaler, err error) {
  290. tc.Lock()
  291. defer tc.Unlock()
  292. obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.HorizontalPodAutoscaler)
  293. assert.Equal(t, namespace, obj.Namespace, "the HPA namespace should be as expected")
  294. assert.Equal(t, hpaName, obj.Name, "the HPA name should be as expected")
  295. assert.Equal(t, tc.expectedDesiredReplicas, obj.Status.DesiredReplicas, "the desired replica count reported in the object status should be as expected")
  296. if tc.verifyCPUCurrent {
  297. if assert.NotNil(t, obj.Status.CurrentCPUUtilizationPercentage, "the reported CPU utilization percentage should be non-nil") {
  298. assert.Equal(t, tc.CPUCurrent, *obj.Status.CurrentCPUUtilizationPercentage, "the report CPU utilization percentage should be as expected")
  299. }
  300. }
  301. var actualConditions []autoscalingv1.HorizontalPodAutoscalerCondition
  302. if err := json.Unmarshal([]byte(obj.ObjectMeta.Annotations[autoscaling.HorizontalPodAutoscalerConditionsAnnotation]), &actualConditions); err != nil {
  303. return true, nil, err
  304. }
  305. // TODO: it's ok not to sort these becaues statusOk
  306. // contains all the conditions, so we'll never be appending.
  307. // Default to statusOk when missing any specific conditions
  308. if tc.expectedConditions == nil {
  309. tc.expectedConditions = statusOkWithOverrides()
  310. }
  311. // clear the message so that we can easily compare
  312. for i := range actualConditions {
  313. actualConditions[i].Message = ""
  314. actualConditions[i].LastTransitionTime = metav1.Time{}
  315. }
  316. assert.Equal(t, tc.expectedConditions, actualConditions, "the status conditions should have been as expected")
  317. tc.statusUpdated = true
  318. // Every time we reconcile HPA object we are updating status.
  319. return true, obj, nil
  320. }()
  321. if obj != nil {
  322. tc.processed <- obj.Name
  323. }
  324. return handled, obj, err
  325. })
  326. fakeScaleClient := &scalefake.FakeScaleClient{}
  327. fakeScaleClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  328. tc.Lock()
  329. defer tc.Unlock()
  330. obj := &autoscalingv1.Scale{
  331. ObjectMeta: metav1.ObjectMeta{
  332. Name: tc.resource.name,
  333. Namespace: namespace,
  334. },
  335. Spec: autoscalingv1.ScaleSpec{
  336. Replicas: tc.initialReplicas,
  337. },
  338. Status: autoscalingv1.ScaleStatus{
  339. Replicas: tc.initialReplicas,
  340. Selector: selector,
  341. },
  342. }
  343. return true, obj, nil
  344. })
  345. fakeScaleClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  346. tc.Lock()
  347. defer tc.Unlock()
  348. obj := &autoscalingv1.Scale{
  349. ObjectMeta: metav1.ObjectMeta{
  350. Name: tc.resource.name,
  351. Namespace: namespace,
  352. },
  353. Spec: autoscalingv1.ScaleSpec{
  354. Replicas: tc.initialReplicas,
  355. },
  356. Status: autoscalingv1.ScaleStatus{
  357. Replicas: tc.initialReplicas,
  358. Selector: selector,
  359. },
  360. }
  361. return true, obj, nil
  362. })
  363. fakeScaleClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  364. tc.Lock()
  365. defer tc.Unlock()
  366. obj := &autoscalingv1.Scale{
  367. ObjectMeta: metav1.ObjectMeta{
  368. Name: tc.resource.name,
  369. Namespace: namespace,
  370. },
  371. Spec: autoscalingv1.ScaleSpec{
  372. Replicas: tc.initialReplicas,
  373. },
  374. Status: autoscalingv1.ScaleStatus{
  375. Replicas: tc.initialReplicas,
  376. Selector: selector,
  377. },
  378. }
  379. return true, obj, nil
  380. })
  381. fakeScaleClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  382. tc.Lock()
  383. defer tc.Unlock()
  384. obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
  385. replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
  386. assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the RC should be as expected")
  387. tc.scaleUpdated = true
  388. return true, obj, nil
  389. })
  390. fakeScaleClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  391. tc.Lock()
  392. defer tc.Unlock()
  393. obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
  394. replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
  395. assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the deployment should be as expected")
  396. tc.scaleUpdated = true
  397. return true, obj, nil
  398. })
  399. fakeScaleClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  400. tc.Lock()
  401. defer tc.Unlock()
  402. obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
  403. replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
  404. assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the replicaset should be as expected")
  405. tc.scaleUpdated = true
  406. return true, obj, nil
  407. })
  408. fakeWatch := watch.NewFake()
  409. fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil))
  410. fakeMetricsClient := &metricsfake.Clientset{}
  411. fakeMetricsClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  412. tc.Lock()
  413. defer tc.Unlock()
  414. metrics := &metricsapi.PodMetricsList{}
  415. for i, cpu := range tc.reportedLevels {
  416. // NB: the list reactor actually does label selector filtering for us,
  417. // so we have to make sure our results match the label selector
  418. podMetric := metricsapi.PodMetrics{
  419. ObjectMeta: metav1.ObjectMeta{
  420. Name: fmt.Sprintf("%s-%d", podNamePrefix, i),
  421. Namespace: namespace,
  422. Labels: labelSet,
  423. },
  424. Timestamp: metav1.Time{Time: time.Now()},
  425. Window: metav1.Duration{Duration: time.Minute},
  426. Containers: []metricsapi.ContainerMetrics{
  427. {
  428. Name: "container",
  429. Usage: v1.ResourceList{
  430. v1.ResourceCPU: *resource.NewMilliQuantity(
  431. int64(cpu),
  432. resource.DecimalSI),
  433. v1.ResourceMemory: *resource.NewQuantity(
  434. int64(1024*1024),
  435. resource.BinarySI),
  436. },
  437. },
  438. },
  439. }
  440. metrics.Items = append(metrics.Items, podMetric)
  441. }
  442. return true, metrics, nil
  443. })
  444. fakeCMClient := &cmfake.FakeCustomMetricsClient{}
  445. fakeCMClient.AddReactor("get", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  446. tc.Lock()
  447. defer tc.Unlock()
  448. getForAction, wasGetFor := action.(cmfake.GetForAction)
  449. if !wasGetFor {
  450. return true, nil, fmt.Errorf("expected a get-for action, got %v instead", action)
  451. }
  452. if getForAction.GetName() == "*" {
  453. metrics := &cmapi.MetricValueList{}
  454. // multiple objects
  455. assert.Equal(t, "pods", getForAction.GetResource().Resource, "the type of object that we requested multiple metrics for should have been pods")
  456. assert.Equal(t, "qps", getForAction.GetMetricName(), "the metric name requested should have been qps, as specified in the metric spec")
  457. for i, level := range tc.reportedLevels {
  458. podMetric := cmapi.MetricValue{
  459. DescribedObject: v1.ObjectReference{
  460. Kind: "Pod",
  461. Name: fmt.Sprintf("%s-%d", podNamePrefix, i),
  462. Namespace: namespace,
  463. },
  464. Timestamp: metav1.Time{Time: time.Now()},
  465. Metric: cmapi.MetricIdentifier{
  466. Name: "qps",
  467. },
  468. Value: *resource.NewMilliQuantity(int64(level), resource.DecimalSI),
  469. }
  470. metrics.Items = append(metrics.Items, podMetric)
  471. }
  472. return true, metrics, nil
  473. }
  474. name := getForAction.GetName()
  475. mapper := testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme)
  476. metrics := &cmapi.MetricValueList{}
  477. var matchedTarget *autoscalingv2.MetricSpec
  478. for i, target := range tc.metricsTarget {
  479. if target.Type == autoscalingv2.ObjectMetricSourceType && name == target.Object.DescribedObject.Name {
  480. gk := schema.FromAPIVersionAndKind(target.Object.DescribedObject.APIVersion, target.Object.DescribedObject.Kind).GroupKind()
  481. mapping, err := mapper.RESTMapping(gk)
  482. if err != nil {
  483. t.Logf("unable to get mapping for %s: %v", gk.String(), err)
  484. continue
  485. }
  486. groupResource := mapping.Resource.GroupResource()
  487. if getForAction.GetResource().Resource == groupResource.String() {
  488. matchedTarget = &tc.metricsTarget[i]
  489. }
  490. }
  491. }
  492. assert.NotNil(t, matchedTarget, "this request should have matched one of the metric specs")
  493. assert.Equal(t, "qps", getForAction.GetMetricName(), "the metric name requested should have been qps, as specified in the metric spec")
  494. metrics.Items = []cmapi.MetricValue{
  495. {
  496. DescribedObject: v1.ObjectReference{
  497. Kind: matchedTarget.Object.DescribedObject.Kind,
  498. APIVersion: matchedTarget.Object.DescribedObject.APIVersion,
  499. Name: name,
  500. },
  501. Timestamp: metav1.Time{Time: time.Now()},
  502. Metric: cmapi.MetricIdentifier{
  503. Name: "qps",
  504. },
  505. Value: *resource.NewMilliQuantity(int64(tc.reportedLevels[0]), resource.DecimalSI),
  506. },
  507. }
  508. return true, metrics, nil
  509. })
  510. fakeEMClient := &emfake.FakeExternalMetricsClient{}
  511. fakeEMClient.AddReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  512. tc.Lock()
  513. defer tc.Unlock()
  514. listAction, wasList := action.(core.ListAction)
  515. if !wasList {
  516. return true, nil, fmt.Errorf("expected a list action, got %v instead", action)
  517. }
  518. metrics := &emapi.ExternalMetricValueList{}
  519. assert.Equal(t, "qps", listAction.GetResource().Resource, "the metric name requested should have been qps, as specified in the metric spec")
  520. for _, level := range tc.reportedLevels {
  521. metric := emapi.ExternalMetricValue{
  522. Timestamp: metav1.Time{Time: time.Now()},
  523. MetricName: "qps",
  524. Value: *resource.NewMilliQuantity(int64(level), resource.DecimalSI),
  525. }
  526. metrics.Items = append(metrics.Items, metric)
  527. }
  528. return true, metrics, nil
  529. })
  530. return fakeClient, fakeMetricsClient, fakeCMClient, fakeEMClient, fakeScaleClient
  531. }
  532. func (tc *testCase) verifyResults(t *testing.T) {
  533. tc.Lock()
  534. defer tc.Unlock()
  535. assert.Equal(t, tc.initialReplicas != tc.expectedDesiredReplicas, tc.scaleUpdated, "the scale should only be updated if we expected a change in replicas")
  536. assert.True(t, tc.statusUpdated, "the status should have been updated")
  537. if tc.verifyEvents {
  538. assert.Equal(t, tc.initialReplicas != tc.expectedDesiredReplicas, tc.eventCreated, "an event should have been created only if we expected a change in replicas")
  539. }
  540. }
  541. func (tc *testCase) setupController(t *testing.T) (*HorizontalController, informers.SharedInformerFactory) {
  542. testClient, testMetricsClient, testCMClient, testEMClient, testScaleClient := tc.prepareTestClient(t)
  543. if tc.testClient != nil {
  544. testClient = tc.testClient
  545. }
  546. if tc.testMetricsClient != nil {
  547. testMetricsClient = tc.testMetricsClient
  548. }
  549. if tc.testCMClient != nil {
  550. testCMClient = tc.testCMClient
  551. }
  552. if tc.testEMClient != nil {
  553. testEMClient = tc.testEMClient
  554. }
  555. if tc.testScaleClient != nil {
  556. testScaleClient = tc.testScaleClient
  557. }
  558. metricsClient := metrics.NewRESTMetricsClient(
  559. testMetricsClient.MetricsV1beta1(),
  560. testCMClient,
  561. testEMClient,
  562. )
  563. eventClient := &fake.Clientset{}
  564. eventClient.AddReactor("create", "events", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  565. tc.Lock()
  566. defer tc.Unlock()
  567. obj := action.(core.CreateAction).GetObject().(*v1.Event)
  568. if tc.verifyEvents {
  569. switch obj.Reason {
  570. case "SuccessfulRescale":
  571. assert.Equal(t, fmt.Sprintf("New size: %d; reason: cpu resource utilization (percentage of request) above target", tc.expectedDesiredReplicas), obj.Message)
  572. case "DesiredReplicasComputed":
  573. assert.Equal(t, fmt.Sprintf(
  574. "Computed the desired num of replicas: %d (avgCPUutil: %d, current replicas: %d)",
  575. tc.expectedDesiredReplicas,
  576. (int64(tc.reportedLevels[0])*100)/tc.reportedCPURequests[0].MilliValue(), tc.initialReplicas), obj.Message)
  577. default:
  578. assert.False(t, true, fmt.Sprintf("Unexpected event: %s / %s", obj.Reason, obj.Message))
  579. }
  580. }
  581. tc.eventCreated = true
  582. return true, obj, nil
  583. })
  584. informerFactory := informers.NewSharedInformerFactory(testClient, controller.NoResyncPeriodFunc())
  585. defaultDownscalestabilizationWindow := 5 * time.Minute
  586. hpaController := NewHorizontalController(
  587. eventClient.CoreV1(),
  588. testScaleClient,
  589. testClient.AutoscalingV1(),
  590. testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme),
  591. metricsClient,
  592. informerFactory.Autoscaling().V1().HorizontalPodAutoscalers(),
  593. informerFactory.Core().V1().Pods(),
  594. controller.NoResyncPeriodFunc(),
  595. defaultDownscalestabilizationWindow,
  596. defaultTestingTolerance,
  597. defaultTestingCpuInitializationPeriod,
  598. defaultTestingDelayOfInitialReadinessStatus,
  599. )
  600. hpaController.hpaListerSynced = alwaysReady
  601. if tc.recommendations != nil {
  602. hpaController.recommendations["test-namespace/test-hpa"] = tc.recommendations
  603. }
  604. return hpaController, informerFactory
  605. }
  606. func hotCpuCreationTime() metav1.Time {
  607. return metav1.Time{Time: time.Now()}
  608. }
  609. func coolCpuCreationTime() metav1.Time {
  610. return metav1.Time{Time: time.Now().Add(-3 * time.Minute)}
  611. }
  612. func (tc *testCase) runTestWithController(t *testing.T, hpaController *HorizontalController, informerFactory informers.SharedInformerFactory) {
  613. stop := make(chan struct{})
  614. defer close(stop)
  615. informerFactory.Start(stop)
  616. go hpaController.Run(stop)
  617. tc.Lock()
  618. shouldWait := tc.verifyEvents
  619. tc.Unlock()
  620. if shouldWait {
  621. // We need to wait for events to be broadcasted (sleep for longer than record.sleepDuration).
  622. timeoutTime := time.Now().Add(2 * time.Second)
  623. for now := time.Now(); timeoutTime.After(now); now = time.Now() {
  624. sleepUntil := timeoutTime.Sub(now)
  625. select {
  626. case <-tc.processed:
  627. // drain the chan of any sent events to keep it from filling before the timeout
  628. case <-time.After(sleepUntil):
  629. // timeout reached, ready to verifyResults
  630. }
  631. }
  632. } else {
  633. // Wait for HPA to be processed.
  634. <-tc.processed
  635. }
  636. tc.verifyResults(t)
  637. }
  638. func (tc *testCase) runTest(t *testing.T) {
  639. hpaController, informerFactory := tc.setupController(t)
  640. tc.runTestWithController(t, hpaController, informerFactory)
  641. }
  642. func TestScaleUp(t *testing.T) {
  643. tc := testCase{
  644. minReplicas: 2,
  645. maxReplicas: 6,
  646. initialReplicas: 3,
  647. expectedDesiredReplicas: 5,
  648. CPUTarget: 30,
  649. verifyCPUCurrent: true,
  650. reportedLevels: []uint64{300, 500, 700},
  651. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  652. useMetricsAPI: true,
  653. }
  654. tc.runTest(t)
  655. }
  656. func TestScaleUpUnreadyLessScale(t *testing.T) {
  657. tc := testCase{
  658. minReplicas: 2,
  659. maxReplicas: 6,
  660. initialReplicas: 3,
  661. expectedDesiredReplicas: 4,
  662. CPUTarget: 30,
  663. CPUCurrent: 60,
  664. verifyCPUCurrent: true,
  665. reportedLevels: []uint64{300, 500, 700},
  666. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  667. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue},
  668. useMetricsAPI: true,
  669. }
  670. tc.runTest(t)
  671. }
  672. func TestScaleUpHotCpuLessScale(t *testing.T) {
  673. tc := testCase{
  674. minReplicas: 2,
  675. maxReplicas: 6,
  676. initialReplicas: 3,
  677. expectedDesiredReplicas: 4,
  678. CPUTarget: 30,
  679. CPUCurrent: 60,
  680. verifyCPUCurrent: true,
  681. reportedLevels: []uint64{300, 500, 700},
  682. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  683. reportedPodStartTime: []metav1.Time{hotCpuCreationTime(), coolCpuCreationTime(), coolCpuCreationTime()},
  684. useMetricsAPI: true,
  685. }
  686. tc.runTest(t)
  687. }
  688. func TestScaleUpUnreadyNoScale(t *testing.T) {
  689. tc := testCase{
  690. minReplicas: 2,
  691. maxReplicas: 6,
  692. initialReplicas: 3,
  693. expectedDesiredReplicas: 3,
  694. CPUTarget: 30,
  695. CPUCurrent: 40,
  696. verifyCPUCurrent: true,
  697. reportedLevels: []uint64{400, 500, 700},
  698. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  699. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  700. useMetricsAPI: true,
  701. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  702. Type: autoscalingv2.AbleToScale,
  703. Status: v1.ConditionTrue,
  704. Reason: "ReadyForNewScale",
  705. }),
  706. }
  707. tc.runTest(t)
  708. }
  709. func TestScaleUpHotCpuNoScale(t *testing.T) {
  710. tc := testCase{
  711. minReplicas: 2,
  712. maxReplicas: 6,
  713. initialReplicas: 3,
  714. expectedDesiredReplicas: 3,
  715. CPUTarget: 30,
  716. CPUCurrent: 40,
  717. verifyCPUCurrent: true,
  718. reportedLevels: []uint64{400, 500, 700},
  719. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  720. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  721. reportedPodStartTime: []metav1.Time{coolCpuCreationTime(), hotCpuCreationTime(), hotCpuCreationTime()},
  722. useMetricsAPI: true,
  723. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  724. Type: autoscalingv2.AbleToScale,
  725. Status: v1.ConditionTrue,
  726. Reason: "ReadyForNewScale",
  727. }),
  728. }
  729. tc.runTest(t)
  730. }
  731. func TestScaleUpIgnoresFailedPods(t *testing.T) {
  732. tc := testCase{
  733. minReplicas: 2,
  734. maxReplicas: 6,
  735. initialReplicas: 2,
  736. expectedDesiredReplicas: 4,
  737. CPUTarget: 30,
  738. CPUCurrent: 60,
  739. verifyCPUCurrent: true,
  740. reportedLevels: []uint64{500, 700},
  741. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  742. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  743. reportedPodPhase: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodFailed, v1.PodFailed},
  744. useMetricsAPI: true,
  745. }
  746. tc.runTest(t)
  747. }
  748. func TestScaleUpIgnoresDeletionPods(t *testing.T) {
  749. tc := testCase{
  750. minReplicas: 2,
  751. maxReplicas: 6,
  752. initialReplicas: 2,
  753. expectedDesiredReplicas: 4,
  754. CPUTarget: 30,
  755. CPUCurrent: 60,
  756. verifyCPUCurrent: true,
  757. reportedLevels: []uint64{500, 700},
  758. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  759. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  760. reportedPodPhase: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning},
  761. reportedPodDeletionTimestamp: []bool{false, false, true, true},
  762. useMetricsAPI: true,
  763. }
  764. tc.runTest(t)
  765. }
  766. func TestScaleUpDeployment(t *testing.T) {
  767. tc := testCase{
  768. minReplicas: 2,
  769. maxReplicas: 6,
  770. initialReplicas: 3,
  771. expectedDesiredReplicas: 5,
  772. CPUTarget: 30,
  773. verifyCPUCurrent: true,
  774. reportedLevels: []uint64{300, 500, 700},
  775. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  776. useMetricsAPI: true,
  777. resource: &fakeResource{
  778. name: "test-dep",
  779. apiVersion: "apps/v1",
  780. kind: "Deployment",
  781. },
  782. }
  783. tc.runTest(t)
  784. }
  785. func TestScaleUpReplicaSet(t *testing.T) {
  786. tc := testCase{
  787. minReplicas: 2,
  788. maxReplicas: 6,
  789. initialReplicas: 3,
  790. expectedDesiredReplicas: 5,
  791. CPUTarget: 30,
  792. verifyCPUCurrent: true,
  793. reportedLevels: []uint64{300, 500, 700},
  794. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  795. useMetricsAPI: true,
  796. resource: &fakeResource{
  797. name: "test-replicaset",
  798. apiVersion: "apps/v1",
  799. kind: "ReplicaSet",
  800. },
  801. }
  802. tc.runTest(t)
  803. }
  804. func TestScaleUpCM(t *testing.T) {
  805. averageValue := resource.MustParse("15.0")
  806. tc := testCase{
  807. minReplicas: 2,
  808. maxReplicas: 6,
  809. initialReplicas: 3,
  810. expectedDesiredReplicas: 4,
  811. CPUTarget: 0,
  812. metricsTarget: []autoscalingv2.MetricSpec{
  813. {
  814. Type: autoscalingv2.PodsMetricSourceType,
  815. Pods: &autoscalingv2.PodsMetricSource{
  816. Metric: autoscalingv2.MetricIdentifier{
  817. Name: "qps",
  818. },
  819. Target: autoscalingv2.MetricTarget{
  820. AverageValue: &averageValue,
  821. },
  822. },
  823. },
  824. },
  825. reportedLevels: []uint64{20000, 10000, 30000},
  826. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  827. }
  828. tc.runTest(t)
  829. }
  830. func TestScaleUpCMUnreadyAndHotCpuNoLessScale(t *testing.T) {
  831. averageValue := resource.MustParse("15.0")
  832. tc := testCase{
  833. minReplicas: 2,
  834. maxReplicas: 6,
  835. initialReplicas: 3,
  836. expectedDesiredReplicas: 6,
  837. CPUTarget: 0,
  838. metricsTarget: []autoscalingv2.MetricSpec{
  839. {
  840. Type: autoscalingv2.PodsMetricSourceType,
  841. Pods: &autoscalingv2.PodsMetricSource{
  842. Metric: autoscalingv2.MetricIdentifier{
  843. Name: "qps",
  844. },
  845. Target: autoscalingv2.MetricTarget{
  846. AverageValue: &averageValue,
  847. },
  848. },
  849. },
  850. },
  851. reportedLevels: []uint64{50000, 10000, 30000},
  852. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse},
  853. reportedPodStartTime: []metav1.Time{coolCpuCreationTime(), coolCpuCreationTime(), hotCpuCreationTime()},
  854. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  855. }
  856. tc.runTest(t)
  857. }
  858. func TestScaleUpCMUnreadyandCpuHot(t *testing.T) {
  859. averageValue := resource.MustParse("15.0")
  860. tc := testCase{
  861. minReplicas: 2,
  862. maxReplicas: 6,
  863. initialReplicas: 3,
  864. expectedDesiredReplicas: 6,
  865. CPUTarget: 0,
  866. metricsTarget: []autoscalingv2.MetricSpec{
  867. {
  868. Type: autoscalingv2.PodsMetricSourceType,
  869. Pods: &autoscalingv2.PodsMetricSource{
  870. Metric: autoscalingv2.MetricIdentifier{
  871. Name: "qps",
  872. },
  873. Target: autoscalingv2.MetricTarget{
  874. AverageValue: &averageValue,
  875. },
  876. },
  877. },
  878. },
  879. reportedLevels: []uint64{50000, 15000, 30000},
  880. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionFalse},
  881. reportedPodStartTime: []metav1.Time{hotCpuCreationTime(), coolCpuCreationTime(), hotCpuCreationTime()},
  882. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  883. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  884. Type: autoscalingv2.AbleToScale,
  885. Status: v1.ConditionTrue,
  886. Reason: "SucceededRescale",
  887. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  888. Type: autoscalingv2.ScalingLimited,
  889. Status: v1.ConditionTrue,
  890. Reason: "TooManyReplicas",
  891. }),
  892. }
  893. tc.runTest(t)
  894. }
  895. func TestScaleUpHotCpuNoScaleWouldScaleDown(t *testing.T) {
  896. averageValue := resource.MustParse("15.0")
  897. tc := testCase{
  898. minReplicas: 2,
  899. maxReplicas: 6,
  900. initialReplicas: 3,
  901. expectedDesiredReplicas: 6,
  902. CPUTarget: 0,
  903. metricsTarget: []autoscalingv2.MetricSpec{
  904. {
  905. Type: autoscalingv2.PodsMetricSourceType,
  906. Pods: &autoscalingv2.PodsMetricSource{
  907. Metric: autoscalingv2.MetricIdentifier{
  908. Name: "qps",
  909. },
  910. Target: autoscalingv2.MetricTarget{
  911. AverageValue: &averageValue,
  912. },
  913. },
  914. },
  915. },
  916. reportedLevels: []uint64{50000, 15000, 30000},
  917. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  918. reportedPodStartTime: []metav1.Time{hotCpuCreationTime(), coolCpuCreationTime(), hotCpuCreationTime()},
  919. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  920. Type: autoscalingv2.AbleToScale,
  921. Status: v1.ConditionTrue,
  922. Reason: "SucceededRescale",
  923. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  924. Type: autoscalingv2.ScalingLimited,
  925. Status: v1.ConditionTrue,
  926. Reason: "TooManyReplicas",
  927. }),
  928. }
  929. tc.runTest(t)
  930. }
  931. func TestScaleUpCMObject(t *testing.T) {
  932. targetValue := resource.MustParse("15.0")
  933. tc := testCase{
  934. minReplicas: 2,
  935. maxReplicas: 6,
  936. initialReplicas: 3,
  937. expectedDesiredReplicas: 4,
  938. CPUTarget: 0,
  939. metricsTarget: []autoscalingv2.MetricSpec{
  940. {
  941. Type: autoscalingv2.ObjectMetricSourceType,
  942. Object: &autoscalingv2.ObjectMetricSource{
  943. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  944. APIVersion: "apps/v1",
  945. Kind: "Deployment",
  946. Name: "some-deployment",
  947. },
  948. Metric: autoscalingv2.MetricIdentifier{
  949. Name: "qps",
  950. },
  951. Target: autoscalingv2.MetricTarget{
  952. Value: &targetValue,
  953. },
  954. },
  955. },
  956. },
  957. reportedLevels: []uint64{20000},
  958. }
  959. tc.runTest(t)
  960. }
  961. func TestScaleUpPerPodCMObject(t *testing.T) {
  962. targetAverageValue := resource.MustParse("10.0")
  963. tc := testCase{
  964. minReplicas: 2,
  965. maxReplicas: 6,
  966. initialReplicas: 3,
  967. expectedDesiredReplicas: 4,
  968. CPUTarget: 0,
  969. metricsTarget: []autoscalingv2.MetricSpec{
  970. {
  971. Type: autoscalingv2.ObjectMetricSourceType,
  972. Object: &autoscalingv2.ObjectMetricSource{
  973. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  974. APIVersion: "apps/v1",
  975. Kind: "Deployment",
  976. Name: "some-deployment",
  977. },
  978. Metric: autoscalingv2.MetricIdentifier{
  979. Name: "qps",
  980. },
  981. Target: autoscalingv2.MetricTarget{
  982. AverageValue: &targetAverageValue,
  983. },
  984. },
  985. },
  986. },
  987. reportedLevels: []uint64{40000},
  988. }
  989. tc.runTest(t)
  990. }
  991. func TestScaleUpCMExternal(t *testing.T) {
  992. tc := testCase{
  993. minReplicas: 2,
  994. maxReplicas: 6,
  995. initialReplicas: 3,
  996. expectedDesiredReplicas: 4,
  997. metricsTarget: []autoscalingv2.MetricSpec{
  998. {
  999. Type: autoscalingv2.ExternalMetricSourceType,
  1000. External: &autoscalingv2.ExternalMetricSource{
  1001. Metric: autoscalingv2.MetricIdentifier{
  1002. Name: "qps",
  1003. Selector: &metav1.LabelSelector{},
  1004. },
  1005. Target: autoscalingv2.MetricTarget{
  1006. Value: resource.NewMilliQuantity(6666, resource.DecimalSI),
  1007. },
  1008. },
  1009. },
  1010. },
  1011. reportedLevels: []uint64{8600},
  1012. }
  1013. tc.runTest(t)
  1014. }
  1015. func TestScaleUpPerPodCMExternal(t *testing.T) {
  1016. tc := testCase{
  1017. minReplicas: 2,
  1018. maxReplicas: 6,
  1019. initialReplicas: 3,
  1020. expectedDesiredReplicas: 4,
  1021. metricsTarget: []autoscalingv2.MetricSpec{
  1022. {
  1023. Type: autoscalingv2.ExternalMetricSourceType,
  1024. External: &autoscalingv2.ExternalMetricSource{
  1025. Metric: autoscalingv2.MetricIdentifier{
  1026. Name: "qps",
  1027. Selector: &metav1.LabelSelector{},
  1028. },
  1029. Target: autoscalingv2.MetricTarget{
  1030. AverageValue: resource.NewMilliQuantity(2222, resource.DecimalSI),
  1031. },
  1032. },
  1033. },
  1034. },
  1035. reportedLevels: []uint64{8600},
  1036. }
  1037. tc.runTest(t)
  1038. }
  1039. func TestScaleDown(t *testing.T) {
  1040. tc := testCase{
  1041. minReplicas: 2,
  1042. maxReplicas: 6,
  1043. initialReplicas: 5,
  1044. expectedDesiredReplicas: 3,
  1045. CPUTarget: 50,
  1046. verifyCPUCurrent: true,
  1047. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1048. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1049. useMetricsAPI: true,
  1050. recommendations: []timestampedRecommendation{},
  1051. }
  1052. tc.runTest(t)
  1053. }
  1054. func TestScaleDownStabilizeInitialSize(t *testing.T) {
  1055. tc := testCase{
  1056. minReplicas: 2,
  1057. maxReplicas: 6,
  1058. initialReplicas: 5,
  1059. expectedDesiredReplicas: 5,
  1060. CPUTarget: 50,
  1061. verifyCPUCurrent: true,
  1062. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1063. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1064. useMetricsAPI: true,
  1065. recommendations: nil,
  1066. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1067. Type: autoscalingv2.AbleToScale,
  1068. Status: v1.ConditionTrue,
  1069. Reason: "ReadyForNewScale",
  1070. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  1071. Type: autoscalingv2.AbleToScale,
  1072. Status: v1.ConditionTrue,
  1073. Reason: "ScaleDownStabilized",
  1074. }),
  1075. }
  1076. tc.runTest(t)
  1077. }
  1078. func TestScaleDownCM(t *testing.T) {
  1079. averageValue := resource.MustParse("20.0")
  1080. tc := testCase{
  1081. minReplicas: 2,
  1082. maxReplicas: 6,
  1083. initialReplicas: 5,
  1084. expectedDesiredReplicas: 3,
  1085. CPUTarget: 0,
  1086. metricsTarget: []autoscalingv2.MetricSpec{
  1087. {
  1088. Type: autoscalingv2.PodsMetricSourceType,
  1089. Pods: &autoscalingv2.PodsMetricSource{
  1090. Metric: autoscalingv2.MetricIdentifier{
  1091. Name: "qps",
  1092. },
  1093. Target: autoscalingv2.MetricTarget{
  1094. AverageValue: &averageValue,
  1095. },
  1096. },
  1097. },
  1098. },
  1099. reportedLevels: []uint64{12000, 12000, 12000, 12000, 12000},
  1100. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1101. recommendations: []timestampedRecommendation{},
  1102. }
  1103. tc.runTest(t)
  1104. }
  1105. func TestScaleDownCMObject(t *testing.T) {
  1106. targetValue := resource.MustParse("20.0")
  1107. tc := testCase{
  1108. minReplicas: 2,
  1109. maxReplicas: 6,
  1110. initialReplicas: 5,
  1111. expectedDesiredReplicas: 3,
  1112. CPUTarget: 0,
  1113. metricsTarget: []autoscalingv2.MetricSpec{
  1114. {
  1115. Type: autoscalingv2.ObjectMetricSourceType,
  1116. Object: &autoscalingv2.ObjectMetricSource{
  1117. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1118. APIVersion: "apps/v1",
  1119. Kind: "Deployment",
  1120. Name: "some-deployment",
  1121. },
  1122. Metric: autoscalingv2.MetricIdentifier{
  1123. Name: "qps",
  1124. },
  1125. Target: autoscalingv2.MetricTarget{
  1126. Value: &targetValue,
  1127. },
  1128. },
  1129. },
  1130. },
  1131. reportedLevels: []uint64{12000},
  1132. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1133. recommendations: []timestampedRecommendation{},
  1134. }
  1135. tc.runTest(t)
  1136. }
  1137. func TestScaleDownPerPodCMObject(t *testing.T) {
  1138. targetAverageValue := resource.MustParse("20.0")
  1139. tc := testCase{
  1140. minReplicas: 2,
  1141. maxReplicas: 6,
  1142. initialReplicas: 5,
  1143. expectedDesiredReplicas: 3,
  1144. CPUTarget: 0,
  1145. metricsTarget: []autoscalingv2.MetricSpec{
  1146. {
  1147. Type: autoscalingv2.ObjectMetricSourceType,
  1148. Object: &autoscalingv2.ObjectMetricSource{
  1149. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1150. APIVersion: "apps/v1",
  1151. Kind: "Deployment",
  1152. Name: "some-deployment",
  1153. },
  1154. Metric: autoscalingv2.MetricIdentifier{
  1155. Name: "qps",
  1156. },
  1157. Target: autoscalingv2.MetricTarget{
  1158. AverageValue: &targetAverageValue,
  1159. },
  1160. },
  1161. },
  1162. },
  1163. reportedLevels: []uint64{60000},
  1164. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1165. recommendations: []timestampedRecommendation{},
  1166. }
  1167. tc.runTest(t)
  1168. }
  1169. func TestScaleDownCMExternal(t *testing.T) {
  1170. tc := testCase{
  1171. minReplicas: 2,
  1172. maxReplicas: 6,
  1173. initialReplicas: 5,
  1174. expectedDesiredReplicas: 3,
  1175. metricsTarget: []autoscalingv2.MetricSpec{
  1176. {
  1177. Type: autoscalingv2.ExternalMetricSourceType,
  1178. External: &autoscalingv2.ExternalMetricSource{
  1179. Metric: autoscalingv2.MetricIdentifier{
  1180. Name: "qps",
  1181. Selector: &metav1.LabelSelector{},
  1182. },
  1183. Target: autoscalingv2.MetricTarget{
  1184. Value: resource.NewMilliQuantity(14400, resource.DecimalSI),
  1185. },
  1186. },
  1187. },
  1188. },
  1189. reportedLevels: []uint64{8600},
  1190. recommendations: []timestampedRecommendation{},
  1191. }
  1192. tc.runTest(t)
  1193. }
  1194. func TestScaleDownPerPodCMExternal(t *testing.T) {
  1195. tc := testCase{
  1196. minReplicas: 2,
  1197. maxReplicas: 6,
  1198. initialReplicas: 5,
  1199. expectedDesiredReplicas: 3,
  1200. metricsTarget: []autoscalingv2.MetricSpec{
  1201. {
  1202. Type: autoscalingv2.ExternalMetricSourceType,
  1203. External: &autoscalingv2.ExternalMetricSource{
  1204. Metric: autoscalingv2.MetricIdentifier{
  1205. Name: "qps",
  1206. Selector: &metav1.LabelSelector{},
  1207. },
  1208. Target: autoscalingv2.MetricTarget{
  1209. AverageValue: resource.NewMilliQuantity(3000, resource.DecimalSI),
  1210. },
  1211. },
  1212. },
  1213. },
  1214. reportedLevels: []uint64{8600},
  1215. recommendations: []timestampedRecommendation{},
  1216. }
  1217. tc.runTest(t)
  1218. }
  1219. func TestScaleDownIncludeUnreadyPods(t *testing.T) {
  1220. tc := testCase{
  1221. minReplicas: 2,
  1222. maxReplicas: 6,
  1223. initialReplicas: 5,
  1224. expectedDesiredReplicas: 2,
  1225. CPUTarget: 50,
  1226. CPUCurrent: 30,
  1227. verifyCPUCurrent: true,
  1228. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1229. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1230. useMetricsAPI: true,
  1231. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1232. recommendations: []timestampedRecommendation{},
  1233. }
  1234. tc.runTest(t)
  1235. }
  1236. func TestScaleDownIgnoreHotCpuPods(t *testing.T) {
  1237. tc := testCase{
  1238. minReplicas: 2,
  1239. maxReplicas: 6,
  1240. initialReplicas: 5,
  1241. expectedDesiredReplicas: 2,
  1242. CPUTarget: 50,
  1243. CPUCurrent: 30,
  1244. verifyCPUCurrent: true,
  1245. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1246. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1247. useMetricsAPI: true,
  1248. reportedPodStartTime: []metav1.Time{coolCpuCreationTime(), coolCpuCreationTime(), coolCpuCreationTime(), hotCpuCreationTime(), hotCpuCreationTime()},
  1249. recommendations: []timestampedRecommendation{},
  1250. }
  1251. tc.runTest(t)
  1252. }
  1253. func TestScaleDownIgnoresFailedPods(t *testing.T) {
  1254. tc := testCase{
  1255. minReplicas: 2,
  1256. maxReplicas: 6,
  1257. initialReplicas: 5,
  1258. expectedDesiredReplicas: 3,
  1259. CPUTarget: 50,
  1260. CPUCurrent: 28,
  1261. verifyCPUCurrent: true,
  1262. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1263. 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")},
  1264. useMetricsAPI: true,
  1265. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1266. reportedPodPhase: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodFailed, v1.PodFailed},
  1267. recommendations: []timestampedRecommendation{},
  1268. }
  1269. tc.runTest(t)
  1270. }
  1271. func TestScaleDownIgnoresDeletionPods(t *testing.T) {
  1272. tc := testCase{
  1273. minReplicas: 2,
  1274. maxReplicas: 6,
  1275. initialReplicas: 5,
  1276. expectedDesiredReplicas: 3,
  1277. CPUTarget: 50,
  1278. CPUCurrent: 28,
  1279. verifyCPUCurrent: true,
  1280. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1281. 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")},
  1282. useMetricsAPI: true,
  1283. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1284. reportedPodPhase: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning},
  1285. reportedPodDeletionTimestamp: []bool{false, false, false, false, false, true, true},
  1286. recommendations: []timestampedRecommendation{},
  1287. }
  1288. tc.runTest(t)
  1289. }
  1290. func TestTolerance(t *testing.T) {
  1291. tc := testCase{
  1292. minReplicas: 1,
  1293. maxReplicas: 5,
  1294. initialReplicas: 3,
  1295. expectedDesiredReplicas: 3,
  1296. CPUTarget: 100,
  1297. reportedLevels: []uint64{1010, 1030, 1020},
  1298. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1299. useMetricsAPI: true,
  1300. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1301. Type: autoscalingv2.AbleToScale,
  1302. Status: v1.ConditionTrue,
  1303. Reason: "ReadyForNewScale",
  1304. }),
  1305. }
  1306. tc.runTest(t)
  1307. }
  1308. func TestToleranceCM(t *testing.T) {
  1309. averageValue := resource.MustParse("20.0")
  1310. tc := testCase{
  1311. minReplicas: 1,
  1312. maxReplicas: 5,
  1313. initialReplicas: 3,
  1314. expectedDesiredReplicas: 3,
  1315. metricsTarget: []autoscalingv2.MetricSpec{
  1316. {
  1317. Type: autoscalingv2.PodsMetricSourceType,
  1318. Pods: &autoscalingv2.PodsMetricSource{
  1319. Metric: autoscalingv2.MetricIdentifier{
  1320. Name: "qps",
  1321. },
  1322. Target: autoscalingv2.MetricTarget{
  1323. AverageValue: &averageValue,
  1324. },
  1325. },
  1326. },
  1327. },
  1328. reportedLevels: []uint64{20000, 20001, 21000},
  1329. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1330. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1331. Type: autoscalingv2.AbleToScale,
  1332. Status: v1.ConditionTrue,
  1333. Reason: "ReadyForNewScale",
  1334. }),
  1335. }
  1336. tc.runTest(t)
  1337. }
  1338. func TestToleranceCMObject(t *testing.T) {
  1339. targetValue := resource.MustParse("20.0")
  1340. tc := testCase{
  1341. minReplicas: 1,
  1342. maxReplicas: 5,
  1343. initialReplicas: 3,
  1344. expectedDesiredReplicas: 3,
  1345. metricsTarget: []autoscalingv2.MetricSpec{
  1346. {
  1347. Type: autoscalingv2.ObjectMetricSourceType,
  1348. Object: &autoscalingv2.ObjectMetricSource{
  1349. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1350. APIVersion: "apps/v1",
  1351. Kind: "Deployment",
  1352. Name: "some-deployment",
  1353. },
  1354. Metric: autoscalingv2.MetricIdentifier{
  1355. Name: "qps",
  1356. },
  1357. Target: autoscalingv2.MetricTarget{
  1358. Value: &targetValue,
  1359. },
  1360. },
  1361. },
  1362. },
  1363. reportedLevels: []uint64{20050},
  1364. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1365. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1366. Type: autoscalingv2.AbleToScale,
  1367. Status: v1.ConditionTrue,
  1368. Reason: "ReadyForNewScale",
  1369. }),
  1370. }
  1371. tc.runTest(t)
  1372. }
  1373. func TestToleranceCMExternal(t *testing.T) {
  1374. tc := testCase{
  1375. minReplicas: 2,
  1376. maxReplicas: 6,
  1377. initialReplicas: 4,
  1378. expectedDesiredReplicas: 4,
  1379. metricsTarget: []autoscalingv2.MetricSpec{
  1380. {
  1381. Type: autoscalingv2.ExternalMetricSourceType,
  1382. External: &autoscalingv2.ExternalMetricSource{
  1383. Metric: autoscalingv2.MetricIdentifier{
  1384. Name: "qps",
  1385. Selector: &metav1.LabelSelector{},
  1386. },
  1387. Target: autoscalingv2.MetricTarget{
  1388. Value: resource.NewMilliQuantity(8666, resource.DecimalSI),
  1389. },
  1390. },
  1391. },
  1392. },
  1393. reportedLevels: []uint64{8600},
  1394. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1395. Type: autoscalingv2.AbleToScale,
  1396. Status: v1.ConditionTrue,
  1397. Reason: "ReadyForNewScale",
  1398. }),
  1399. }
  1400. tc.runTest(t)
  1401. }
  1402. func TestTolerancePerPodCMObject(t *testing.T) {
  1403. tc := testCase{
  1404. minReplicas: 2,
  1405. maxReplicas: 6,
  1406. initialReplicas: 4,
  1407. expectedDesiredReplicas: 4,
  1408. metricsTarget: []autoscalingv2.MetricSpec{
  1409. {
  1410. Type: autoscalingv2.ObjectMetricSourceType,
  1411. Object: &autoscalingv2.ObjectMetricSource{
  1412. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1413. APIVersion: "apps/v1",
  1414. Kind: "Deployment",
  1415. Name: "some-deployment",
  1416. },
  1417. Metric: autoscalingv2.MetricIdentifier{
  1418. Name: "qps",
  1419. Selector: &metav1.LabelSelector{},
  1420. },
  1421. Target: autoscalingv2.MetricTarget{
  1422. AverageValue: resource.NewMilliQuantity(2200, resource.DecimalSI),
  1423. },
  1424. },
  1425. },
  1426. },
  1427. reportedLevels: []uint64{8600},
  1428. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1429. Type: autoscalingv2.AbleToScale,
  1430. Status: v1.ConditionTrue,
  1431. Reason: "ReadyForNewScale",
  1432. }),
  1433. }
  1434. tc.runTest(t)
  1435. }
  1436. func TestTolerancePerPodCMExternal(t *testing.T) {
  1437. tc := testCase{
  1438. minReplicas: 2,
  1439. maxReplicas: 6,
  1440. initialReplicas: 4,
  1441. expectedDesiredReplicas: 4,
  1442. metricsTarget: []autoscalingv2.MetricSpec{
  1443. {
  1444. Type: autoscalingv2.ExternalMetricSourceType,
  1445. External: &autoscalingv2.ExternalMetricSource{
  1446. Metric: autoscalingv2.MetricIdentifier{
  1447. Name: "qps",
  1448. Selector: &metav1.LabelSelector{},
  1449. },
  1450. Target: autoscalingv2.MetricTarget{
  1451. AverageValue: resource.NewMilliQuantity(2200, resource.DecimalSI),
  1452. },
  1453. },
  1454. },
  1455. },
  1456. reportedLevels: []uint64{8600},
  1457. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1458. Type: autoscalingv2.AbleToScale,
  1459. Status: v1.ConditionTrue,
  1460. Reason: "ReadyForNewScale",
  1461. }),
  1462. }
  1463. tc.runTest(t)
  1464. }
  1465. func TestMinReplicas(t *testing.T) {
  1466. tc := testCase{
  1467. minReplicas: 2,
  1468. maxReplicas: 5,
  1469. initialReplicas: 3,
  1470. expectedDesiredReplicas: 2,
  1471. CPUTarget: 90,
  1472. reportedLevels: []uint64{10, 95, 10},
  1473. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1474. useMetricsAPI: true,
  1475. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1476. Type: autoscalingv2.ScalingLimited,
  1477. Status: v1.ConditionTrue,
  1478. Reason: "TooFewReplicas",
  1479. }),
  1480. recommendations: []timestampedRecommendation{},
  1481. }
  1482. tc.runTest(t)
  1483. }
  1484. func TestMinReplicasDesiredZero(t *testing.T) {
  1485. tc := testCase{
  1486. minReplicas: 2,
  1487. maxReplicas: 5,
  1488. initialReplicas: 3,
  1489. expectedDesiredReplicas: 2,
  1490. CPUTarget: 90,
  1491. reportedLevels: []uint64{0, 0, 0},
  1492. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1493. useMetricsAPI: true,
  1494. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1495. Type: autoscalingv2.ScalingLimited,
  1496. Status: v1.ConditionTrue,
  1497. Reason: "TooFewReplicas",
  1498. }),
  1499. recommendations: []timestampedRecommendation{},
  1500. }
  1501. tc.runTest(t)
  1502. }
  1503. func TestZeroReplicas(t *testing.T) {
  1504. tc := testCase{
  1505. minReplicas: 3,
  1506. maxReplicas: 5,
  1507. initialReplicas: 0,
  1508. expectedDesiredReplicas: 0,
  1509. CPUTarget: 90,
  1510. reportedLevels: []uint64{},
  1511. reportedCPURequests: []resource.Quantity{},
  1512. useMetricsAPI: true,
  1513. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1514. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1515. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"},
  1516. },
  1517. }
  1518. tc.runTest(t)
  1519. }
  1520. func TestTooFewReplicas(t *testing.T) {
  1521. tc := testCase{
  1522. minReplicas: 3,
  1523. maxReplicas: 5,
  1524. initialReplicas: 2,
  1525. expectedDesiredReplicas: 3,
  1526. CPUTarget: 90,
  1527. reportedLevels: []uint64{},
  1528. reportedCPURequests: []resource.Quantity{},
  1529. useMetricsAPI: true,
  1530. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1531. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  1532. },
  1533. }
  1534. tc.runTest(t)
  1535. }
  1536. func TestTooManyReplicas(t *testing.T) {
  1537. tc := testCase{
  1538. minReplicas: 3,
  1539. maxReplicas: 5,
  1540. initialReplicas: 10,
  1541. expectedDesiredReplicas: 5,
  1542. CPUTarget: 90,
  1543. reportedLevels: []uint64{},
  1544. reportedCPURequests: []resource.Quantity{},
  1545. useMetricsAPI: true,
  1546. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1547. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  1548. },
  1549. }
  1550. tc.runTest(t)
  1551. }
  1552. func TestMaxReplicas(t *testing.T) {
  1553. tc := testCase{
  1554. minReplicas: 2,
  1555. maxReplicas: 5,
  1556. initialReplicas: 3,
  1557. expectedDesiredReplicas: 5,
  1558. CPUTarget: 90,
  1559. reportedLevels: []uint64{8000, 9500, 1000},
  1560. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1561. useMetricsAPI: true,
  1562. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1563. Type: autoscalingv2.ScalingLimited,
  1564. Status: v1.ConditionTrue,
  1565. Reason: "TooManyReplicas",
  1566. }),
  1567. }
  1568. tc.runTest(t)
  1569. }
  1570. func TestSuperfluousMetrics(t *testing.T) {
  1571. tc := testCase{
  1572. minReplicas: 2,
  1573. maxReplicas: 6,
  1574. initialReplicas: 4,
  1575. expectedDesiredReplicas: 6,
  1576. CPUTarget: 100,
  1577. reportedLevels: []uint64{4000, 9500, 3000, 7000, 3200, 2000},
  1578. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1579. useMetricsAPI: true,
  1580. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1581. Type: autoscalingv2.ScalingLimited,
  1582. Status: v1.ConditionTrue,
  1583. Reason: "TooManyReplicas",
  1584. }),
  1585. }
  1586. tc.runTest(t)
  1587. }
  1588. func TestMissingMetrics(t *testing.T) {
  1589. tc := testCase{
  1590. minReplicas: 2,
  1591. maxReplicas: 6,
  1592. initialReplicas: 4,
  1593. expectedDesiredReplicas: 3,
  1594. CPUTarget: 100,
  1595. reportedLevels: []uint64{400, 95},
  1596. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1597. useMetricsAPI: true,
  1598. recommendations: []timestampedRecommendation{},
  1599. }
  1600. tc.runTest(t)
  1601. }
  1602. func TestEmptyMetrics(t *testing.T) {
  1603. tc := testCase{
  1604. minReplicas: 2,
  1605. maxReplicas: 6,
  1606. initialReplicas: 4,
  1607. expectedDesiredReplicas: 4,
  1608. CPUTarget: 100,
  1609. reportedLevels: []uint64{},
  1610. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1611. useMetricsAPI: true,
  1612. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1613. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1614. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetResourceMetric"},
  1615. },
  1616. }
  1617. tc.runTest(t)
  1618. }
  1619. func TestEmptyCPURequest(t *testing.T) {
  1620. tc := testCase{
  1621. minReplicas: 1,
  1622. maxReplicas: 5,
  1623. initialReplicas: 1,
  1624. expectedDesiredReplicas: 1,
  1625. CPUTarget: 100,
  1626. reportedLevels: []uint64{200},
  1627. reportedCPURequests: []resource.Quantity{},
  1628. useMetricsAPI: true,
  1629. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1630. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1631. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetResourceMetric"},
  1632. },
  1633. }
  1634. tc.runTest(t)
  1635. }
  1636. func TestEventCreated(t *testing.T) {
  1637. tc := testCase{
  1638. minReplicas: 1,
  1639. maxReplicas: 5,
  1640. initialReplicas: 1,
  1641. expectedDesiredReplicas: 2,
  1642. CPUTarget: 50,
  1643. reportedLevels: []uint64{200},
  1644. reportedCPURequests: []resource.Quantity{resource.MustParse("0.2")},
  1645. verifyEvents: true,
  1646. useMetricsAPI: true,
  1647. }
  1648. tc.runTest(t)
  1649. }
  1650. func TestEventNotCreated(t *testing.T) {
  1651. tc := testCase{
  1652. minReplicas: 1,
  1653. maxReplicas: 5,
  1654. initialReplicas: 2,
  1655. expectedDesiredReplicas: 2,
  1656. CPUTarget: 50,
  1657. reportedLevels: []uint64{200, 200},
  1658. reportedCPURequests: []resource.Quantity{resource.MustParse("0.4"), resource.MustParse("0.4")},
  1659. verifyEvents: true,
  1660. useMetricsAPI: true,
  1661. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1662. Type: autoscalingv2.AbleToScale,
  1663. Status: v1.ConditionTrue,
  1664. Reason: "ReadyForNewScale",
  1665. }),
  1666. }
  1667. tc.runTest(t)
  1668. }
  1669. func TestMissingReports(t *testing.T) {
  1670. tc := testCase{
  1671. minReplicas: 1,
  1672. maxReplicas: 5,
  1673. initialReplicas: 4,
  1674. expectedDesiredReplicas: 2,
  1675. CPUTarget: 50,
  1676. reportedLevels: []uint64{200},
  1677. reportedCPURequests: []resource.Quantity{resource.MustParse("0.2")},
  1678. useMetricsAPI: true,
  1679. recommendations: []timestampedRecommendation{},
  1680. }
  1681. tc.runTest(t)
  1682. }
  1683. func TestUpscaleCap(t *testing.T) {
  1684. tc := testCase{
  1685. minReplicas: 1,
  1686. maxReplicas: 100,
  1687. initialReplicas: 3,
  1688. expectedDesiredReplicas: 24,
  1689. CPUTarget: 10,
  1690. reportedLevels: []uint64{100, 200, 300},
  1691. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  1692. useMetricsAPI: true,
  1693. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1694. Type: autoscalingv2.ScalingLimited,
  1695. Status: v1.ConditionTrue,
  1696. Reason: "ScaleUpLimit",
  1697. }),
  1698. }
  1699. tc.runTest(t)
  1700. }
  1701. func TestUpscaleCapGreaterThanMaxReplicas(t *testing.T) {
  1702. tc := testCase{
  1703. minReplicas: 1,
  1704. maxReplicas: 20,
  1705. initialReplicas: 3,
  1706. // expectedDesiredReplicas would be 24 without maxReplicas
  1707. expectedDesiredReplicas: 20,
  1708. CPUTarget: 10,
  1709. reportedLevels: []uint64{100, 200, 300},
  1710. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  1711. useMetricsAPI: true,
  1712. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1713. Type: autoscalingv2.ScalingLimited,
  1714. Status: v1.ConditionTrue,
  1715. Reason: "TooManyReplicas",
  1716. }),
  1717. }
  1718. tc.runTest(t)
  1719. }
  1720. func TestConditionInvalidSelectorMissing(t *testing.T) {
  1721. tc := testCase{
  1722. minReplicas: 1,
  1723. maxReplicas: 100,
  1724. initialReplicas: 3,
  1725. expectedDesiredReplicas: 3,
  1726. CPUTarget: 10,
  1727. reportedLevels: []uint64{100, 200, 300},
  1728. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  1729. useMetricsAPI: true,
  1730. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1731. {
  1732. Type: autoscalingv1.AbleToScale,
  1733. Status: v1.ConditionTrue,
  1734. Reason: "SucceededGetScale",
  1735. },
  1736. {
  1737. Type: autoscalingv1.ScalingActive,
  1738. Status: v1.ConditionFalse,
  1739. Reason: "InvalidSelector",
  1740. },
  1741. },
  1742. }
  1743. _, _, _, _, testScaleClient := tc.prepareTestClient(t)
  1744. tc.testScaleClient = testScaleClient
  1745. testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  1746. obj := &autoscalingv1.Scale{
  1747. ObjectMeta: metav1.ObjectMeta{
  1748. Name: tc.resource.name,
  1749. },
  1750. Spec: autoscalingv1.ScaleSpec{
  1751. Replicas: tc.initialReplicas,
  1752. },
  1753. Status: autoscalingv1.ScaleStatus{
  1754. Replicas: tc.initialReplicas,
  1755. },
  1756. }
  1757. return true, obj, nil
  1758. })
  1759. tc.runTest(t)
  1760. }
  1761. func TestConditionInvalidSelectorUnparsable(t *testing.T) {
  1762. tc := testCase{
  1763. minReplicas: 1,
  1764. maxReplicas: 100,
  1765. initialReplicas: 3,
  1766. expectedDesiredReplicas: 3,
  1767. CPUTarget: 10,
  1768. reportedLevels: []uint64{100, 200, 300},
  1769. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  1770. useMetricsAPI: true,
  1771. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1772. {
  1773. Type: autoscalingv1.AbleToScale,
  1774. Status: v1.ConditionTrue,
  1775. Reason: "SucceededGetScale",
  1776. },
  1777. {
  1778. Type: autoscalingv1.ScalingActive,
  1779. Status: v1.ConditionFalse,
  1780. Reason: "InvalidSelector",
  1781. },
  1782. },
  1783. }
  1784. _, _, _, _, testScaleClient := tc.prepareTestClient(t)
  1785. tc.testScaleClient = testScaleClient
  1786. testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  1787. obj := &autoscalingv1.Scale{
  1788. ObjectMeta: metav1.ObjectMeta{
  1789. Name: tc.resource.name,
  1790. },
  1791. Spec: autoscalingv1.ScaleSpec{
  1792. Replicas: tc.initialReplicas,
  1793. },
  1794. Status: autoscalingv1.ScaleStatus{
  1795. Replicas: tc.initialReplicas,
  1796. Selector: "cheddar cheese",
  1797. },
  1798. }
  1799. return true, obj, nil
  1800. })
  1801. tc.runTest(t)
  1802. }
  1803. func TestConditionFailedGetMetrics(t *testing.T) {
  1804. targetValue := resource.MustParse("15.0")
  1805. averageValue := resource.MustParse("15.0")
  1806. metricsTargets := map[string][]autoscalingv2.MetricSpec{
  1807. "FailedGetResourceMetric": nil,
  1808. "FailedGetPodsMetric": {
  1809. {
  1810. Type: autoscalingv2.PodsMetricSourceType,
  1811. Pods: &autoscalingv2.PodsMetricSource{
  1812. Metric: autoscalingv2.MetricIdentifier{
  1813. Name: "qps",
  1814. },
  1815. Target: autoscalingv2.MetricTarget{
  1816. AverageValue: &averageValue,
  1817. },
  1818. },
  1819. },
  1820. },
  1821. "FailedGetObjectMetric": {
  1822. {
  1823. Type: autoscalingv2.ObjectMetricSourceType,
  1824. Object: &autoscalingv2.ObjectMetricSource{
  1825. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1826. APIVersion: "apps/v1",
  1827. Kind: "Deployment",
  1828. Name: "some-deployment",
  1829. },
  1830. Metric: autoscalingv2.MetricIdentifier{
  1831. Name: "qps",
  1832. },
  1833. Target: autoscalingv2.MetricTarget{
  1834. Value: &targetValue,
  1835. },
  1836. },
  1837. },
  1838. },
  1839. "FailedGetExternalMetric": {
  1840. {
  1841. Type: autoscalingv2.ExternalMetricSourceType,
  1842. External: &autoscalingv2.ExternalMetricSource{
  1843. Metric: autoscalingv2.MetricIdentifier{
  1844. Name: "qps",
  1845. Selector: &metav1.LabelSelector{},
  1846. },
  1847. Target: autoscalingv2.MetricTarget{
  1848. Value: resource.NewMilliQuantity(300, resource.DecimalSI),
  1849. },
  1850. },
  1851. },
  1852. },
  1853. }
  1854. for reason, specs := range metricsTargets {
  1855. tc := testCase{
  1856. minReplicas: 1,
  1857. maxReplicas: 100,
  1858. initialReplicas: 3,
  1859. expectedDesiredReplicas: 3,
  1860. CPUTarget: 10,
  1861. reportedLevels: []uint64{100, 200, 300},
  1862. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  1863. useMetricsAPI: true,
  1864. }
  1865. _, testMetricsClient, testCMClient, testEMClient, _ := tc.prepareTestClient(t)
  1866. tc.testMetricsClient = testMetricsClient
  1867. tc.testCMClient = testCMClient
  1868. tc.testEMClient = testEMClient
  1869. testMetricsClient.PrependReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  1870. return true, &metricsapi.PodMetricsList{}, fmt.Errorf("something went wrong")
  1871. })
  1872. testCMClient.PrependReactor("get", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  1873. return true, &cmapi.MetricValueList{}, fmt.Errorf("something went wrong")
  1874. })
  1875. testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  1876. return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  1877. })
  1878. tc.expectedConditions = []autoscalingv1.HorizontalPodAutoscalerCondition{
  1879. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1880. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: reason},
  1881. }
  1882. if specs != nil {
  1883. tc.CPUTarget = 0
  1884. } else {
  1885. tc.CPUTarget = 10
  1886. }
  1887. tc.metricsTarget = specs
  1888. tc.runTest(t)
  1889. }
  1890. }
  1891. func TestConditionInvalidSourceType(t *testing.T) {
  1892. tc := testCase{
  1893. minReplicas: 2,
  1894. maxReplicas: 6,
  1895. initialReplicas: 3,
  1896. expectedDesiredReplicas: 3,
  1897. CPUTarget: 0,
  1898. metricsTarget: []autoscalingv2.MetricSpec{
  1899. {
  1900. Type: "CheddarCheese",
  1901. },
  1902. },
  1903. reportedLevels: []uint64{20000},
  1904. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1905. {
  1906. Type: autoscalingv1.AbleToScale,
  1907. Status: v1.ConditionTrue,
  1908. Reason: "SucceededGetScale",
  1909. },
  1910. {
  1911. Type: autoscalingv1.ScalingActive,
  1912. Status: v1.ConditionFalse,
  1913. Reason: "InvalidMetricSourceType",
  1914. },
  1915. },
  1916. }
  1917. tc.runTest(t)
  1918. }
  1919. func TestConditionFailedGetScale(t *testing.T) {
  1920. tc := testCase{
  1921. minReplicas: 1,
  1922. maxReplicas: 100,
  1923. initialReplicas: 3,
  1924. expectedDesiredReplicas: 3,
  1925. CPUTarget: 10,
  1926. reportedLevels: []uint64{100, 200, 300},
  1927. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  1928. useMetricsAPI: true,
  1929. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1930. {
  1931. Type: autoscalingv1.AbleToScale,
  1932. Status: v1.ConditionFalse,
  1933. Reason: "FailedGetScale",
  1934. },
  1935. },
  1936. }
  1937. _, _, _, _, testScaleClient := tc.prepareTestClient(t)
  1938. tc.testScaleClient = testScaleClient
  1939. testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  1940. return true, &autoscalingv1.Scale{}, fmt.Errorf("something went wrong")
  1941. })
  1942. tc.runTest(t)
  1943. }
  1944. func TestConditionFailedUpdateScale(t *testing.T) {
  1945. tc := testCase{
  1946. minReplicas: 1,
  1947. maxReplicas: 5,
  1948. initialReplicas: 3,
  1949. expectedDesiredReplicas: 3,
  1950. CPUTarget: 100,
  1951. reportedLevels: []uint64{150, 150, 150},
  1952. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  1953. useMetricsAPI: true,
  1954. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1955. Type: autoscalingv2.AbleToScale,
  1956. Status: v1.ConditionFalse,
  1957. Reason: "FailedUpdateScale",
  1958. }),
  1959. }
  1960. _, _, _, _, testScaleClient := tc.prepareTestClient(t)
  1961. tc.testScaleClient = testScaleClient
  1962. testScaleClient.PrependReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  1963. return true, &autoscalingv1.Scale{}, fmt.Errorf("something went wrong")
  1964. })
  1965. tc.runTest(t)
  1966. }
  1967. func NoTestBackoffUpscale(t *testing.T) {
  1968. time := metav1.Time{Time: time.Now()}
  1969. tc := testCase{
  1970. minReplicas: 1,
  1971. maxReplicas: 5,
  1972. initialReplicas: 3,
  1973. expectedDesiredReplicas: 3,
  1974. CPUTarget: 100,
  1975. reportedLevels: []uint64{150, 150, 150},
  1976. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  1977. useMetricsAPI: true,
  1978. lastScaleTime: &time,
  1979. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1980. Type: autoscalingv2.AbleToScale,
  1981. Status: v1.ConditionTrue,
  1982. Reason: "ReadyForNewScale",
  1983. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  1984. Type: autoscalingv2.AbleToScale,
  1985. Status: v1.ConditionTrue,
  1986. Reason: "SucceededRescale",
  1987. }),
  1988. }
  1989. tc.runTest(t)
  1990. }
  1991. func TestNoBackoffUpscaleCM(t *testing.T) {
  1992. averageValue := resource.MustParse("15.0")
  1993. time := metav1.Time{Time: time.Now()}
  1994. tc := testCase{
  1995. minReplicas: 1,
  1996. maxReplicas: 5,
  1997. initialReplicas: 3,
  1998. expectedDesiredReplicas: 4,
  1999. CPUTarget: 0,
  2000. metricsTarget: []autoscalingv2.MetricSpec{
  2001. {
  2002. Type: autoscalingv2.PodsMetricSourceType,
  2003. Pods: &autoscalingv2.PodsMetricSource{
  2004. Metric: autoscalingv2.MetricIdentifier{
  2005. Name: "qps",
  2006. },
  2007. Target: autoscalingv2.MetricTarget{
  2008. AverageValue: &averageValue,
  2009. },
  2010. },
  2011. },
  2012. },
  2013. reportedLevels: []uint64{20000, 10000, 30000},
  2014. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2015. //useMetricsAPI: true,
  2016. lastScaleTime: &time,
  2017. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2018. Type: autoscalingv2.AbleToScale,
  2019. Status: v1.ConditionTrue,
  2020. Reason: "ReadyForNewScale",
  2021. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2022. Type: autoscalingv2.AbleToScale,
  2023. Status: v1.ConditionTrue,
  2024. Reason: "SucceededRescale",
  2025. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2026. Type: autoscalingv2.ScalingLimited,
  2027. Status: v1.ConditionFalse,
  2028. Reason: "DesiredWithinRange",
  2029. }),
  2030. }
  2031. tc.runTest(t)
  2032. }
  2033. func TestNoBackoffUpscaleCMNoBackoffCpu(t *testing.T) {
  2034. averageValue := resource.MustParse("15.0")
  2035. time := metav1.Time{Time: time.Now()}
  2036. tc := testCase{
  2037. minReplicas: 1,
  2038. maxReplicas: 5,
  2039. initialReplicas: 3,
  2040. expectedDesiredReplicas: 5,
  2041. CPUTarget: 10,
  2042. metricsTarget: []autoscalingv2.MetricSpec{
  2043. {
  2044. Type: autoscalingv2.PodsMetricSourceType,
  2045. Pods: &autoscalingv2.PodsMetricSource{
  2046. Metric: autoscalingv2.MetricIdentifier{
  2047. Name: "qps",
  2048. },
  2049. Target: autoscalingv2.MetricTarget{
  2050. AverageValue: &averageValue,
  2051. },
  2052. },
  2053. },
  2054. },
  2055. reportedLevels: []uint64{20000, 10000, 30000},
  2056. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2057. useMetricsAPI: true,
  2058. lastScaleTime: &time,
  2059. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2060. Type: autoscalingv2.AbleToScale,
  2061. Status: v1.ConditionTrue,
  2062. Reason: "ReadyForNewScale",
  2063. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2064. Type: autoscalingv2.AbleToScale,
  2065. Status: v1.ConditionTrue,
  2066. Reason: "SucceededRescale",
  2067. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2068. Type: autoscalingv2.ScalingLimited,
  2069. Status: v1.ConditionTrue,
  2070. Reason: "TooManyReplicas",
  2071. }),
  2072. }
  2073. tc.runTest(t)
  2074. }
  2075. func TestStabilizeDownscale(t *testing.T) {
  2076. tc := testCase{
  2077. minReplicas: 1,
  2078. maxReplicas: 5,
  2079. initialReplicas: 4,
  2080. expectedDesiredReplicas: 4,
  2081. CPUTarget: 100,
  2082. reportedLevels: []uint64{50, 50, 50},
  2083. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2084. useMetricsAPI: true,
  2085. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2086. Type: autoscalingv2.AbleToScale,
  2087. Status: v1.ConditionTrue,
  2088. Reason: "ReadyForNewScale",
  2089. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2090. Type: autoscalingv2.AbleToScale,
  2091. Status: v1.ConditionTrue,
  2092. Reason: "ScaleDownStabilized",
  2093. }),
  2094. recommendations: []timestampedRecommendation{
  2095. {10, time.Now().Add(-10 * time.Minute)},
  2096. {4, time.Now().Add(-1 * time.Minute)},
  2097. },
  2098. }
  2099. tc.runTest(t)
  2100. }
  2101. // TestComputedToleranceAlgImplementation is a regression test which
  2102. // back-calculates a minimal percentage for downscaling based on a small percentage
  2103. // increase in pod utilization which is calibrated against the tolerance value.
  2104. func TestComputedToleranceAlgImplementation(t *testing.T) {
  2105. startPods := int32(10)
  2106. // 150 mCPU per pod.
  2107. totalUsedCPUOfAllPods := uint64(startPods * 150)
  2108. // Each pod starts out asking for 2X what is really needed.
  2109. // This means we will have a 50% ratio of used/requested
  2110. totalRequestedCPUOfAllPods := int32(2 * totalUsedCPUOfAllPods)
  2111. requestedToUsed := float64(totalRequestedCPUOfAllPods / int32(totalUsedCPUOfAllPods))
  2112. // Spread the amount we ask over 10 pods. We can add some jitter later in reportedLevels.
  2113. perPodRequested := totalRequestedCPUOfAllPods / startPods
  2114. // Force a minimal scaling event by satisfying (tolerance < 1 - resourcesUsedRatio).
  2115. target := math.Abs(1/(requestedToUsed*(1-defaultTestingTolerance))) + .01
  2116. finalCPUPercentTarget := int32(target * 100)
  2117. resourcesUsedRatio := float64(totalUsedCPUOfAllPods) / float64(float64(totalRequestedCPUOfAllPods)*target)
  2118. // i.e. .60 * 20 -> scaled down expectation.
  2119. finalPods := int32(math.Ceil(resourcesUsedRatio * float64(startPods)))
  2120. // To breach tolerance we will create a utilization ratio difference of tolerance to usageRatioToleranceValue)
  2121. tc1 := testCase{
  2122. minReplicas: 0,
  2123. maxReplicas: 1000,
  2124. initialReplicas: startPods,
  2125. expectedDesiredReplicas: finalPods,
  2126. CPUTarget: finalCPUPercentTarget,
  2127. reportedLevels: []uint64{
  2128. totalUsedCPUOfAllPods / 10,
  2129. totalUsedCPUOfAllPods / 10,
  2130. totalUsedCPUOfAllPods / 10,
  2131. totalUsedCPUOfAllPods / 10,
  2132. totalUsedCPUOfAllPods / 10,
  2133. totalUsedCPUOfAllPods / 10,
  2134. totalUsedCPUOfAllPods / 10,
  2135. totalUsedCPUOfAllPods / 10,
  2136. totalUsedCPUOfAllPods / 10,
  2137. totalUsedCPUOfAllPods / 10,
  2138. },
  2139. reportedCPURequests: []resource.Quantity{
  2140. resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"),
  2141. resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"),
  2142. resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"),
  2143. resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"),
  2144. resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"),
  2145. resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"),
  2146. resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"),
  2147. resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"),
  2148. resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  2149. resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  2150. },
  2151. useMetricsAPI: true,
  2152. recommendations: []timestampedRecommendation{},
  2153. }
  2154. tc1.runTest(t)
  2155. target = math.Abs(1/(requestedToUsed*(1-defaultTestingTolerance))) + .004
  2156. finalCPUPercentTarget = int32(target * 100)
  2157. tc2 := testCase{
  2158. minReplicas: 0,
  2159. maxReplicas: 1000,
  2160. initialReplicas: startPods,
  2161. expectedDesiredReplicas: startPods,
  2162. CPUTarget: finalCPUPercentTarget,
  2163. reportedLevels: []uint64{
  2164. totalUsedCPUOfAllPods / 10,
  2165. totalUsedCPUOfAllPods / 10,
  2166. totalUsedCPUOfAllPods / 10,
  2167. totalUsedCPUOfAllPods / 10,
  2168. totalUsedCPUOfAllPods / 10,
  2169. totalUsedCPUOfAllPods / 10,
  2170. totalUsedCPUOfAllPods / 10,
  2171. totalUsedCPUOfAllPods / 10,
  2172. totalUsedCPUOfAllPods / 10,
  2173. totalUsedCPUOfAllPods / 10,
  2174. },
  2175. reportedCPURequests: []resource.Quantity{
  2176. resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"),
  2177. resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"),
  2178. resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"),
  2179. resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"),
  2180. resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"),
  2181. resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"),
  2182. resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"),
  2183. resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"),
  2184. resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  2185. resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  2186. },
  2187. useMetricsAPI: true,
  2188. recommendations: []timestampedRecommendation{},
  2189. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2190. Type: autoscalingv2.AbleToScale,
  2191. Status: v1.ConditionTrue,
  2192. Reason: "ReadyForNewScale",
  2193. }),
  2194. }
  2195. tc2.runTest(t)
  2196. }
  2197. func TestScaleUpRCImmediately(t *testing.T) {
  2198. time := metav1.Time{Time: time.Now()}
  2199. tc := testCase{
  2200. minReplicas: 2,
  2201. maxReplicas: 6,
  2202. initialReplicas: 1,
  2203. expectedDesiredReplicas: 2,
  2204. verifyCPUCurrent: false,
  2205. reportedLevels: []uint64{0, 0, 0, 0},
  2206. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2207. useMetricsAPI: true,
  2208. lastScaleTime: &time,
  2209. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2210. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  2211. },
  2212. }
  2213. tc.runTest(t)
  2214. }
  2215. func TestScaleDownRCImmediately(t *testing.T) {
  2216. time := metav1.Time{Time: time.Now()}
  2217. tc := testCase{
  2218. minReplicas: 2,
  2219. maxReplicas: 5,
  2220. initialReplicas: 6,
  2221. expectedDesiredReplicas: 5,
  2222. CPUTarget: 50,
  2223. reportedLevels: []uint64{8000, 9500, 1000},
  2224. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2225. useMetricsAPI: true,
  2226. lastScaleTime: &time,
  2227. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2228. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  2229. },
  2230. }
  2231. tc.runTest(t)
  2232. }
  2233. func TestAvoidUncessaryUpdates(t *testing.T) {
  2234. now := metav1.Time{Time: time.Now().Add(-time.Hour)}
  2235. tc := testCase{
  2236. minReplicas: 2,
  2237. maxReplicas: 6,
  2238. initialReplicas: 2,
  2239. expectedDesiredReplicas: 2,
  2240. CPUTarget: 30,
  2241. CPUCurrent: 40,
  2242. verifyCPUCurrent: true,
  2243. reportedLevels: []uint64{400, 500, 700},
  2244. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2245. reportedPodStartTime: []metav1.Time{coolCpuCreationTime(), hotCpuCreationTime(), hotCpuCreationTime()},
  2246. useMetricsAPI: true,
  2247. lastScaleTime: &now,
  2248. recommendations: []timestampedRecommendation{},
  2249. }
  2250. testClient, _, _, _, _ := tc.prepareTestClient(t)
  2251. tc.testClient = testClient
  2252. testClient.PrependReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2253. tc.Lock()
  2254. defer tc.Unlock()
  2255. // fake out the verification logic and mark that we're done processing
  2256. go func() {
  2257. // wait a tick and then mark that we're finished (otherwise, we have no
  2258. // way to indicate that we're finished, because the function decides not to do anything)
  2259. time.Sleep(1 * time.Second)
  2260. tc.Lock()
  2261. tc.statusUpdated = true
  2262. tc.Unlock()
  2263. tc.processed <- "test-hpa"
  2264. }()
  2265. quantity := resource.MustParse("400m")
  2266. obj := &autoscalingv2.HorizontalPodAutoscalerList{
  2267. Items: []autoscalingv2.HorizontalPodAutoscaler{
  2268. {
  2269. ObjectMeta: metav1.ObjectMeta{
  2270. Name: "test-hpa",
  2271. Namespace: "test-namespace",
  2272. SelfLink: "experimental/v1/namespaces/test-namespace/horizontalpodautoscalers/test-hpa",
  2273. },
  2274. Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
  2275. ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
  2276. Kind: "ReplicationController",
  2277. Name: "test-rc",
  2278. APIVersion: "v1",
  2279. },
  2280. MinReplicas: &tc.minReplicas,
  2281. MaxReplicas: tc.maxReplicas,
  2282. },
  2283. Status: autoscalingv2.HorizontalPodAutoscalerStatus{
  2284. CurrentReplicas: tc.initialReplicas,
  2285. DesiredReplicas: tc.initialReplicas,
  2286. LastScaleTime: tc.lastScaleTime,
  2287. CurrentMetrics: []autoscalingv2.MetricStatus{
  2288. {
  2289. Type: autoscalingv2.ResourceMetricSourceType,
  2290. Resource: &autoscalingv2.ResourceMetricStatus{
  2291. Name: v1.ResourceCPU,
  2292. Current: autoscalingv2.MetricValueStatus{
  2293. AverageValue: &quantity,
  2294. AverageUtilization: &tc.CPUCurrent,
  2295. },
  2296. },
  2297. },
  2298. },
  2299. Conditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2300. {
  2301. Type: autoscalingv2.AbleToScale,
  2302. Status: v1.ConditionTrue,
  2303. LastTransitionTime: *tc.lastScaleTime,
  2304. Reason: "ReadyForNewScale",
  2305. Message: "recommended size matches current size",
  2306. },
  2307. {
  2308. Type: autoscalingv2.ScalingActive,
  2309. Status: v1.ConditionTrue,
  2310. LastTransitionTime: *tc.lastScaleTime,
  2311. Reason: "ValidMetricFound",
  2312. Message: "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)",
  2313. },
  2314. {
  2315. Type: autoscalingv2.ScalingLimited,
  2316. Status: v1.ConditionTrue,
  2317. LastTransitionTime: *tc.lastScaleTime,
  2318. Reason: "TooFewReplicas",
  2319. Message: "the desired replica count is more than the maximum replica count",
  2320. },
  2321. },
  2322. },
  2323. },
  2324. },
  2325. }
  2326. // and... convert to autoscaling v1 to return the right type
  2327. objv1, err := unsafeConvertToVersionVia(obj, autoscalingv1.SchemeGroupVersion)
  2328. if err != nil {
  2329. return true, nil, err
  2330. }
  2331. return true, objv1, nil
  2332. })
  2333. testClient.PrependReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2334. assert.Fail(t, "should not have attempted to update the HPA when nothing changed")
  2335. // mark that we've processed this HPA
  2336. tc.processed <- ""
  2337. return true, nil, fmt.Errorf("unexpected call")
  2338. })
  2339. controller, informerFactory := tc.setupController(t)
  2340. tc.runTestWithController(t, controller, informerFactory)
  2341. }
  2342. func TestConvertDesiredReplicasWithRules(t *testing.T) {
  2343. conversionTestCases := []struct {
  2344. currentReplicas int32
  2345. expectedDesiredReplicas int32
  2346. hpaMinReplicas int32
  2347. hpaMaxReplicas int32
  2348. expectedConvertedDesiredReplicas int32
  2349. expectedCondition string
  2350. annotation string
  2351. }{
  2352. {
  2353. currentReplicas: 5,
  2354. expectedDesiredReplicas: 7,
  2355. hpaMinReplicas: 3,
  2356. hpaMaxReplicas: 8,
  2357. expectedConvertedDesiredReplicas: 7,
  2358. expectedCondition: "DesiredWithinRange",
  2359. annotation: "prenormalized desired replicas within range",
  2360. },
  2361. {
  2362. currentReplicas: 3,
  2363. expectedDesiredReplicas: 1,
  2364. hpaMinReplicas: 2,
  2365. hpaMaxReplicas: 8,
  2366. expectedConvertedDesiredReplicas: 2,
  2367. expectedCondition: "TooFewReplicas",
  2368. annotation: "prenormalized desired replicas < minReplicas",
  2369. },
  2370. {
  2371. currentReplicas: 1,
  2372. expectedDesiredReplicas: 0,
  2373. hpaMinReplicas: 0,
  2374. hpaMaxReplicas: 10,
  2375. expectedConvertedDesiredReplicas: 1,
  2376. expectedCondition: "TooFewReplicas",
  2377. annotation: "1 is minLimit because hpaMinReplicas < 1",
  2378. },
  2379. {
  2380. currentReplicas: 20,
  2381. expectedDesiredReplicas: 1000,
  2382. hpaMinReplicas: 1,
  2383. hpaMaxReplicas: 10,
  2384. expectedConvertedDesiredReplicas: 10,
  2385. expectedCondition: "TooManyReplicas",
  2386. annotation: "maxReplicas is the limit because maxReplicas < scaleUpLimit",
  2387. },
  2388. {
  2389. currentReplicas: 3,
  2390. expectedDesiredReplicas: 1000,
  2391. hpaMinReplicas: 1,
  2392. hpaMaxReplicas: 2000,
  2393. expectedConvertedDesiredReplicas: calculateScaleUpLimit(3),
  2394. expectedCondition: "ScaleUpLimit",
  2395. annotation: "scaleUpLimit is the limit because scaleUpLimit < maxReplicas",
  2396. },
  2397. }
  2398. for _, ctc := range conversionTestCases {
  2399. actualConvertedDesiredReplicas, actualCondition, _ := convertDesiredReplicasWithRules(
  2400. ctc.currentReplicas, ctc.expectedDesiredReplicas, ctc.hpaMinReplicas, ctc.hpaMaxReplicas,
  2401. )
  2402. assert.Equal(t, ctc.expectedConvertedDesiredReplicas, actualConvertedDesiredReplicas, ctc.annotation)
  2403. assert.Equal(t, ctc.expectedCondition, actualCondition, ctc.annotation)
  2404. }
  2405. }
  2406. func TestNormalizeDesiredReplicas(t *testing.T) {
  2407. tests := []struct {
  2408. name string
  2409. key string
  2410. recommendations []timestampedRecommendation
  2411. prenormalizedDesiredReplicas int32
  2412. expectedStabilizedReplicas int32
  2413. expectedLogLength int
  2414. }{
  2415. {
  2416. "empty log",
  2417. "",
  2418. []timestampedRecommendation{},
  2419. 5,
  2420. 5,
  2421. 1,
  2422. },
  2423. {
  2424. "stabilize",
  2425. "",
  2426. []timestampedRecommendation{
  2427. {4, time.Now().Add(-2 * time.Minute)},
  2428. {5, time.Now().Add(-1 * time.Minute)},
  2429. },
  2430. 3,
  2431. 5,
  2432. 3,
  2433. },
  2434. {
  2435. "no stabilize",
  2436. "",
  2437. []timestampedRecommendation{
  2438. {1, time.Now().Add(-2 * time.Minute)},
  2439. {2, time.Now().Add(-1 * time.Minute)},
  2440. },
  2441. 3,
  2442. 3,
  2443. 3,
  2444. },
  2445. {
  2446. "no stabilize - old recommendations",
  2447. "",
  2448. []timestampedRecommendation{
  2449. {10, time.Now().Add(-10 * time.Minute)},
  2450. {9, time.Now().Add(-9 * time.Minute)},
  2451. },
  2452. 3,
  2453. 3,
  2454. 2,
  2455. },
  2456. {
  2457. "stabilize - old recommendations",
  2458. "",
  2459. []timestampedRecommendation{
  2460. {10, time.Now().Add(-10 * time.Minute)},
  2461. {4, time.Now().Add(-1 * time.Minute)},
  2462. {5, time.Now().Add(-2 * time.Minute)},
  2463. {9, time.Now().Add(-9 * time.Minute)},
  2464. },
  2465. 3,
  2466. 5,
  2467. 4,
  2468. },
  2469. }
  2470. for _, tc := range tests {
  2471. hc := HorizontalController{
  2472. downscaleStabilisationWindow: 5 * time.Minute,
  2473. recommendations: map[string][]timestampedRecommendation{
  2474. tc.key: tc.recommendations,
  2475. },
  2476. }
  2477. r := hc.stabilizeRecommendation(tc.key, tc.prenormalizedDesiredReplicas)
  2478. if r != tc.expectedStabilizedReplicas {
  2479. t.Errorf("[%s] got %d stabilized replicas, expected %d", tc.name, r, tc.expectedStabilizedReplicas)
  2480. }
  2481. if len(hc.recommendations[tc.key]) != tc.expectedLogLength {
  2482. t.Errorf("[%s] after stabilization recommendations log has %d entries, expected %d", tc.name, len(hc.recommendations[tc.key]), tc.expectedLogLength)
  2483. }
  2484. }
  2485. }
  2486. func TestScaleUpOneMetricEmpty(t *testing.T) {
  2487. tc := testCase{
  2488. minReplicas: 2,
  2489. maxReplicas: 6,
  2490. initialReplicas: 3,
  2491. expectedDesiredReplicas: 4,
  2492. CPUTarget: 30,
  2493. verifyCPUCurrent: true,
  2494. metricsTarget: []autoscalingv2.MetricSpec{
  2495. {
  2496. Type: autoscalingv2.ExternalMetricSourceType,
  2497. External: &autoscalingv2.ExternalMetricSource{
  2498. Metric: autoscalingv2.MetricIdentifier{
  2499. Name: "qps",
  2500. Selector: &metav1.LabelSelector{},
  2501. },
  2502. Target: autoscalingv2.MetricTarget{
  2503. Value: resource.NewMilliQuantity(100, resource.DecimalSI),
  2504. },
  2505. },
  2506. },
  2507. },
  2508. reportedLevels: []uint64{300, 400, 500},
  2509. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2510. }
  2511. _, _, _, testEMClient, _ := tc.prepareTestClient(t)
  2512. testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2513. return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  2514. })
  2515. tc.testEMClient = testEMClient
  2516. tc.runTest(t)
  2517. }
  2518. func TestNoScaleDownOneMetricInvalid(t *testing.T) {
  2519. tc := testCase{
  2520. minReplicas: 2,
  2521. maxReplicas: 6,
  2522. initialReplicas: 5,
  2523. expectedDesiredReplicas: 5,
  2524. CPUTarget: 50,
  2525. metricsTarget: []autoscalingv2.MetricSpec{
  2526. {
  2527. Type: "CheddarCheese",
  2528. },
  2529. },
  2530. reportedLevels: []uint64{100, 300, 500, 250, 250},
  2531. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2532. useMetricsAPI: true,
  2533. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2534. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  2535. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "InvalidMetricSourceType"},
  2536. },
  2537. }
  2538. tc.runTest(t)
  2539. }
  2540. func TestNoScaleDownOneMetricEmpty(t *testing.T) {
  2541. tc := testCase{
  2542. minReplicas: 2,
  2543. maxReplicas: 6,
  2544. initialReplicas: 5,
  2545. expectedDesiredReplicas: 5,
  2546. CPUTarget: 50,
  2547. metricsTarget: []autoscalingv2.MetricSpec{
  2548. {
  2549. Type: autoscalingv2.ExternalMetricSourceType,
  2550. External: &autoscalingv2.ExternalMetricSource{
  2551. Metric: autoscalingv2.MetricIdentifier{
  2552. Name: "qps",
  2553. Selector: &metav1.LabelSelector{},
  2554. },
  2555. Target: autoscalingv2.MetricTarget{
  2556. Value: resource.NewMilliQuantity(1000, resource.DecimalSI),
  2557. },
  2558. },
  2559. },
  2560. },
  2561. reportedLevels: []uint64{100, 300, 500, 250, 250},
  2562. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2563. useMetricsAPI: true,
  2564. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2565. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  2566. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetExternalMetric"},
  2567. },
  2568. }
  2569. _, _, _, testEMClient, _ := tc.prepareTestClient(t)
  2570. testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2571. return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  2572. })
  2573. tc.testEMClient = testEMClient
  2574. tc.runTest(t)
  2575. }
  2576. // TODO: add more tests