horizontal_test.go 139 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048
  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. autoscalingapiv2beta2 "k8s.io/kubernetes/pkg/apis/autoscaling/v2beta2"
  38. "k8s.io/kubernetes/pkg/controller"
  39. "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics"
  40. cmapi "k8s.io/metrics/pkg/apis/custom_metrics/v1beta2"
  41. emapi "k8s.io/metrics/pkg/apis/external_metrics/v1beta1"
  42. metricsapi "k8s.io/metrics/pkg/apis/metrics/v1beta1"
  43. metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake"
  44. cmfake "k8s.io/metrics/pkg/client/custom_metrics/fake"
  45. emfake "k8s.io/metrics/pkg/client/external_metrics/fake"
  46. utilpointer "k8s.io/utils/pointer"
  47. "github.com/stretchr/testify/assert"
  48. _ "k8s.io/kubernetes/pkg/apis/apps/install"
  49. _ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
  50. )
  51. // From now on, the HPA controller does have history in it (scaleUpEvents, scaleDownEvents)
  52. // Hence the second HPA controller reconcile cycle might return different result (comparing with the first run).
  53. // Current test infrastructure has a race condition, when several reconcile cycles will be performed
  54. // while it should be stopped right after the first one. And the second will raise an exception
  55. // because of different result.
  56. // This comment has more info: https://github.com/kubernetes/kubernetes/pull/74525#issuecomment-502653106
  57. // We need to rework this infrastructure: https://github.com/kubernetes/kubernetes/issues/79222
  58. var statusOk = []autoscalingv2.HorizontalPodAutoscalerCondition{
  59. {Type: autoscalingv2.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  60. {Type: autoscalingv2.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"},
  61. {Type: autoscalingv2.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"},
  62. }
  63. // statusOkWithOverrides returns the "ok" status with the given conditions as overridden
  64. func statusOkWithOverrides(overrides ...autoscalingv2.HorizontalPodAutoscalerCondition) []autoscalingv1.HorizontalPodAutoscalerCondition {
  65. resv2 := make([]autoscalingv2.HorizontalPodAutoscalerCondition, len(statusOk))
  66. copy(resv2, statusOk)
  67. for _, override := range overrides {
  68. resv2 = setConditionInList(resv2, override.Type, override.Status, override.Reason, override.Message)
  69. }
  70. // copy to a v1 slice
  71. resv1 := make([]autoscalingv1.HorizontalPodAutoscalerCondition, len(resv2))
  72. for i, cond := range resv2 {
  73. resv1[i] = autoscalingv1.HorizontalPodAutoscalerCondition{
  74. Type: autoscalingv1.HorizontalPodAutoscalerConditionType(cond.Type),
  75. Status: cond.Status,
  76. Reason: cond.Reason,
  77. }
  78. }
  79. return resv1
  80. }
  81. func alwaysReady() bool { return true }
  82. type fakeResource struct {
  83. name string
  84. apiVersion string
  85. kind string
  86. }
  87. type testCase struct {
  88. sync.Mutex
  89. minReplicas int32
  90. maxReplicas int32
  91. specReplicas int32
  92. statusReplicas int32
  93. initialReplicas int32
  94. scaleUpRules *autoscalingv2.HPAScalingRules
  95. scaleDownRules *autoscalingv2.HPAScalingRules
  96. // CPU target utilization as a percentage of the requested resources.
  97. CPUTarget int32
  98. CPUCurrent int32
  99. verifyCPUCurrent bool
  100. reportedLevels []uint64
  101. reportedCPURequests []resource.Quantity
  102. reportedPodReadiness []v1.ConditionStatus
  103. reportedPodStartTime []metav1.Time
  104. reportedPodPhase []v1.PodPhase
  105. reportedPodDeletionTimestamp []bool
  106. scaleUpdated bool
  107. statusUpdated bool
  108. eventCreated bool
  109. verifyEvents bool
  110. useMetricsAPI bool
  111. metricsTarget []autoscalingv2.MetricSpec
  112. expectedDesiredReplicas int32
  113. expectedConditions []autoscalingv1.HorizontalPodAutoscalerCondition
  114. // Channel with names of HPA objects which we have reconciled.
  115. processed chan string
  116. // Target resource information.
  117. resource *fakeResource
  118. // Last scale time
  119. lastScaleTime *metav1.Time
  120. // override the test clients
  121. testClient *fake.Clientset
  122. testMetricsClient *metricsfake.Clientset
  123. testCMClient *cmfake.FakeCustomMetricsClient
  124. testEMClient *emfake.FakeExternalMetricsClient
  125. testScaleClient *scalefake.FakeScaleClient
  126. recommendations []timestampedRecommendation
  127. }
  128. // Needs to be called under a lock.
  129. func (tc *testCase) computeCPUCurrent() {
  130. if len(tc.reportedLevels) != len(tc.reportedCPURequests) || len(tc.reportedLevels) == 0 {
  131. return
  132. }
  133. reported := 0
  134. for _, r := range tc.reportedLevels {
  135. reported += int(r)
  136. }
  137. requested := 0
  138. for _, req := range tc.reportedCPURequests {
  139. requested += int(req.MilliValue())
  140. }
  141. tc.CPUCurrent = int32(100 * reported / requested)
  142. }
  143. func init() {
  144. // set this high so we don't accidentally run into it when testing
  145. scaleUpLimitFactor = 8
  146. }
  147. func (tc *testCase) prepareTestClient(t *testing.T) (*fake.Clientset, *metricsfake.Clientset, *cmfake.FakeCustomMetricsClient, *emfake.FakeExternalMetricsClient, *scalefake.FakeScaleClient) {
  148. namespace := "test-namespace"
  149. hpaName := "test-hpa"
  150. podNamePrefix := "test-pod"
  151. labelSet := map[string]string{"name": podNamePrefix}
  152. selector := labels.SelectorFromSet(labelSet).String()
  153. tc.Lock()
  154. tc.scaleUpdated = false
  155. tc.statusUpdated = false
  156. tc.eventCreated = false
  157. tc.processed = make(chan string, 100)
  158. if tc.CPUCurrent == 0 {
  159. tc.computeCPUCurrent()
  160. }
  161. if tc.resource == nil {
  162. tc.resource = &fakeResource{
  163. name: "test-rc",
  164. apiVersion: "v1",
  165. kind: "ReplicationController",
  166. }
  167. }
  168. tc.Unlock()
  169. fakeClient := &fake.Clientset{}
  170. fakeClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  171. tc.Lock()
  172. defer tc.Unlock()
  173. var behavior *autoscalingv2.HorizontalPodAutoscalerBehavior
  174. if tc.scaleUpRules != nil || tc.scaleDownRules != nil {
  175. behavior = &autoscalingv2.HorizontalPodAutoscalerBehavior{
  176. ScaleUp: tc.scaleUpRules,
  177. ScaleDown: tc.scaleDownRules,
  178. }
  179. }
  180. hpa := autoscalingv2.HorizontalPodAutoscaler{
  181. ObjectMeta: metav1.ObjectMeta{
  182. Name: hpaName,
  183. Namespace: namespace,
  184. SelfLink: "experimental/v1/namespaces/" + namespace + "/horizontalpodautoscalers/" + hpaName,
  185. },
  186. Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
  187. ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
  188. Kind: tc.resource.kind,
  189. Name: tc.resource.name,
  190. APIVersion: tc.resource.apiVersion,
  191. },
  192. MinReplicas: &tc.minReplicas,
  193. MaxReplicas: tc.maxReplicas,
  194. Behavior: behavior,
  195. },
  196. Status: autoscalingv2.HorizontalPodAutoscalerStatus{
  197. CurrentReplicas: tc.specReplicas,
  198. DesiredReplicas: tc.specReplicas,
  199. LastScaleTime: tc.lastScaleTime,
  200. },
  201. }
  202. // Initialize default values
  203. autoscalingapiv2beta2.SetDefaults_HorizontalPodAutoscalerBehavior(&hpa)
  204. obj := &autoscalingv2.HorizontalPodAutoscalerList{
  205. Items: []autoscalingv2.HorizontalPodAutoscaler{hpa},
  206. }
  207. if tc.CPUTarget > 0 {
  208. obj.Items[0].Spec.Metrics = []autoscalingv2.MetricSpec{
  209. {
  210. Type: autoscalingv2.ResourceMetricSourceType,
  211. Resource: &autoscalingv2.ResourceMetricSource{
  212. Name: v1.ResourceCPU,
  213. Target: autoscalingv2.MetricTarget{
  214. AverageUtilization: &tc.CPUTarget,
  215. },
  216. },
  217. },
  218. }
  219. }
  220. if len(tc.metricsTarget) > 0 {
  221. obj.Items[0].Spec.Metrics = append(obj.Items[0].Spec.Metrics, tc.metricsTarget...)
  222. }
  223. if len(obj.Items[0].Spec.Metrics) == 0 {
  224. // manually add in the defaulting logic
  225. obj.Items[0].Spec.Metrics = []autoscalingv2.MetricSpec{
  226. {
  227. Type: autoscalingv2.ResourceMetricSourceType,
  228. Resource: &autoscalingv2.ResourceMetricSource{
  229. Name: v1.ResourceCPU,
  230. },
  231. },
  232. }
  233. }
  234. // and... convert to autoscaling v1 to return the right type
  235. objv1, err := unsafeConvertToVersionVia(obj, autoscalingv1.SchemeGroupVersion)
  236. if err != nil {
  237. return true, nil, err
  238. }
  239. return true, objv1, nil
  240. })
  241. fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  242. tc.Lock()
  243. defer tc.Unlock()
  244. obj := &v1.PodList{}
  245. specifiedCPURequests := tc.reportedCPURequests != nil
  246. numPodsToCreate := int(tc.statusReplicas)
  247. if specifiedCPURequests {
  248. numPodsToCreate = len(tc.reportedCPURequests)
  249. }
  250. for i := 0; i < numPodsToCreate; i++ {
  251. podReadiness := v1.ConditionTrue
  252. if tc.reportedPodReadiness != nil {
  253. podReadiness = tc.reportedPodReadiness[i]
  254. }
  255. var podStartTime metav1.Time
  256. if tc.reportedPodStartTime != nil {
  257. podStartTime = tc.reportedPodStartTime[i]
  258. }
  259. podPhase := v1.PodRunning
  260. if tc.reportedPodPhase != nil {
  261. podPhase = tc.reportedPodPhase[i]
  262. }
  263. podDeletionTimestamp := false
  264. if tc.reportedPodDeletionTimestamp != nil {
  265. podDeletionTimestamp = tc.reportedPodDeletionTimestamp[i]
  266. }
  267. podName := fmt.Sprintf("%s-%d", podNamePrefix, i)
  268. reportedCPURequest := resource.MustParse("1.0")
  269. if specifiedCPURequests {
  270. reportedCPURequest = tc.reportedCPURequests[i]
  271. }
  272. pod := v1.Pod{
  273. Status: v1.PodStatus{
  274. Phase: podPhase,
  275. Conditions: []v1.PodCondition{
  276. {
  277. Type: v1.PodReady,
  278. Status: podReadiness,
  279. LastTransitionTime: podStartTime,
  280. },
  281. },
  282. StartTime: &podStartTime,
  283. },
  284. ObjectMeta: metav1.ObjectMeta{
  285. Name: podName,
  286. Namespace: namespace,
  287. Labels: map[string]string{
  288. "name": podNamePrefix,
  289. },
  290. },
  291. Spec: v1.PodSpec{
  292. Containers: []v1.Container{
  293. {
  294. Resources: v1.ResourceRequirements{
  295. Requests: v1.ResourceList{
  296. v1.ResourceCPU: reportedCPURequest,
  297. },
  298. },
  299. },
  300. },
  301. },
  302. }
  303. if podDeletionTimestamp {
  304. pod.DeletionTimestamp = &metav1.Time{Time: time.Now()}
  305. }
  306. obj.Items = append(obj.Items, pod)
  307. }
  308. return true, obj, nil
  309. })
  310. fakeClient.AddReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  311. handled, obj, err := func() (handled bool, ret *autoscalingv1.HorizontalPodAutoscaler, err error) {
  312. tc.Lock()
  313. defer tc.Unlock()
  314. obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.HorizontalPodAutoscaler)
  315. assert.Equal(t, namespace, obj.Namespace, "the HPA namespace should be as expected")
  316. assert.Equal(t, hpaName, obj.Name, "the HPA name should be as expected")
  317. assert.Equal(t, tc.expectedDesiredReplicas, obj.Status.DesiredReplicas, "the desired replica count reported in the object status should be as expected")
  318. if tc.verifyCPUCurrent {
  319. if assert.NotNil(t, obj.Status.CurrentCPUUtilizationPercentage, "the reported CPU utilization percentage should be non-nil") {
  320. assert.Equal(t, tc.CPUCurrent, *obj.Status.CurrentCPUUtilizationPercentage, "the report CPU utilization percentage should be as expected")
  321. }
  322. }
  323. var actualConditions []autoscalingv1.HorizontalPodAutoscalerCondition
  324. if err := json.Unmarshal([]byte(obj.ObjectMeta.Annotations[autoscaling.HorizontalPodAutoscalerConditionsAnnotation]), &actualConditions); err != nil {
  325. return true, nil, err
  326. }
  327. // TODO: it's ok not to sort these because statusOk
  328. // contains all the conditions, so we'll never be appending.
  329. // Default to statusOk when missing any specific conditions
  330. if tc.expectedConditions == nil {
  331. tc.expectedConditions = statusOkWithOverrides()
  332. }
  333. // clear the message so that we can easily compare
  334. for i := range actualConditions {
  335. actualConditions[i].Message = ""
  336. actualConditions[i].LastTransitionTime = metav1.Time{}
  337. }
  338. assert.Equal(t, tc.expectedConditions, actualConditions, "the status conditions should have been as expected")
  339. tc.statusUpdated = true
  340. // Every time we reconcile HPA object we are updating status.
  341. return true, obj, nil
  342. }()
  343. if obj != nil {
  344. tc.processed <- obj.Name
  345. }
  346. return handled, obj, err
  347. })
  348. fakeScaleClient := &scalefake.FakeScaleClient{}
  349. fakeScaleClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  350. tc.Lock()
  351. defer tc.Unlock()
  352. obj := &autoscalingv1.Scale{
  353. ObjectMeta: metav1.ObjectMeta{
  354. Name: tc.resource.name,
  355. Namespace: namespace,
  356. },
  357. Spec: autoscalingv1.ScaleSpec{
  358. Replicas: tc.specReplicas,
  359. },
  360. Status: autoscalingv1.ScaleStatus{
  361. Replicas: tc.statusReplicas,
  362. Selector: selector,
  363. },
  364. }
  365. return true, obj, nil
  366. })
  367. fakeScaleClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  368. tc.Lock()
  369. defer tc.Unlock()
  370. obj := &autoscalingv1.Scale{
  371. ObjectMeta: metav1.ObjectMeta{
  372. Name: tc.resource.name,
  373. Namespace: namespace,
  374. },
  375. Spec: autoscalingv1.ScaleSpec{
  376. Replicas: tc.specReplicas,
  377. },
  378. Status: autoscalingv1.ScaleStatus{
  379. Replicas: tc.statusReplicas,
  380. Selector: selector,
  381. },
  382. }
  383. return true, obj, nil
  384. })
  385. fakeScaleClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  386. tc.Lock()
  387. defer tc.Unlock()
  388. obj := &autoscalingv1.Scale{
  389. ObjectMeta: metav1.ObjectMeta{
  390. Name: tc.resource.name,
  391. Namespace: namespace,
  392. },
  393. Spec: autoscalingv1.ScaleSpec{
  394. Replicas: tc.specReplicas,
  395. },
  396. Status: autoscalingv1.ScaleStatus{
  397. Replicas: tc.statusReplicas,
  398. Selector: selector,
  399. },
  400. }
  401. return true, obj, nil
  402. })
  403. fakeScaleClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  404. tc.Lock()
  405. defer tc.Unlock()
  406. obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
  407. replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
  408. assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the RC should be as expected")
  409. tc.scaleUpdated = true
  410. return true, obj, nil
  411. })
  412. fakeScaleClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  413. tc.Lock()
  414. defer tc.Unlock()
  415. obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
  416. replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
  417. assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the deployment should be as expected")
  418. tc.scaleUpdated = true
  419. return true, obj, nil
  420. })
  421. fakeScaleClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  422. tc.Lock()
  423. defer tc.Unlock()
  424. obj := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale)
  425. replicas := action.(core.UpdateAction).GetObject().(*autoscalingv1.Scale).Spec.Replicas
  426. assert.Equal(t, tc.expectedDesiredReplicas, replicas, "the replica count of the replicaset should be as expected")
  427. tc.scaleUpdated = true
  428. return true, obj, nil
  429. })
  430. fakeWatch := watch.NewFake()
  431. fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil))
  432. fakeMetricsClient := &metricsfake.Clientset{}
  433. fakeMetricsClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  434. tc.Lock()
  435. defer tc.Unlock()
  436. metrics := &metricsapi.PodMetricsList{}
  437. for i, cpu := range tc.reportedLevels {
  438. // NB: the list reactor actually does label selector filtering for us,
  439. // so we have to make sure our results match the label selector
  440. podMetric := metricsapi.PodMetrics{
  441. ObjectMeta: metav1.ObjectMeta{
  442. Name: fmt.Sprintf("%s-%d", podNamePrefix, i),
  443. Namespace: namespace,
  444. Labels: labelSet,
  445. },
  446. Timestamp: metav1.Time{Time: time.Now()},
  447. Window: metav1.Duration{Duration: time.Minute},
  448. Containers: []metricsapi.ContainerMetrics{
  449. {
  450. Name: "container",
  451. Usage: v1.ResourceList{
  452. v1.ResourceCPU: *resource.NewMilliQuantity(
  453. int64(cpu),
  454. resource.DecimalSI),
  455. v1.ResourceMemory: *resource.NewQuantity(
  456. int64(1024*1024),
  457. resource.BinarySI),
  458. },
  459. },
  460. },
  461. }
  462. metrics.Items = append(metrics.Items, podMetric)
  463. }
  464. return true, metrics, nil
  465. })
  466. fakeCMClient := &cmfake.FakeCustomMetricsClient{}
  467. fakeCMClient.AddReactor("get", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  468. tc.Lock()
  469. defer tc.Unlock()
  470. getForAction, wasGetFor := action.(cmfake.GetForAction)
  471. if !wasGetFor {
  472. return true, nil, fmt.Errorf("expected a get-for action, got %v instead", action)
  473. }
  474. if getForAction.GetName() == "*" {
  475. metrics := &cmapi.MetricValueList{}
  476. // multiple objects
  477. assert.Equal(t, "pods", getForAction.GetResource().Resource, "the type of object that we requested multiple metrics for should have been pods")
  478. assert.Equal(t, "qps", getForAction.GetMetricName(), "the metric name requested should have been qps, as specified in the metric spec")
  479. for i, level := range tc.reportedLevels {
  480. podMetric := cmapi.MetricValue{
  481. DescribedObject: v1.ObjectReference{
  482. Kind: "Pod",
  483. Name: fmt.Sprintf("%s-%d", podNamePrefix, i),
  484. Namespace: namespace,
  485. },
  486. Timestamp: metav1.Time{Time: time.Now()},
  487. Metric: cmapi.MetricIdentifier{
  488. Name: "qps",
  489. },
  490. Value: *resource.NewMilliQuantity(int64(level), resource.DecimalSI),
  491. }
  492. metrics.Items = append(metrics.Items, podMetric)
  493. }
  494. return true, metrics, nil
  495. }
  496. name := getForAction.GetName()
  497. mapper := testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme)
  498. metrics := &cmapi.MetricValueList{}
  499. var matchedTarget *autoscalingv2.MetricSpec
  500. for i, target := range tc.metricsTarget {
  501. if target.Type == autoscalingv2.ObjectMetricSourceType && name == target.Object.DescribedObject.Name {
  502. gk := schema.FromAPIVersionAndKind(target.Object.DescribedObject.APIVersion, target.Object.DescribedObject.Kind).GroupKind()
  503. mapping, err := mapper.RESTMapping(gk)
  504. if err != nil {
  505. t.Logf("unable to get mapping for %s: %v", gk.String(), err)
  506. continue
  507. }
  508. groupResource := mapping.Resource.GroupResource()
  509. if getForAction.GetResource().Resource == groupResource.String() {
  510. matchedTarget = &tc.metricsTarget[i]
  511. }
  512. }
  513. }
  514. assert.NotNil(t, matchedTarget, "this request should have matched one of the metric specs")
  515. assert.Equal(t, "qps", getForAction.GetMetricName(), "the metric name requested should have been qps, as specified in the metric spec")
  516. metrics.Items = []cmapi.MetricValue{
  517. {
  518. DescribedObject: v1.ObjectReference{
  519. Kind: matchedTarget.Object.DescribedObject.Kind,
  520. APIVersion: matchedTarget.Object.DescribedObject.APIVersion,
  521. Name: name,
  522. },
  523. Timestamp: metav1.Time{Time: time.Now()},
  524. Metric: cmapi.MetricIdentifier{
  525. Name: "qps",
  526. },
  527. Value: *resource.NewMilliQuantity(int64(tc.reportedLevels[0]), resource.DecimalSI),
  528. },
  529. }
  530. return true, metrics, nil
  531. })
  532. fakeEMClient := &emfake.FakeExternalMetricsClient{}
  533. fakeEMClient.AddReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  534. tc.Lock()
  535. defer tc.Unlock()
  536. listAction, wasList := action.(core.ListAction)
  537. if !wasList {
  538. return true, nil, fmt.Errorf("expected a list action, got %v instead", action)
  539. }
  540. metrics := &emapi.ExternalMetricValueList{}
  541. assert.Equal(t, "qps", listAction.GetResource().Resource, "the metric name requested should have been qps, as specified in the metric spec")
  542. for _, level := range tc.reportedLevels {
  543. metric := emapi.ExternalMetricValue{
  544. Timestamp: metav1.Time{Time: time.Now()},
  545. MetricName: "qps",
  546. Value: *resource.NewMilliQuantity(int64(level), resource.DecimalSI),
  547. }
  548. metrics.Items = append(metrics.Items, metric)
  549. }
  550. return true, metrics, nil
  551. })
  552. return fakeClient, fakeMetricsClient, fakeCMClient, fakeEMClient, fakeScaleClient
  553. }
  554. func (tc *testCase) verifyResults(t *testing.T) {
  555. tc.Lock()
  556. defer tc.Unlock()
  557. assert.Equal(t, tc.specReplicas != tc.expectedDesiredReplicas, tc.scaleUpdated, "the scale should only be updated if we expected a change in replicas")
  558. assert.True(t, tc.statusUpdated, "the status should have been updated")
  559. if tc.verifyEvents {
  560. assert.Equal(t, tc.specReplicas != tc.expectedDesiredReplicas, tc.eventCreated, "an event should have been created only if we expected a change in replicas")
  561. }
  562. }
  563. func (tc *testCase) setupController(t *testing.T) (*HorizontalController, informers.SharedInformerFactory) {
  564. testClient, testMetricsClient, testCMClient, testEMClient, testScaleClient := tc.prepareTestClient(t)
  565. if tc.testClient != nil {
  566. testClient = tc.testClient
  567. }
  568. if tc.testMetricsClient != nil {
  569. testMetricsClient = tc.testMetricsClient
  570. }
  571. if tc.testCMClient != nil {
  572. testCMClient = tc.testCMClient
  573. }
  574. if tc.testEMClient != nil {
  575. testEMClient = tc.testEMClient
  576. }
  577. if tc.testScaleClient != nil {
  578. testScaleClient = tc.testScaleClient
  579. }
  580. metricsClient := metrics.NewRESTMetricsClient(
  581. testMetricsClient.MetricsV1beta1(),
  582. testCMClient,
  583. testEMClient,
  584. )
  585. eventClient := &fake.Clientset{}
  586. eventClient.AddReactor("create", "events", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  587. tc.Lock()
  588. defer tc.Unlock()
  589. obj := action.(core.CreateAction).GetObject().(*v1.Event)
  590. if tc.verifyEvents {
  591. switch obj.Reason {
  592. case "SuccessfulRescale":
  593. assert.Equal(t, fmt.Sprintf("New size: %d; reason: cpu resource utilization (percentage of request) above target", tc.expectedDesiredReplicas), obj.Message)
  594. case "DesiredReplicasComputed":
  595. assert.Equal(t, fmt.Sprintf(
  596. "Computed the desired num of replicas: %d (avgCPUutil: %d, current replicas: %d)",
  597. tc.expectedDesiredReplicas,
  598. (int64(tc.reportedLevels[0])*100)/tc.reportedCPURequests[0].MilliValue(), tc.specReplicas), obj.Message)
  599. default:
  600. assert.False(t, true, fmt.Sprintf("Unexpected event: %s / %s", obj.Reason, obj.Message))
  601. }
  602. }
  603. tc.eventCreated = true
  604. return true, obj, nil
  605. })
  606. informerFactory := informers.NewSharedInformerFactory(testClient, controller.NoResyncPeriodFunc())
  607. defaultDownscalestabilizationWindow := 5 * time.Minute
  608. hpaController := NewHorizontalController(
  609. eventClient.CoreV1(),
  610. testScaleClient,
  611. testClient.AutoscalingV1(),
  612. testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme),
  613. metricsClient,
  614. informerFactory.Autoscaling().V1().HorizontalPodAutoscalers(),
  615. informerFactory.Core().V1().Pods(),
  616. 100*time.Millisecond, // we need non-zero resync period to avoid race conditions
  617. defaultDownscalestabilizationWindow,
  618. defaultTestingTolerance,
  619. defaultTestingCpuInitializationPeriod,
  620. defaultTestingDelayOfInitialReadinessStatus,
  621. )
  622. hpaController.hpaListerSynced = alwaysReady
  623. if tc.recommendations != nil {
  624. hpaController.recommendations["test-namespace/test-hpa"] = tc.recommendations
  625. }
  626. return hpaController, informerFactory
  627. }
  628. func hotCpuCreationTime() metav1.Time {
  629. return metav1.Time{Time: time.Now()}
  630. }
  631. func coolCpuCreationTime() metav1.Time {
  632. return metav1.Time{Time: time.Now().Add(-3 * time.Minute)}
  633. }
  634. func (tc *testCase) runTestWithController(t *testing.T, hpaController *HorizontalController, informerFactory informers.SharedInformerFactory) {
  635. stop := make(chan struct{})
  636. defer close(stop)
  637. informerFactory.Start(stop)
  638. go hpaController.Run(stop)
  639. tc.Lock()
  640. shouldWait := tc.verifyEvents
  641. tc.Unlock()
  642. if shouldWait {
  643. // We need to wait for events to be broadcasted (sleep for longer than record.sleepDuration).
  644. timeoutTime := time.Now().Add(2 * time.Second)
  645. for now := time.Now(); timeoutTime.After(now); now = time.Now() {
  646. sleepUntil := timeoutTime.Sub(now)
  647. select {
  648. case <-tc.processed:
  649. // drain the chan of any sent events to keep it from filling before the timeout
  650. case <-time.After(sleepUntil):
  651. // timeout reached, ready to verifyResults
  652. }
  653. }
  654. } else {
  655. // Wait for HPA to be processed.
  656. <-tc.processed
  657. }
  658. tc.verifyResults(t)
  659. }
  660. func (tc *testCase) runTest(t *testing.T) {
  661. hpaController, informerFactory := tc.setupController(t)
  662. tc.runTestWithController(t, hpaController, informerFactory)
  663. }
  664. func TestScaleUp(t *testing.T) {
  665. tc := testCase{
  666. minReplicas: 2,
  667. maxReplicas: 6,
  668. specReplicas: 3,
  669. statusReplicas: 3,
  670. expectedDesiredReplicas: 5,
  671. CPUTarget: 30,
  672. verifyCPUCurrent: true,
  673. reportedLevels: []uint64{300, 500, 700},
  674. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  675. useMetricsAPI: true,
  676. }
  677. tc.runTest(t)
  678. }
  679. func TestScaleUpUnreadyLessScale(t *testing.T) {
  680. tc := testCase{
  681. minReplicas: 2,
  682. maxReplicas: 6,
  683. specReplicas: 3,
  684. statusReplicas: 3,
  685. expectedDesiredReplicas: 4,
  686. CPUTarget: 30,
  687. CPUCurrent: 60,
  688. verifyCPUCurrent: true,
  689. reportedLevels: []uint64{300, 500, 700},
  690. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  691. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionTrue},
  692. useMetricsAPI: true,
  693. }
  694. tc.runTest(t)
  695. }
  696. func TestScaleUpHotCpuLessScale(t *testing.T) {
  697. tc := testCase{
  698. minReplicas: 2,
  699. maxReplicas: 6,
  700. specReplicas: 3,
  701. statusReplicas: 3,
  702. expectedDesiredReplicas: 4,
  703. CPUTarget: 30,
  704. CPUCurrent: 60,
  705. verifyCPUCurrent: true,
  706. reportedLevels: []uint64{300, 500, 700},
  707. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  708. reportedPodStartTime: []metav1.Time{hotCpuCreationTime(), coolCpuCreationTime(), coolCpuCreationTime()},
  709. useMetricsAPI: true,
  710. }
  711. tc.runTest(t)
  712. }
  713. func TestScaleUpUnreadyNoScale(t *testing.T) {
  714. tc := testCase{
  715. minReplicas: 2,
  716. maxReplicas: 6,
  717. specReplicas: 3,
  718. statusReplicas: 3,
  719. expectedDesiredReplicas: 3,
  720. CPUTarget: 30,
  721. CPUCurrent: 40,
  722. verifyCPUCurrent: true,
  723. reportedLevels: []uint64{400, 500, 700},
  724. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  725. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  726. useMetricsAPI: true,
  727. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  728. Type: autoscalingv2.AbleToScale,
  729. Status: v1.ConditionTrue,
  730. Reason: "ReadyForNewScale",
  731. }),
  732. }
  733. tc.runTest(t)
  734. }
  735. func TestScaleUpHotCpuNoScale(t *testing.T) {
  736. tc := testCase{
  737. minReplicas: 2,
  738. maxReplicas: 6,
  739. specReplicas: 3,
  740. statusReplicas: 3,
  741. expectedDesiredReplicas: 3,
  742. CPUTarget: 30,
  743. CPUCurrent: 40,
  744. verifyCPUCurrent: true,
  745. reportedLevels: []uint64{400, 500, 700},
  746. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  747. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  748. reportedPodStartTime: []metav1.Time{coolCpuCreationTime(), hotCpuCreationTime(), hotCpuCreationTime()},
  749. useMetricsAPI: true,
  750. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  751. Type: autoscalingv2.AbleToScale,
  752. Status: v1.ConditionTrue,
  753. Reason: "ReadyForNewScale",
  754. }),
  755. }
  756. tc.runTest(t)
  757. }
  758. func TestScaleUpIgnoresFailedPods(t *testing.T) {
  759. tc := testCase{
  760. minReplicas: 2,
  761. maxReplicas: 6,
  762. specReplicas: 2,
  763. statusReplicas: 2,
  764. expectedDesiredReplicas: 4,
  765. CPUTarget: 30,
  766. CPUCurrent: 60,
  767. verifyCPUCurrent: true,
  768. reportedLevels: []uint64{500, 700},
  769. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  770. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  771. reportedPodPhase: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodFailed, v1.PodFailed},
  772. useMetricsAPI: true,
  773. }
  774. tc.runTest(t)
  775. }
  776. func TestScaleUpIgnoresDeletionPods(t *testing.T) {
  777. tc := testCase{
  778. minReplicas: 2,
  779. maxReplicas: 6,
  780. specReplicas: 2,
  781. statusReplicas: 2,
  782. expectedDesiredReplicas: 4,
  783. CPUTarget: 30,
  784. CPUCurrent: 60,
  785. verifyCPUCurrent: true,
  786. reportedLevels: []uint64{500, 700},
  787. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  788. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  789. reportedPodPhase: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning},
  790. reportedPodDeletionTimestamp: []bool{false, false, true, true},
  791. useMetricsAPI: true,
  792. }
  793. tc.runTest(t)
  794. }
  795. func TestScaleUpDeployment(t *testing.T) {
  796. tc := testCase{
  797. minReplicas: 2,
  798. maxReplicas: 6,
  799. specReplicas: 3,
  800. statusReplicas: 3,
  801. expectedDesiredReplicas: 5,
  802. CPUTarget: 30,
  803. verifyCPUCurrent: true,
  804. reportedLevels: []uint64{300, 500, 700},
  805. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  806. useMetricsAPI: true,
  807. resource: &fakeResource{
  808. name: "test-dep",
  809. apiVersion: "apps/v1",
  810. kind: "Deployment",
  811. },
  812. }
  813. tc.runTest(t)
  814. }
  815. func TestScaleUpReplicaSet(t *testing.T) {
  816. tc := testCase{
  817. minReplicas: 2,
  818. maxReplicas: 6,
  819. specReplicas: 3,
  820. statusReplicas: 3,
  821. expectedDesiredReplicas: 5,
  822. CPUTarget: 30,
  823. verifyCPUCurrent: true,
  824. reportedLevels: []uint64{300, 500, 700},
  825. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  826. useMetricsAPI: true,
  827. resource: &fakeResource{
  828. name: "test-replicaset",
  829. apiVersion: "apps/v1",
  830. kind: "ReplicaSet",
  831. },
  832. }
  833. tc.runTest(t)
  834. }
  835. func TestScaleUpCM(t *testing.T) {
  836. averageValue := resource.MustParse("15.0")
  837. tc := testCase{
  838. minReplicas: 2,
  839. maxReplicas: 6,
  840. specReplicas: 3,
  841. statusReplicas: 3,
  842. expectedDesiredReplicas: 4,
  843. CPUTarget: 0,
  844. metricsTarget: []autoscalingv2.MetricSpec{
  845. {
  846. Type: autoscalingv2.PodsMetricSourceType,
  847. Pods: &autoscalingv2.PodsMetricSource{
  848. Metric: autoscalingv2.MetricIdentifier{
  849. Name: "qps",
  850. },
  851. Target: autoscalingv2.MetricTarget{
  852. AverageValue: &averageValue,
  853. },
  854. },
  855. },
  856. },
  857. reportedLevels: []uint64{20000, 10000, 30000},
  858. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  859. }
  860. tc.runTest(t)
  861. }
  862. func TestScaleUpCMUnreadyAndHotCpuNoLessScale(t *testing.T) {
  863. averageValue := resource.MustParse("15.0")
  864. tc := testCase{
  865. minReplicas: 2,
  866. maxReplicas: 6,
  867. specReplicas: 3,
  868. statusReplicas: 3,
  869. expectedDesiredReplicas: 6,
  870. CPUTarget: 0,
  871. metricsTarget: []autoscalingv2.MetricSpec{
  872. {
  873. Type: autoscalingv2.PodsMetricSourceType,
  874. Pods: &autoscalingv2.PodsMetricSource{
  875. Metric: autoscalingv2.MetricIdentifier{
  876. Name: "qps",
  877. },
  878. Target: autoscalingv2.MetricTarget{
  879. AverageValue: &averageValue,
  880. },
  881. },
  882. },
  883. },
  884. reportedLevels: []uint64{50000, 10000, 30000},
  885. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse},
  886. reportedPodStartTime: []metav1.Time{coolCpuCreationTime(), coolCpuCreationTime(), hotCpuCreationTime()},
  887. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  888. }
  889. tc.runTest(t)
  890. }
  891. func TestScaleUpCMUnreadyandCpuHot(t *testing.T) {
  892. averageValue := resource.MustParse("15.0")
  893. tc := testCase{
  894. minReplicas: 2,
  895. maxReplicas: 6,
  896. specReplicas: 3,
  897. statusReplicas: 3,
  898. expectedDesiredReplicas: 6,
  899. CPUTarget: 0,
  900. metricsTarget: []autoscalingv2.MetricSpec{
  901. {
  902. Type: autoscalingv2.PodsMetricSourceType,
  903. Pods: &autoscalingv2.PodsMetricSource{
  904. Metric: autoscalingv2.MetricIdentifier{
  905. Name: "qps",
  906. },
  907. Target: autoscalingv2.MetricTarget{
  908. AverageValue: &averageValue,
  909. },
  910. },
  911. },
  912. },
  913. reportedLevels: []uint64{50000, 15000, 30000},
  914. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionFalse, v1.ConditionTrue, v1.ConditionFalse},
  915. reportedPodStartTime: []metav1.Time{hotCpuCreationTime(), coolCpuCreationTime(), hotCpuCreationTime()},
  916. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  917. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  918. Type: autoscalingv2.AbleToScale,
  919. Status: v1.ConditionTrue,
  920. Reason: "SucceededRescale",
  921. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  922. Type: autoscalingv2.ScalingLimited,
  923. Status: v1.ConditionTrue,
  924. Reason: "TooManyReplicas",
  925. }),
  926. }
  927. tc.runTest(t)
  928. }
  929. func TestScaleUpHotCpuNoScaleWouldScaleDown(t *testing.T) {
  930. averageValue := resource.MustParse("15.0")
  931. tc := testCase{
  932. minReplicas: 2,
  933. maxReplicas: 6,
  934. specReplicas: 3,
  935. statusReplicas: 3,
  936. expectedDesiredReplicas: 6,
  937. CPUTarget: 0,
  938. metricsTarget: []autoscalingv2.MetricSpec{
  939. {
  940. Type: autoscalingv2.PodsMetricSourceType,
  941. Pods: &autoscalingv2.PodsMetricSource{
  942. Metric: autoscalingv2.MetricIdentifier{
  943. Name: "qps",
  944. },
  945. Target: autoscalingv2.MetricTarget{
  946. AverageValue: &averageValue,
  947. },
  948. },
  949. },
  950. },
  951. reportedLevels: []uint64{50000, 15000, 30000},
  952. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  953. reportedPodStartTime: []metav1.Time{hotCpuCreationTime(), coolCpuCreationTime(), hotCpuCreationTime()},
  954. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  955. Type: autoscalingv2.AbleToScale,
  956. Status: v1.ConditionTrue,
  957. Reason: "SucceededRescale",
  958. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  959. Type: autoscalingv2.ScalingLimited,
  960. Status: v1.ConditionTrue,
  961. Reason: "TooManyReplicas",
  962. }),
  963. }
  964. tc.runTest(t)
  965. }
  966. func TestScaleUpCMObject(t *testing.T) {
  967. targetValue := resource.MustParse("15.0")
  968. tc := testCase{
  969. minReplicas: 2,
  970. maxReplicas: 6,
  971. specReplicas: 3,
  972. statusReplicas: 3,
  973. expectedDesiredReplicas: 4,
  974. CPUTarget: 0,
  975. metricsTarget: []autoscalingv2.MetricSpec{
  976. {
  977. Type: autoscalingv2.ObjectMetricSourceType,
  978. Object: &autoscalingv2.ObjectMetricSource{
  979. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  980. APIVersion: "apps/v1",
  981. Kind: "Deployment",
  982. Name: "some-deployment",
  983. },
  984. Metric: autoscalingv2.MetricIdentifier{
  985. Name: "qps",
  986. },
  987. Target: autoscalingv2.MetricTarget{
  988. Value: &targetValue,
  989. },
  990. },
  991. },
  992. },
  993. reportedLevels: []uint64{20000},
  994. }
  995. tc.runTest(t)
  996. }
  997. func TestScaleUpFromZeroCMObject(t *testing.T) {
  998. targetValue := resource.MustParse("15.0")
  999. tc := testCase{
  1000. minReplicas: 0,
  1001. maxReplicas: 6,
  1002. specReplicas: 0,
  1003. statusReplicas: 0,
  1004. expectedDesiredReplicas: 2,
  1005. CPUTarget: 0,
  1006. metricsTarget: []autoscalingv2.MetricSpec{
  1007. {
  1008. Type: autoscalingv2.ObjectMetricSourceType,
  1009. Object: &autoscalingv2.ObjectMetricSource{
  1010. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1011. APIVersion: "apps/v1",
  1012. Kind: "Deployment",
  1013. Name: "some-deployment",
  1014. },
  1015. Metric: autoscalingv2.MetricIdentifier{
  1016. Name: "qps",
  1017. },
  1018. Target: autoscalingv2.MetricTarget{
  1019. Value: &targetValue,
  1020. },
  1021. },
  1022. },
  1023. },
  1024. reportedLevels: []uint64{20000},
  1025. }
  1026. tc.runTest(t)
  1027. }
  1028. func TestScaleUpFromZeroIgnoresToleranceCMObject(t *testing.T) {
  1029. targetValue := resource.MustParse("1.0")
  1030. tc := testCase{
  1031. minReplicas: 0,
  1032. maxReplicas: 6,
  1033. specReplicas: 0,
  1034. statusReplicas: 0,
  1035. expectedDesiredReplicas: 1,
  1036. CPUTarget: 0,
  1037. metricsTarget: []autoscalingv2.MetricSpec{
  1038. {
  1039. Type: autoscalingv2.ObjectMetricSourceType,
  1040. Object: &autoscalingv2.ObjectMetricSource{
  1041. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1042. APIVersion: "apps/v1",
  1043. Kind: "Deployment",
  1044. Name: "some-deployment",
  1045. },
  1046. Metric: autoscalingv2.MetricIdentifier{
  1047. Name: "qps",
  1048. },
  1049. Target: autoscalingv2.MetricTarget{
  1050. Value: &targetValue,
  1051. },
  1052. },
  1053. },
  1054. },
  1055. reportedLevels: []uint64{1000},
  1056. }
  1057. tc.runTest(t)
  1058. }
  1059. func TestScaleUpPerPodCMObject(t *testing.T) {
  1060. targetAverageValue := resource.MustParse("10.0")
  1061. tc := testCase{
  1062. minReplicas: 2,
  1063. maxReplicas: 6,
  1064. specReplicas: 3,
  1065. statusReplicas: 3,
  1066. expectedDesiredReplicas: 4,
  1067. CPUTarget: 0,
  1068. metricsTarget: []autoscalingv2.MetricSpec{
  1069. {
  1070. Type: autoscalingv2.ObjectMetricSourceType,
  1071. Object: &autoscalingv2.ObjectMetricSource{
  1072. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1073. APIVersion: "apps/v1",
  1074. Kind: "Deployment",
  1075. Name: "some-deployment",
  1076. },
  1077. Metric: autoscalingv2.MetricIdentifier{
  1078. Name: "qps",
  1079. },
  1080. Target: autoscalingv2.MetricTarget{
  1081. AverageValue: &targetAverageValue,
  1082. },
  1083. },
  1084. },
  1085. },
  1086. reportedLevels: []uint64{40000},
  1087. }
  1088. tc.runTest(t)
  1089. }
  1090. func TestScaleUpCMExternal(t *testing.T) {
  1091. tc := testCase{
  1092. minReplicas: 2,
  1093. maxReplicas: 6,
  1094. specReplicas: 3,
  1095. statusReplicas: 3,
  1096. expectedDesiredReplicas: 4,
  1097. metricsTarget: []autoscalingv2.MetricSpec{
  1098. {
  1099. Type: autoscalingv2.ExternalMetricSourceType,
  1100. External: &autoscalingv2.ExternalMetricSource{
  1101. Metric: autoscalingv2.MetricIdentifier{
  1102. Name: "qps",
  1103. Selector: &metav1.LabelSelector{},
  1104. },
  1105. Target: autoscalingv2.MetricTarget{
  1106. Value: resource.NewMilliQuantity(6666, resource.DecimalSI),
  1107. },
  1108. },
  1109. },
  1110. },
  1111. reportedLevels: []uint64{8600},
  1112. }
  1113. tc.runTest(t)
  1114. }
  1115. func TestScaleUpPerPodCMExternal(t *testing.T) {
  1116. tc := testCase{
  1117. minReplicas: 2,
  1118. maxReplicas: 6,
  1119. specReplicas: 3,
  1120. statusReplicas: 3,
  1121. expectedDesiredReplicas: 4,
  1122. metricsTarget: []autoscalingv2.MetricSpec{
  1123. {
  1124. Type: autoscalingv2.ExternalMetricSourceType,
  1125. External: &autoscalingv2.ExternalMetricSource{
  1126. Metric: autoscalingv2.MetricIdentifier{
  1127. Name: "qps",
  1128. Selector: &metav1.LabelSelector{},
  1129. },
  1130. Target: autoscalingv2.MetricTarget{
  1131. AverageValue: resource.NewMilliQuantity(2222, resource.DecimalSI),
  1132. },
  1133. },
  1134. },
  1135. },
  1136. reportedLevels: []uint64{8600},
  1137. }
  1138. tc.runTest(t)
  1139. }
  1140. func TestScaleDown(t *testing.T) {
  1141. tc := testCase{
  1142. minReplicas: 2,
  1143. maxReplicas: 6,
  1144. specReplicas: 5,
  1145. statusReplicas: 5,
  1146. expectedDesiredReplicas: 3,
  1147. CPUTarget: 50,
  1148. verifyCPUCurrent: true,
  1149. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1150. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1151. useMetricsAPI: true,
  1152. recommendations: []timestampedRecommendation{},
  1153. }
  1154. tc.runTest(t)
  1155. }
  1156. func TestScaleDownWithScalingRules(t *testing.T) {
  1157. tc := testCase{
  1158. minReplicas: 2,
  1159. maxReplicas: 6,
  1160. scaleUpRules: generateScalingRules(0, 0, 100, 15, 30),
  1161. specReplicas: 5,
  1162. statusReplicas: 5,
  1163. expectedDesiredReplicas: 3,
  1164. CPUTarget: 50,
  1165. verifyCPUCurrent: true,
  1166. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1167. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1168. useMetricsAPI: true,
  1169. recommendations: []timestampedRecommendation{},
  1170. }
  1171. tc.runTest(t)
  1172. }
  1173. func TestScaleUpOneMetricInvalid(t *testing.T) {
  1174. tc := testCase{
  1175. minReplicas: 2,
  1176. maxReplicas: 6,
  1177. specReplicas: 3,
  1178. statusReplicas: 3,
  1179. expectedDesiredReplicas: 4,
  1180. CPUTarget: 30,
  1181. verifyCPUCurrent: true,
  1182. metricsTarget: []autoscalingv2.MetricSpec{
  1183. {
  1184. Type: "CheddarCheese",
  1185. },
  1186. },
  1187. reportedLevels: []uint64{300, 400, 500},
  1188. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1189. }
  1190. tc.runTest(t)
  1191. }
  1192. func TestScaleUpFromZeroOneMetricInvalid(t *testing.T) {
  1193. tc := testCase{
  1194. minReplicas: 0,
  1195. maxReplicas: 6,
  1196. specReplicas: 0,
  1197. statusReplicas: 0,
  1198. expectedDesiredReplicas: 4,
  1199. CPUTarget: 30,
  1200. verifyCPUCurrent: true,
  1201. metricsTarget: []autoscalingv2.MetricSpec{
  1202. {
  1203. Type: "CheddarCheese",
  1204. },
  1205. },
  1206. reportedLevels: []uint64{300, 400, 500},
  1207. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1208. recommendations: []timestampedRecommendation{},
  1209. }
  1210. tc.runTest(t)
  1211. }
  1212. func TestScaleUpBothMetricsEmpty(t *testing.T) { // Switch to missing
  1213. tc := testCase{
  1214. minReplicas: 2,
  1215. maxReplicas: 6,
  1216. specReplicas: 3,
  1217. statusReplicas: 3,
  1218. expectedDesiredReplicas: 3,
  1219. CPUTarget: 0,
  1220. metricsTarget: []autoscalingv2.MetricSpec{
  1221. {
  1222. Type: "CheddarCheese",
  1223. },
  1224. },
  1225. reportedLevels: []uint64{},
  1226. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1227. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1228. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1229. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "InvalidMetricSourceType"},
  1230. },
  1231. }
  1232. tc.runTest(t)
  1233. }
  1234. func TestScaleDownStabilizeInitialSize(t *testing.T) {
  1235. tc := testCase{
  1236. minReplicas: 2,
  1237. maxReplicas: 6,
  1238. specReplicas: 5,
  1239. statusReplicas: 5,
  1240. expectedDesiredReplicas: 5,
  1241. CPUTarget: 50,
  1242. verifyCPUCurrent: true,
  1243. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1244. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1245. useMetricsAPI: true,
  1246. recommendations: nil,
  1247. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1248. Type: autoscalingv2.AbleToScale,
  1249. Status: v1.ConditionTrue,
  1250. Reason: "ReadyForNewScale",
  1251. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  1252. Type: autoscalingv2.AbleToScale,
  1253. Status: v1.ConditionTrue,
  1254. Reason: "ScaleDownStabilized",
  1255. }),
  1256. }
  1257. tc.runTest(t)
  1258. }
  1259. func TestScaleDownCM(t *testing.T) {
  1260. averageValue := resource.MustParse("20.0")
  1261. tc := testCase{
  1262. minReplicas: 2,
  1263. maxReplicas: 6,
  1264. specReplicas: 5,
  1265. statusReplicas: 5,
  1266. expectedDesiredReplicas: 3,
  1267. CPUTarget: 0,
  1268. metricsTarget: []autoscalingv2.MetricSpec{
  1269. {
  1270. Type: autoscalingv2.PodsMetricSourceType,
  1271. Pods: &autoscalingv2.PodsMetricSource{
  1272. Metric: autoscalingv2.MetricIdentifier{
  1273. Name: "qps",
  1274. },
  1275. Target: autoscalingv2.MetricTarget{
  1276. AverageValue: &averageValue,
  1277. },
  1278. },
  1279. },
  1280. },
  1281. reportedLevels: []uint64{12000, 12000, 12000, 12000, 12000},
  1282. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1283. recommendations: []timestampedRecommendation{},
  1284. }
  1285. tc.runTest(t)
  1286. }
  1287. func TestScaleDownCMObject(t *testing.T) {
  1288. targetValue := resource.MustParse("20.0")
  1289. tc := testCase{
  1290. minReplicas: 2,
  1291. maxReplicas: 6,
  1292. specReplicas: 5,
  1293. statusReplicas: 5,
  1294. expectedDesiredReplicas: 3,
  1295. CPUTarget: 0,
  1296. metricsTarget: []autoscalingv2.MetricSpec{
  1297. {
  1298. Type: autoscalingv2.ObjectMetricSourceType,
  1299. Object: &autoscalingv2.ObjectMetricSource{
  1300. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1301. APIVersion: "apps/v1",
  1302. Kind: "Deployment",
  1303. Name: "some-deployment",
  1304. },
  1305. Metric: autoscalingv2.MetricIdentifier{
  1306. Name: "qps",
  1307. },
  1308. Target: autoscalingv2.MetricTarget{
  1309. Value: &targetValue,
  1310. },
  1311. },
  1312. },
  1313. },
  1314. reportedLevels: []uint64{12000},
  1315. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1316. recommendations: []timestampedRecommendation{},
  1317. }
  1318. tc.runTest(t)
  1319. }
  1320. func TestScaleDownToZeroCMObject(t *testing.T) {
  1321. targetValue := resource.MustParse("20.0")
  1322. tc := testCase{
  1323. minReplicas: 0,
  1324. maxReplicas: 6,
  1325. specReplicas: 5,
  1326. statusReplicas: 5,
  1327. expectedDesiredReplicas: 0,
  1328. CPUTarget: 0,
  1329. metricsTarget: []autoscalingv2.MetricSpec{
  1330. {
  1331. Type: autoscalingv2.ObjectMetricSourceType,
  1332. Object: &autoscalingv2.ObjectMetricSource{
  1333. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1334. APIVersion: "apps/v1",
  1335. Kind: "Deployment",
  1336. Name: "some-deployment",
  1337. },
  1338. Metric: autoscalingv2.MetricIdentifier{
  1339. Name: "qps",
  1340. },
  1341. Target: autoscalingv2.MetricTarget{
  1342. Value: &targetValue,
  1343. },
  1344. },
  1345. },
  1346. },
  1347. reportedLevels: []uint64{0},
  1348. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1349. recommendations: []timestampedRecommendation{},
  1350. }
  1351. tc.runTest(t)
  1352. }
  1353. func TestScaleDownPerPodCMObject(t *testing.T) {
  1354. targetAverageValue := resource.MustParse("20.0")
  1355. tc := testCase{
  1356. minReplicas: 2,
  1357. maxReplicas: 6,
  1358. specReplicas: 5,
  1359. statusReplicas: 5,
  1360. expectedDesiredReplicas: 3,
  1361. CPUTarget: 0,
  1362. metricsTarget: []autoscalingv2.MetricSpec{
  1363. {
  1364. Type: autoscalingv2.ObjectMetricSourceType,
  1365. Object: &autoscalingv2.ObjectMetricSource{
  1366. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1367. APIVersion: "apps/v1",
  1368. Kind: "Deployment",
  1369. Name: "some-deployment",
  1370. },
  1371. Metric: autoscalingv2.MetricIdentifier{
  1372. Name: "qps",
  1373. },
  1374. Target: autoscalingv2.MetricTarget{
  1375. AverageValue: &targetAverageValue,
  1376. },
  1377. },
  1378. },
  1379. },
  1380. reportedLevels: []uint64{60000},
  1381. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1382. recommendations: []timestampedRecommendation{},
  1383. }
  1384. tc.runTest(t)
  1385. }
  1386. func TestScaleDownCMExternal(t *testing.T) {
  1387. tc := testCase{
  1388. minReplicas: 2,
  1389. maxReplicas: 6,
  1390. specReplicas: 5,
  1391. statusReplicas: 5,
  1392. expectedDesiredReplicas: 3,
  1393. metricsTarget: []autoscalingv2.MetricSpec{
  1394. {
  1395. Type: autoscalingv2.ExternalMetricSourceType,
  1396. External: &autoscalingv2.ExternalMetricSource{
  1397. Metric: autoscalingv2.MetricIdentifier{
  1398. Name: "qps",
  1399. Selector: &metav1.LabelSelector{},
  1400. },
  1401. Target: autoscalingv2.MetricTarget{
  1402. Value: resource.NewMilliQuantity(14400, resource.DecimalSI),
  1403. },
  1404. },
  1405. },
  1406. },
  1407. reportedLevels: []uint64{8600},
  1408. recommendations: []timestampedRecommendation{},
  1409. }
  1410. tc.runTest(t)
  1411. }
  1412. func TestScaleDownToZeroCMExternal(t *testing.T) {
  1413. tc := testCase{
  1414. minReplicas: 0,
  1415. maxReplicas: 6,
  1416. specReplicas: 5,
  1417. statusReplicas: 5,
  1418. expectedDesiredReplicas: 0,
  1419. metricsTarget: []autoscalingv2.MetricSpec{
  1420. {
  1421. Type: autoscalingv2.ExternalMetricSourceType,
  1422. External: &autoscalingv2.ExternalMetricSource{
  1423. Metric: autoscalingv2.MetricIdentifier{
  1424. Name: "qps",
  1425. Selector: &metav1.LabelSelector{},
  1426. },
  1427. Target: autoscalingv2.MetricTarget{
  1428. Value: resource.NewMilliQuantity(14400, resource.DecimalSI),
  1429. },
  1430. },
  1431. },
  1432. },
  1433. reportedLevels: []uint64{0},
  1434. recommendations: []timestampedRecommendation{},
  1435. }
  1436. tc.runTest(t)
  1437. }
  1438. func TestScaleDownPerPodCMExternal(t *testing.T) {
  1439. tc := testCase{
  1440. minReplicas: 2,
  1441. maxReplicas: 6,
  1442. specReplicas: 5,
  1443. statusReplicas: 5,
  1444. expectedDesiredReplicas: 3,
  1445. metricsTarget: []autoscalingv2.MetricSpec{
  1446. {
  1447. Type: autoscalingv2.ExternalMetricSourceType,
  1448. External: &autoscalingv2.ExternalMetricSource{
  1449. Metric: autoscalingv2.MetricIdentifier{
  1450. Name: "qps",
  1451. Selector: &metav1.LabelSelector{},
  1452. },
  1453. Target: autoscalingv2.MetricTarget{
  1454. AverageValue: resource.NewMilliQuantity(3000, resource.DecimalSI),
  1455. },
  1456. },
  1457. },
  1458. },
  1459. reportedLevels: []uint64{8600},
  1460. recommendations: []timestampedRecommendation{},
  1461. }
  1462. tc.runTest(t)
  1463. }
  1464. func TestScaleDownIncludeUnreadyPods(t *testing.T) {
  1465. tc := testCase{
  1466. minReplicas: 2,
  1467. maxReplicas: 6,
  1468. specReplicas: 5,
  1469. statusReplicas: 5,
  1470. expectedDesiredReplicas: 2,
  1471. CPUTarget: 50,
  1472. CPUCurrent: 30,
  1473. verifyCPUCurrent: true,
  1474. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1475. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1476. useMetricsAPI: true,
  1477. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1478. recommendations: []timestampedRecommendation{},
  1479. }
  1480. tc.runTest(t)
  1481. }
  1482. func TestScaleDownOneMetricInvalid(t *testing.T) {
  1483. tc := testCase{
  1484. minReplicas: 2,
  1485. maxReplicas: 6,
  1486. specReplicas: 5,
  1487. statusReplicas: 5,
  1488. expectedDesiredReplicas: 3,
  1489. CPUTarget: 50,
  1490. metricsTarget: []autoscalingv2.MetricSpec{
  1491. {
  1492. Type: "CheddarCheese",
  1493. },
  1494. },
  1495. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1496. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1497. useMetricsAPI: true,
  1498. recommendations: []timestampedRecommendation{},
  1499. }
  1500. tc.runTest(t)
  1501. }
  1502. func TestScaleDownOneMetricEmpty(t *testing.T) {
  1503. tc := testCase{
  1504. minReplicas: 2,
  1505. maxReplicas: 6,
  1506. specReplicas: 5,
  1507. statusReplicas: 5,
  1508. expectedDesiredReplicas: 3,
  1509. CPUTarget: 50,
  1510. metricsTarget: []autoscalingv2.MetricSpec{
  1511. {
  1512. Type: autoscalingv2.ExternalMetricSourceType,
  1513. External: &autoscalingv2.ExternalMetricSource{
  1514. Metric: autoscalingv2.MetricIdentifier{
  1515. Name: "qps",
  1516. Selector: &metav1.LabelSelector{},
  1517. },
  1518. Target: autoscalingv2.MetricTarget{
  1519. Type: autoscalingv2.AverageValueMetricType,
  1520. AverageValue: resource.NewMilliQuantity(1000, resource.DecimalSI),
  1521. },
  1522. },
  1523. },
  1524. },
  1525. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1526. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1527. useMetricsAPI: true,
  1528. recommendations: []timestampedRecommendation{},
  1529. }
  1530. _, _, _, testEMClient, _ := tc.prepareTestClient(t)
  1531. testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  1532. return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  1533. })
  1534. tc.testEMClient = testEMClient
  1535. tc.runTest(t)
  1536. }
  1537. func TestScaleDownIgnoreHotCpuPods(t *testing.T) {
  1538. tc := testCase{
  1539. minReplicas: 2,
  1540. maxReplicas: 6,
  1541. specReplicas: 5,
  1542. statusReplicas: 5,
  1543. expectedDesiredReplicas: 2,
  1544. CPUTarget: 50,
  1545. CPUCurrent: 30,
  1546. verifyCPUCurrent: true,
  1547. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1548. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1549. useMetricsAPI: true,
  1550. reportedPodStartTime: []metav1.Time{coolCpuCreationTime(), coolCpuCreationTime(), coolCpuCreationTime(), hotCpuCreationTime(), hotCpuCreationTime()},
  1551. recommendations: []timestampedRecommendation{},
  1552. }
  1553. tc.runTest(t)
  1554. }
  1555. func TestScaleDownIgnoresFailedPods(t *testing.T) {
  1556. tc := testCase{
  1557. minReplicas: 2,
  1558. maxReplicas: 6,
  1559. specReplicas: 5,
  1560. statusReplicas: 5,
  1561. expectedDesiredReplicas: 3,
  1562. CPUTarget: 50,
  1563. CPUCurrent: 28,
  1564. verifyCPUCurrent: true,
  1565. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1566. 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")},
  1567. useMetricsAPI: true,
  1568. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1569. reportedPodPhase: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodFailed, v1.PodFailed},
  1570. recommendations: []timestampedRecommendation{},
  1571. }
  1572. tc.runTest(t)
  1573. }
  1574. func TestScaleDownIgnoresDeletionPods(t *testing.T) {
  1575. tc := testCase{
  1576. minReplicas: 2,
  1577. maxReplicas: 6,
  1578. specReplicas: 5,
  1579. statusReplicas: 5,
  1580. expectedDesiredReplicas: 3,
  1581. CPUTarget: 50,
  1582. CPUCurrent: 28,
  1583. verifyCPUCurrent: true,
  1584. reportedLevels: []uint64{100, 300, 500, 250, 250},
  1585. 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")},
  1586. useMetricsAPI: true,
  1587. reportedPodReadiness: []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionTrue, v1.ConditionFalse, v1.ConditionFalse},
  1588. reportedPodPhase: []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning, v1.PodRunning},
  1589. reportedPodDeletionTimestamp: []bool{false, false, false, false, false, true, true},
  1590. recommendations: []timestampedRecommendation{},
  1591. }
  1592. tc.runTest(t)
  1593. }
  1594. func TestTolerance(t *testing.T) {
  1595. tc := testCase{
  1596. minReplicas: 1,
  1597. maxReplicas: 5,
  1598. specReplicas: 3,
  1599. statusReplicas: 3,
  1600. expectedDesiredReplicas: 3,
  1601. CPUTarget: 100,
  1602. reportedLevels: []uint64{1010, 1030, 1020},
  1603. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1604. useMetricsAPI: true,
  1605. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1606. Type: autoscalingv2.AbleToScale,
  1607. Status: v1.ConditionTrue,
  1608. Reason: "ReadyForNewScale",
  1609. }),
  1610. }
  1611. tc.runTest(t)
  1612. }
  1613. func TestToleranceCM(t *testing.T) {
  1614. averageValue := resource.MustParse("20.0")
  1615. tc := testCase{
  1616. minReplicas: 1,
  1617. maxReplicas: 5,
  1618. specReplicas: 3,
  1619. statusReplicas: 3,
  1620. expectedDesiredReplicas: 3,
  1621. metricsTarget: []autoscalingv2.MetricSpec{
  1622. {
  1623. Type: autoscalingv2.PodsMetricSourceType,
  1624. Pods: &autoscalingv2.PodsMetricSource{
  1625. Metric: autoscalingv2.MetricIdentifier{
  1626. Name: "qps",
  1627. },
  1628. Target: autoscalingv2.MetricTarget{
  1629. AverageValue: &averageValue,
  1630. },
  1631. },
  1632. },
  1633. },
  1634. reportedLevels: []uint64{20000, 20001, 21000},
  1635. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1636. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1637. Type: autoscalingv2.AbleToScale,
  1638. Status: v1.ConditionTrue,
  1639. Reason: "ReadyForNewScale",
  1640. }),
  1641. }
  1642. tc.runTest(t)
  1643. }
  1644. func TestToleranceCMObject(t *testing.T) {
  1645. targetValue := resource.MustParse("20.0")
  1646. tc := testCase{
  1647. minReplicas: 1,
  1648. maxReplicas: 5,
  1649. specReplicas: 3,
  1650. statusReplicas: 3,
  1651. expectedDesiredReplicas: 3,
  1652. metricsTarget: []autoscalingv2.MetricSpec{
  1653. {
  1654. Type: autoscalingv2.ObjectMetricSourceType,
  1655. Object: &autoscalingv2.ObjectMetricSource{
  1656. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1657. APIVersion: "apps/v1",
  1658. Kind: "Deployment",
  1659. Name: "some-deployment",
  1660. },
  1661. Metric: autoscalingv2.MetricIdentifier{
  1662. Name: "qps",
  1663. },
  1664. Target: autoscalingv2.MetricTarget{
  1665. Value: &targetValue,
  1666. },
  1667. },
  1668. },
  1669. },
  1670. reportedLevels: []uint64{20050},
  1671. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1672. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1673. Type: autoscalingv2.AbleToScale,
  1674. Status: v1.ConditionTrue,
  1675. Reason: "ReadyForNewScale",
  1676. }),
  1677. }
  1678. tc.runTest(t)
  1679. }
  1680. func TestToleranceCMExternal(t *testing.T) {
  1681. tc := testCase{
  1682. minReplicas: 2,
  1683. maxReplicas: 6,
  1684. specReplicas: 4,
  1685. statusReplicas: 4,
  1686. expectedDesiredReplicas: 4,
  1687. metricsTarget: []autoscalingv2.MetricSpec{
  1688. {
  1689. Type: autoscalingv2.ExternalMetricSourceType,
  1690. External: &autoscalingv2.ExternalMetricSource{
  1691. Metric: autoscalingv2.MetricIdentifier{
  1692. Name: "qps",
  1693. Selector: &metav1.LabelSelector{},
  1694. },
  1695. Target: autoscalingv2.MetricTarget{
  1696. Value: resource.NewMilliQuantity(8666, resource.DecimalSI),
  1697. },
  1698. },
  1699. },
  1700. },
  1701. reportedLevels: []uint64{8600},
  1702. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1703. Type: autoscalingv2.AbleToScale,
  1704. Status: v1.ConditionTrue,
  1705. Reason: "ReadyForNewScale",
  1706. }),
  1707. }
  1708. tc.runTest(t)
  1709. }
  1710. func TestTolerancePerPodCMObject(t *testing.T) {
  1711. tc := testCase{
  1712. minReplicas: 2,
  1713. maxReplicas: 6,
  1714. specReplicas: 4,
  1715. statusReplicas: 4,
  1716. expectedDesiredReplicas: 4,
  1717. metricsTarget: []autoscalingv2.MetricSpec{
  1718. {
  1719. Type: autoscalingv2.ObjectMetricSourceType,
  1720. Object: &autoscalingv2.ObjectMetricSource{
  1721. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  1722. APIVersion: "apps/v1",
  1723. Kind: "Deployment",
  1724. Name: "some-deployment",
  1725. },
  1726. Metric: autoscalingv2.MetricIdentifier{
  1727. Name: "qps",
  1728. Selector: &metav1.LabelSelector{},
  1729. },
  1730. Target: autoscalingv2.MetricTarget{
  1731. AverageValue: resource.NewMilliQuantity(2200, resource.DecimalSI),
  1732. },
  1733. },
  1734. },
  1735. },
  1736. reportedLevels: []uint64{8600},
  1737. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1738. Type: autoscalingv2.AbleToScale,
  1739. Status: v1.ConditionTrue,
  1740. Reason: "ReadyForNewScale",
  1741. }),
  1742. }
  1743. tc.runTest(t)
  1744. }
  1745. func TestTolerancePerPodCMExternal(t *testing.T) {
  1746. tc := testCase{
  1747. minReplicas: 2,
  1748. maxReplicas: 6,
  1749. specReplicas: 4,
  1750. statusReplicas: 4,
  1751. expectedDesiredReplicas: 4,
  1752. metricsTarget: []autoscalingv2.MetricSpec{
  1753. {
  1754. Type: autoscalingv2.ExternalMetricSourceType,
  1755. External: &autoscalingv2.ExternalMetricSource{
  1756. Metric: autoscalingv2.MetricIdentifier{
  1757. Name: "qps",
  1758. Selector: &metav1.LabelSelector{},
  1759. },
  1760. Target: autoscalingv2.MetricTarget{
  1761. AverageValue: resource.NewMilliQuantity(2200, resource.DecimalSI),
  1762. },
  1763. },
  1764. },
  1765. },
  1766. reportedLevels: []uint64{8600},
  1767. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1768. Type: autoscalingv2.AbleToScale,
  1769. Status: v1.ConditionTrue,
  1770. Reason: "ReadyForNewScale",
  1771. }),
  1772. }
  1773. tc.runTest(t)
  1774. }
  1775. func TestMinReplicas(t *testing.T) {
  1776. tc := testCase{
  1777. minReplicas: 2,
  1778. maxReplicas: 5,
  1779. specReplicas: 3,
  1780. statusReplicas: 3,
  1781. expectedDesiredReplicas: 2,
  1782. CPUTarget: 90,
  1783. reportedLevels: []uint64{10, 95, 10},
  1784. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1785. useMetricsAPI: true,
  1786. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1787. Type: autoscalingv2.ScalingLimited,
  1788. Status: v1.ConditionTrue,
  1789. Reason: "TooFewReplicas",
  1790. }),
  1791. recommendations: []timestampedRecommendation{},
  1792. }
  1793. tc.runTest(t)
  1794. }
  1795. func TestZeroMinReplicasDesiredZero(t *testing.T) {
  1796. tc := testCase{
  1797. minReplicas: 0,
  1798. maxReplicas: 5,
  1799. specReplicas: 3,
  1800. statusReplicas: 3,
  1801. expectedDesiredReplicas: 0,
  1802. CPUTarget: 90,
  1803. reportedLevels: []uint64{0, 0, 0},
  1804. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1805. useMetricsAPI: true,
  1806. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1807. Type: autoscalingv2.ScalingLimited,
  1808. Status: v1.ConditionFalse,
  1809. Reason: "DesiredWithinRange",
  1810. }),
  1811. recommendations: []timestampedRecommendation{},
  1812. }
  1813. tc.runTest(t)
  1814. }
  1815. func TestMinReplicasDesiredZero(t *testing.T) {
  1816. tc := testCase{
  1817. minReplicas: 2,
  1818. maxReplicas: 5,
  1819. specReplicas: 3,
  1820. statusReplicas: 3,
  1821. expectedDesiredReplicas: 2,
  1822. CPUTarget: 90,
  1823. reportedLevels: []uint64{0, 0, 0},
  1824. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1825. useMetricsAPI: true,
  1826. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1827. Type: autoscalingv2.ScalingLimited,
  1828. Status: v1.ConditionTrue,
  1829. Reason: "TooFewReplicas",
  1830. }),
  1831. recommendations: []timestampedRecommendation{},
  1832. }
  1833. tc.runTest(t)
  1834. }
  1835. func TestZeroReplicas(t *testing.T) {
  1836. tc := testCase{
  1837. minReplicas: 3,
  1838. maxReplicas: 5,
  1839. specReplicas: 0,
  1840. statusReplicas: 0,
  1841. expectedDesiredReplicas: 0,
  1842. CPUTarget: 90,
  1843. reportedLevels: []uint64{},
  1844. reportedCPURequests: []resource.Quantity{},
  1845. useMetricsAPI: true,
  1846. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1847. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1848. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "ScalingDisabled"},
  1849. },
  1850. }
  1851. tc.runTest(t)
  1852. }
  1853. func TestTooFewReplicas(t *testing.T) {
  1854. tc := testCase{
  1855. minReplicas: 3,
  1856. maxReplicas: 5,
  1857. specReplicas: 2,
  1858. statusReplicas: 2,
  1859. expectedDesiredReplicas: 3,
  1860. CPUTarget: 90,
  1861. reportedLevels: []uint64{},
  1862. reportedCPURequests: []resource.Quantity{},
  1863. useMetricsAPI: true,
  1864. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1865. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  1866. },
  1867. }
  1868. tc.runTest(t)
  1869. }
  1870. func TestTooManyReplicas(t *testing.T) {
  1871. tc := testCase{
  1872. minReplicas: 3,
  1873. maxReplicas: 5,
  1874. specReplicas: 10,
  1875. statusReplicas: 10,
  1876. expectedDesiredReplicas: 5,
  1877. CPUTarget: 90,
  1878. reportedLevels: []uint64{},
  1879. reportedCPURequests: []resource.Quantity{},
  1880. useMetricsAPI: true,
  1881. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1882. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  1883. },
  1884. }
  1885. tc.runTest(t)
  1886. }
  1887. func TestMaxReplicas(t *testing.T) {
  1888. tc := testCase{
  1889. minReplicas: 2,
  1890. maxReplicas: 5,
  1891. specReplicas: 3,
  1892. statusReplicas: 3,
  1893. expectedDesiredReplicas: 5,
  1894. CPUTarget: 90,
  1895. reportedLevels: []uint64{8000, 9500, 1000},
  1896. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  1897. useMetricsAPI: true,
  1898. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1899. Type: autoscalingv2.ScalingLimited,
  1900. Status: v1.ConditionTrue,
  1901. Reason: "TooManyReplicas",
  1902. }),
  1903. }
  1904. tc.runTest(t)
  1905. }
  1906. func TestSuperfluousMetrics(t *testing.T) {
  1907. tc := testCase{
  1908. minReplicas: 2,
  1909. maxReplicas: 6,
  1910. specReplicas: 4,
  1911. statusReplicas: 4,
  1912. expectedDesiredReplicas: 6,
  1913. CPUTarget: 100,
  1914. reportedLevels: []uint64{4000, 9500, 3000, 7000, 3200, 2000},
  1915. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1916. useMetricsAPI: true,
  1917. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  1918. Type: autoscalingv2.ScalingLimited,
  1919. Status: v1.ConditionTrue,
  1920. Reason: "TooManyReplicas",
  1921. }),
  1922. }
  1923. tc.runTest(t)
  1924. }
  1925. func TestMissingMetrics(t *testing.T) {
  1926. tc := testCase{
  1927. minReplicas: 2,
  1928. maxReplicas: 6,
  1929. specReplicas: 4,
  1930. statusReplicas: 4,
  1931. expectedDesiredReplicas: 3,
  1932. CPUTarget: 100,
  1933. reportedLevels: []uint64{400, 95},
  1934. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1935. useMetricsAPI: true,
  1936. recommendations: []timestampedRecommendation{},
  1937. }
  1938. tc.runTest(t)
  1939. }
  1940. func TestEmptyMetrics(t *testing.T) {
  1941. tc := testCase{
  1942. minReplicas: 2,
  1943. maxReplicas: 6,
  1944. specReplicas: 4,
  1945. statusReplicas: 4,
  1946. expectedDesiredReplicas: 4,
  1947. CPUTarget: 100,
  1948. reportedLevels: []uint64{},
  1949. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  1950. useMetricsAPI: true,
  1951. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1952. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1953. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetResourceMetric"},
  1954. },
  1955. }
  1956. tc.runTest(t)
  1957. }
  1958. func TestEmptyCPURequest(t *testing.T) {
  1959. tc := testCase{
  1960. minReplicas: 1,
  1961. maxReplicas: 5,
  1962. specReplicas: 1,
  1963. statusReplicas: 1,
  1964. expectedDesiredReplicas: 1,
  1965. CPUTarget: 100,
  1966. reportedLevels: []uint64{200},
  1967. reportedCPURequests: []resource.Quantity{},
  1968. useMetricsAPI: true,
  1969. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  1970. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  1971. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: "FailedGetResourceMetric"},
  1972. },
  1973. }
  1974. tc.runTest(t)
  1975. }
  1976. func TestEventCreated(t *testing.T) {
  1977. tc := testCase{
  1978. minReplicas: 1,
  1979. maxReplicas: 5,
  1980. specReplicas: 1,
  1981. statusReplicas: 1,
  1982. expectedDesiredReplicas: 2,
  1983. CPUTarget: 50,
  1984. reportedLevels: []uint64{200},
  1985. reportedCPURequests: []resource.Quantity{resource.MustParse("0.2")},
  1986. verifyEvents: true,
  1987. useMetricsAPI: true,
  1988. }
  1989. tc.runTest(t)
  1990. }
  1991. func TestEventNotCreated(t *testing.T) {
  1992. tc := testCase{
  1993. minReplicas: 1,
  1994. maxReplicas: 5,
  1995. specReplicas: 2,
  1996. statusReplicas: 2,
  1997. expectedDesiredReplicas: 2,
  1998. CPUTarget: 50,
  1999. reportedLevels: []uint64{200, 200},
  2000. reportedCPURequests: []resource.Quantity{resource.MustParse("0.4"), resource.MustParse("0.4")},
  2001. verifyEvents: true,
  2002. useMetricsAPI: true,
  2003. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2004. Type: autoscalingv2.AbleToScale,
  2005. Status: v1.ConditionTrue,
  2006. Reason: "ReadyForNewScale",
  2007. }),
  2008. }
  2009. tc.runTest(t)
  2010. }
  2011. func TestMissingReports(t *testing.T) {
  2012. tc := testCase{
  2013. minReplicas: 1,
  2014. maxReplicas: 5,
  2015. specReplicas: 4,
  2016. statusReplicas: 4,
  2017. expectedDesiredReplicas: 2,
  2018. CPUTarget: 50,
  2019. reportedLevels: []uint64{200},
  2020. reportedCPURequests: []resource.Quantity{resource.MustParse("0.2")},
  2021. useMetricsAPI: true,
  2022. recommendations: []timestampedRecommendation{},
  2023. }
  2024. tc.runTest(t)
  2025. }
  2026. func TestUpscaleCap(t *testing.T) {
  2027. tc := testCase{
  2028. minReplicas: 1,
  2029. maxReplicas: 100,
  2030. specReplicas: 3,
  2031. statusReplicas: 3,
  2032. scaleUpRules: generateScalingRules(0, 0, 700, 60, 0),
  2033. initialReplicas: 3,
  2034. expectedDesiredReplicas: 24,
  2035. CPUTarget: 10,
  2036. reportedLevels: []uint64{100, 200, 300},
  2037. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2038. useMetricsAPI: true,
  2039. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2040. Type: autoscalingv2.ScalingLimited,
  2041. Status: v1.ConditionTrue,
  2042. Reason: "ScaleUpLimit",
  2043. }),
  2044. }
  2045. tc.runTest(t)
  2046. }
  2047. func TestUpscaleCapGreaterThanMaxReplicas(t *testing.T) {
  2048. tc := testCase{
  2049. minReplicas: 1,
  2050. maxReplicas: 20,
  2051. specReplicas: 3,
  2052. statusReplicas: 3,
  2053. scaleUpRules: generateScalingRules(0, 0, 700, 60, 0),
  2054. initialReplicas: 3,
  2055. // expectedDesiredReplicas would be 24 without maxReplicas
  2056. expectedDesiredReplicas: 20,
  2057. CPUTarget: 10,
  2058. reportedLevels: []uint64{100, 200, 300},
  2059. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2060. useMetricsAPI: true,
  2061. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2062. Type: autoscalingv2.ScalingLimited,
  2063. Status: v1.ConditionTrue,
  2064. Reason: "TooManyReplicas",
  2065. }),
  2066. }
  2067. tc.runTest(t)
  2068. }
  2069. func TestMoreReplicasThanSpecNoScale(t *testing.T) {
  2070. tc := testCase{
  2071. minReplicas: 1,
  2072. maxReplicas: 8,
  2073. specReplicas: 4,
  2074. statusReplicas: 5, // Deployment update with 25% surge.
  2075. expectedDesiredReplicas: 4,
  2076. CPUTarget: 50,
  2077. reportedLevels: []uint64{500, 500, 500, 500, 500},
  2078. reportedCPURequests: []resource.Quantity{
  2079. resource.MustParse("1"),
  2080. resource.MustParse("1"),
  2081. resource.MustParse("1"),
  2082. resource.MustParse("1"),
  2083. resource.MustParse("1"),
  2084. },
  2085. useMetricsAPI: true,
  2086. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2087. Type: autoscalingv2.AbleToScale,
  2088. Status: v1.ConditionTrue,
  2089. Reason: "ReadyForNewScale",
  2090. }),
  2091. }
  2092. tc.runTest(t)
  2093. }
  2094. func TestConditionInvalidSelectorMissing(t *testing.T) {
  2095. tc := testCase{
  2096. minReplicas: 1,
  2097. maxReplicas: 100,
  2098. specReplicas: 3,
  2099. statusReplicas: 3,
  2100. expectedDesiredReplicas: 3,
  2101. CPUTarget: 10,
  2102. reportedLevels: []uint64{100, 200, 300},
  2103. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2104. useMetricsAPI: true,
  2105. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2106. {
  2107. Type: autoscalingv1.AbleToScale,
  2108. Status: v1.ConditionTrue,
  2109. Reason: "SucceededGetScale",
  2110. },
  2111. {
  2112. Type: autoscalingv1.ScalingActive,
  2113. Status: v1.ConditionFalse,
  2114. Reason: "InvalidSelector",
  2115. },
  2116. },
  2117. }
  2118. _, _, _, _, testScaleClient := tc.prepareTestClient(t)
  2119. tc.testScaleClient = testScaleClient
  2120. testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2121. obj := &autoscalingv1.Scale{
  2122. ObjectMeta: metav1.ObjectMeta{
  2123. Name: tc.resource.name,
  2124. },
  2125. Spec: autoscalingv1.ScaleSpec{
  2126. Replicas: tc.specReplicas,
  2127. },
  2128. Status: autoscalingv1.ScaleStatus{
  2129. Replicas: tc.specReplicas,
  2130. },
  2131. }
  2132. return true, obj, nil
  2133. })
  2134. tc.runTest(t)
  2135. }
  2136. func TestConditionInvalidSelectorUnparsable(t *testing.T) {
  2137. tc := testCase{
  2138. minReplicas: 1,
  2139. maxReplicas: 100,
  2140. specReplicas: 3,
  2141. statusReplicas: 3,
  2142. expectedDesiredReplicas: 3,
  2143. CPUTarget: 10,
  2144. reportedLevels: []uint64{100, 200, 300},
  2145. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2146. useMetricsAPI: true,
  2147. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2148. {
  2149. Type: autoscalingv1.AbleToScale,
  2150. Status: v1.ConditionTrue,
  2151. Reason: "SucceededGetScale",
  2152. },
  2153. {
  2154. Type: autoscalingv1.ScalingActive,
  2155. Status: v1.ConditionFalse,
  2156. Reason: "InvalidSelector",
  2157. },
  2158. },
  2159. }
  2160. _, _, _, _, testScaleClient := tc.prepareTestClient(t)
  2161. tc.testScaleClient = testScaleClient
  2162. testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2163. obj := &autoscalingv1.Scale{
  2164. ObjectMeta: metav1.ObjectMeta{
  2165. Name: tc.resource.name,
  2166. },
  2167. Spec: autoscalingv1.ScaleSpec{
  2168. Replicas: tc.specReplicas,
  2169. },
  2170. Status: autoscalingv1.ScaleStatus{
  2171. Replicas: tc.specReplicas,
  2172. Selector: "cheddar cheese",
  2173. },
  2174. }
  2175. return true, obj, nil
  2176. })
  2177. tc.runTest(t)
  2178. }
  2179. func TestConditionFailedGetMetrics(t *testing.T) {
  2180. targetValue := resource.MustParse("15.0")
  2181. averageValue := resource.MustParse("15.0")
  2182. metricsTargets := map[string][]autoscalingv2.MetricSpec{
  2183. "FailedGetResourceMetric": nil,
  2184. "FailedGetPodsMetric": {
  2185. {
  2186. Type: autoscalingv2.PodsMetricSourceType,
  2187. Pods: &autoscalingv2.PodsMetricSource{
  2188. Metric: autoscalingv2.MetricIdentifier{
  2189. Name: "qps",
  2190. },
  2191. Target: autoscalingv2.MetricTarget{
  2192. AverageValue: &averageValue,
  2193. },
  2194. },
  2195. },
  2196. },
  2197. "FailedGetObjectMetric": {
  2198. {
  2199. Type: autoscalingv2.ObjectMetricSourceType,
  2200. Object: &autoscalingv2.ObjectMetricSource{
  2201. DescribedObject: autoscalingv2.CrossVersionObjectReference{
  2202. APIVersion: "apps/v1",
  2203. Kind: "Deployment",
  2204. Name: "some-deployment",
  2205. },
  2206. Metric: autoscalingv2.MetricIdentifier{
  2207. Name: "qps",
  2208. },
  2209. Target: autoscalingv2.MetricTarget{
  2210. Value: &targetValue,
  2211. },
  2212. },
  2213. },
  2214. },
  2215. "FailedGetExternalMetric": {
  2216. {
  2217. Type: autoscalingv2.ExternalMetricSourceType,
  2218. External: &autoscalingv2.ExternalMetricSource{
  2219. Metric: autoscalingv2.MetricIdentifier{
  2220. Name: "qps",
  2221. Selector: &metav1.LabelSelector{},
  2222. },
  2223. Target: autoscalingv2.MetricTarget{
  2224. Value: resource.NewMilliQuantity(300, resource.DecimalSI),
  2225. },
  2226. },
  2227. },
  2228. },
  2229. }
  2230. for reason, specs := range metricsTargets {
  2231. tc := testCase{
  2232. minReplicas: 1,
  2233. maxReplicas: 100,
  2234. specReplicas: 3,
  2235. statusReplicas: 3,
  2236. expectedDesiredReplicas: 3,
  2237. CPUTarget: 10,
  2238. reportedLevels: []uint64{100, 200, 300},
  2239. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2240. useMetricsAPI: true,
  2241. }
  2242. _, testMetricsClient, testCMClient, testEMClient, _ := tc.prepareTestClient(t)
  2243. tc.testMetricsClient = testMetricsClient
  2244. tc.testCMClient = testCMClient
  2245. tc.testEMClient = testEMClient
  2246. testMetricsClient.PrependReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2247. return true, &metricsapi.PodMetricsList{}, fmt.Errorf("something went wrong")
  2248. })
  2249. testCMClient.PrependReactor("get", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2250. return true, &cmapi.MetricValueList{}, fmt.Errorf("something went wrong")
  2251. })
  2252. testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2253. return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  2254. })
  2255. tc.expectedConditions = []autoscalingv1.HorizontalPodAutoscalerCondition{
  2256. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededGetScale"},
  2257. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionFalse, Reason: reason},
  2258. }
  2259. if specs != nil {
  2260. tc.CPUTarget = 0
  2261. } else {
  2262. tc.CPUTarget = 10
  2263. }
  2264. tc.metricsTarget = specs
  2265. tc.runTest(t)
  2266. }
  2267. }
  2268. func TestConditionInvalidSourceType(t *testing.T) {
  2269. tc := testCase{
  2270. minReplicas: 2,
  2271. maxReplicas: 6,
  2272. specReplicas: 3,
  2273. statusReplicas: 3,
  2274. expectedDesiredReplicas: 3,
  2275. CPUTarget: 0,
  2276. metricsTarget: []autoscalingv2.MetricSpec{
  2277. {
  2278. Type: "CheddarCheese",
  2279. },
  2280. },
  2281. reportedLevels: []uint64{20000},
  2282. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2283. {
  2284. Type: autoscalingv1.AbleToScale,
  2285. Status: v1.ConditionTrue,
  2286. Reason: "SucceededGetScale",
  2287. },
  2288. {
  2289. Type: autoscalingv1.ScalingActive,
  2290. Status: v1.ConditionFalse,
  2291. Reason: "InvalidMetricSourceType",
  2292. },
  2293. },
  2294. }
  2295. tc.runTest(t)
  2296. }
  2297. func TestConditionFailedGetScale(t *testing.T) {
  2298. tc := testCase{
  2299. minReplicas: 1,
  2300. maxReplicas: 100,
  2301. specReplicas: 3,
  2302. statusReplicas: 3,
  2303. expectedDesiredReplicas: 3,
  2304. CPUTarget: 10,
  2305. reportedLevels: []uint64{100, 200, 300},
  2306. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2307. useMetricsAPI: true,
  2308. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2309. {
  2310. Type: autoscalingv1.AbleToScale,
  2311. Status: v1.ConditionFalse,
  2312. Reason: "FailedGetScale",
  2313. },
  2314. },
  2315. }
  2316. _, _, _, _, testScaleClient := tc.prepareTestClient(t)
  2317. tc.testScaleClient = testScaleClient
  2318. testScaleClient.PrependReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2319. return true, &autoscalingv1.Scale{}, fmt.Errorf("something went wrong")
  2320. })
  2321. tc.runTest(t)
  2322. }
  2323. func TestConditionFailedUpdateScale(t *testing.T) {
  2324. tc := testCase{
  2325. minReplicas: 1,
  2326. maxReplicas: 5,
  2327. specReplicas: 3,
  2328. statusReplicas: 3,
  2329. expectedDesiredReplicas: 3,
  2330. CPUTarget: 100,
  2331. reportedLevels: []uint64{150, 150, 150},
  2332. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2333. useMetricsAPI: true,
  2334. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2335. Type: autoscalingv2.AbleToScale,
  2336. Status: v1.ConditionFalse,
  2337. Reason: "FailedUpdateScale",
  2338. }),
  2339. }
  2340. _, _, _, _, testScaleClient := tc.prepareTestClient(t)
  2341. tc.testScaleClient = testScaleClient
  2342. testScaleClient.PrependReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2343. return true, &autoscalingv1.Scale{}, fmt.Errorf("something went wrong")
  2344. })
  2345. tc.runTest(t)
  2346. }
  2347. func NoTestBackoffUpscale(t *testing.T) {
  2348. time := metav1.Time{Time: time.Now()}
  2349. tc := testCase{
  2350. minReplicas: 1,
  2351. maxReplicas: 5,
  2352. specReplicas: 3,
  2353. statusReplicas: 3,
  2354. expectedDesiredReplicas: 3,
  2355. CPUTarget: 100,
  2356. reportedLevels: []uint64{150, 150, 150},
  2357. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2358. useMetricsAPI: true,
  2359. lastScaleTime: &time,
  2360. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2361. Type: autoscalingv2.AbleToScale,
  2362. Status: v1.ConditionTrue,
  2363. Reason: "ReadyForNewScale",
  2364. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2365. Type: autoscalingv2.AbleToScale,
  2366. Status: v1.ConditionTrue,
  2367. Reason: "SucceededRescale",
  2368. }),
  2369. }
  2370. tc.runTest(t)
  2371. }
  2372. func TestNoBackoffUpscaleCM(t *testing.T) {
  2373. averageValue := resource.MustParse("15.0")
  2374. time := metav1.Time{Time: time.Now()}
  2375. tc := testCase{
  2376. minReplicas: 1,
  2377. maxReplicas: 5,
  2378. specReplicas: 3,
  2379. statusReplicas: 3,
  2380. expectedDesiredReplicas: 4,
  2381. CPUTarget: 0,
  2382. metricsTarget: []autoscalingv2.MetricSpec{
  2383. {
  2384. Type: autoscalingv2.PodsMetricSourceType,
  2385. Pods: &autoscalingv2.PodsMetricSource{
  2386. Metric: autoscalingv2.MetricIdentifier{
  2387. Name: "qps",
  2388. },
  2389. Target: autoscalingv2.MetricTarget{
  2390. AverageValue: &averageValue,
  2391. },
  2392. },
  2393. },
  2394. },
  2395. reportedLevels: []uint64{20000, 10000, 30000},
  2396. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2397. //useMetricsAPI: true,
  2398. lastScaleTime: &time,
  2399. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2400. Type: autoscalingv2.AbleToScale,
  2401. Status: v1.ConditionTrue,
  2402. Reason: "ReadyForNewScale",
  2403. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2404. Type: autoscalingv2.AbleToScale,
  2405. Status: v1.ConditionTrue,
  2406. Reason: "SucceededRescale",
  2407. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2408. Type: autoscalingv2.ScalingLimited,
  2409. Status: v1.ConditionFalse,
  2410. Reason: "DesiredWithinRange",
  2411. }),
  2412. }
  2413. tc.runTest(t)
  2414. }
  2415. func TestNoBackoffUpscaleCMNoBackoffCpu(t *testing.T) {
  2416. averageValue := resource.MustParse("15.0")
  2417. time := metav1.Time{Time: time.Now()}
  2418. tc := testCase{
  2419. minReplicas: 1,
  2420. maxReplicas: 5,
  2421. specReplicas: 3,
  2422. statusReplicas: 3,
  2423. expectedDesiredReplicas: 5,
  2424. CPUTarget: 10,
  2425. metricsTarget: []autoscalingv2.MetricSpec{
  2426. {
  2427. Type: autoscalingv2.PodsMetricSourceType,
  2428. Pods: &autoscalingv2.PodsMetricSource{
  2429. Metric: autoscalingv2.MetricIdentifier{
  2430. Name: "qps",
  2431. },
  2432. Target: autoscalingv2.MetricTarget{
  2433. AverageValue: &averageValue,
  2434. },
  2435. },
  2436. },
  2437. },
  2438. reportedLevels: []uint64{20000, 10000, 30000},
  2439. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2440. useMetricsAPI: true,
  2441. lastScaleTime: &time,
  2442. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2443. Type: autoscalingv2.AbleToScale,
  2444. Status: v1.ConditionTrue,
  2445. Reason: "ReadyForNewScale",
  2446. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2447. Type: autoscalingv2.AbleToScale,
  2448. Status: v1.ConditionTrue,
  2449. Reason: "SucceededRescale",
  2450. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2451. Type: autoscalingv2.ScalingLimited,
  2452. Status: v1.ConditionTrue,
  2453. Reason: "TooManyReplicas",
  2454. }),
  2455. }
  2456. tc.runTest(t)
  2457. }
  2458. func TestStabilizeDownscale(t *testing.T) {
  2459. tc := testCase{
  2460. minReplicas: 1,
  2461. maxReplicas: 5,
  2462. specReplicas: 4,
  2463. statusReplicas: 4,
  2464. expectedDesiredReplicas: 4,
  2465. CPUTarget: 100,
  2466. reportedLevels: []uint64{50, 50, 50},
  2467. reportedCPURequests: []resource.Quantity{resource.MustParse("0.1"), resource.MustParse("0.1"), resource.MustParse("0.1")},
  2468. useMetricsAPI: true,
  2469. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2470. Type: autoscalingv2.AbleToScale,
  2471. Status: v1.ConditionTrue,
  2472. Reason: "ReadyForNewScale",
  2473. }, autoscalingv2.HorizontalPodAutoscalerCondition{
  2474. Type: autoscalingv2.AbleToScale,
  2475. Status: v1.ConditionTrue,
  2476. Reason: "ScaleDownStabilized",
  2477. }),
  2478. recommendations: []timestampedRecommendation{
  2479. {10, time.Now().Add(-10 * time.Minute)},
  2480. {4, time.Now().Add(-1 * time.Minute)},
  2481. },
  2482. }
  2483. tc.runTest(t)
  2484. }
  2485. // TestComputedToleranceAlgImplementation is a regression test which
  2486. // back-calculates a minimal percentage for downscaling based on a small percentage
  2487. // increase in pod utilization which is calibrated against the tolerance value.
  2488. func TestComputedToleranceAlgImplementation(t *testing.T) {
  2489. startPods := int32(10)
  2490. // 150 mCPU per pod.
  2491. totalUsedCPUOfAllPods := uint64(startPods * 150)
  2492. // Each pod starts out asking for 2X what is really needed.
  2493. // This means we will have a 50% ratio of used/requested
  2494. totalRequestedCPUOfAllPods := int32(2 * totalUsedCPUOfAllPods)
  2495. requestedToUsed := float64(totalRequestedCPUOfAllPods / int32(totalUsedCPUOfAllPods))
  2496. // Spread the amount we ask over 10 pods. We can add some jitter later in reportedLevels.
  2497. perPodRequested := totalRequestedCPUOfAllPods / startPods
  2498. // Force a minimal scaling event by satisfying (tolerance < 1 - resourcesUsedRatio).
  2499. target := math.Abs(1/(requestedToUsed*(1-defaultTestingTolerance))) + .01
  2500. finalCPUPercentTarget := int32(target * 100)
  2501. resourcesUsedRatio := float64(totalUsedCPUOfAllPods) / float64(float64(totalRequestedCPUOfAllPods)*target)
  2502. // i.e. .60 * 20 -> scaled down expectation.
  2503. finalPods := int32(math.Ceil(resourcesUsedRatio * float64(startPods)))
  2504. // To breach tolerance we will create a utilization ratio difference of tolerance to usageRatioToleranceValue)
  2505. tc1 := testCase{
  2506. minReplicas: 0,
  2507. maxReplicas: 1000,
  2508. specReplicas: startPods,
  2509. statusReplicas: startPods,
  2510. expectedDesiredReplicas: finalPods,
  2511. CPUTarget: finalCPUPercentTarget,
  2512. reportedLevels: []uint64{
  2513. totalUsedCPUOfAllPods / 10,
  2514. totalUsedCPUOfAllPods / 10,
  2515. totalUsedCPUOfAllPods / 10,
  2516. totalUsedCPUOfAllPods / 10,
  2517. totalUsedCPUOfAllPods / 10,
  2518. totalUsedCPUOfAllPods / 10,
  2519. totalUsedCPUOfAllPods / 10,
  2520. totalUsedCPUOfAllPods / 10,
  2521. totalUsedCPUOfAllPods / 10,
  2522. totalUsedCPUOfAllPods / 10,
  2523. },
  2524. reportedCPURequests: []resource.Quantity{
  2525. resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"),
  2526. resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"),
  2527. resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"),
  2528. resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"),
  2529. resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"),
  2530. resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"),
  2531. resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"),
  2532. resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"),
  2533. resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  2534. resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  2535. },
  2536. useMetricsAPI: true,
  2537. recommendations: []timestampedRecommendation{},
  2538. }
  2539. tc1.runTest(t)
  2540. target = math.Abs(1/(requestedToUsed*(1-defaultTestingTolerance))) + .004
  2541. finalCPUPercentTarget = int32(target * 100)
  2542. tc2 := testCase{
  2543. minReplicas: 0,
  2544. maxReplicas: 1000,
  2545. specReplicas: startPods,
  2546. statusReplicas: startPods,
  2547. expectedDesiredReplicas: startPods,
  2548. CPUTarget: finalCPUPercentTarget,
  2549. reportedLevels: []uint64{
  2550. totalUsedCPUOfAllPods / 10,
  2551. totalUsedCPUOfAllPods / 10,
  2552. totalUsedCPUOfAllPods / 10,
  2553. totalUsedCPUOfAllPods / 10,
  2554. totalUsedCPUOfAllPods / 10,
  2555. totalUsedCPUOfAllPods / 10,
  2556. totalUsedCPUOfAllPods / 10,
  2557. totalUsedCPUOfAllPods / 10,
  2558. totalUsedCPUOfAllPods / 10,
  2559. totalUsedCPUOfAllPods / 10,
  2560. },
  2561. reportedCPURequests: []resource.Quantity{
  2562. resource.MustParse(fmt.Sprint(perPodRequested+100) + "m"),
  2563. resource.MustParse(fmt.Sprint(perPodRequested-100) + "m"),
  2564. resource.MustParse(fmt.Sprint(perPodRequested+10) + "m"),
  2565. resource.MustParse(fmt.Sprint(perPodRequested-10) + "m"),
  2566. resource.MustParse(fmt.Sprint(perPodRequested+2) + "m"),
  2567. resource.MustParse(fmt.Sprint(perPodRequested-2) + "m"),
  2568. resource.MustParse(fmt.Sprint(perPodRequested+1) + "m"),
  2569. resource.MustParse(fmt.Sprint(perPodRequested-1) + "m"),
  2570. resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  2571. resource.MustParse(fmt.Sprint(perPodRequested) + "m"),
  2572. },
  2573. useMetricsAPI: true,
  2574. recommendations: []timestampedRecommendation{},
  2575. expectedConditions: statusOkWithOverrides(autoscalingv2.HorizontalPodAutoscalerCondition{
  2576. Type: autoscalingv2.AbleToScale,
  2577. Status: v1.ConditionTrue,
  2578. Reason: "ReadyForNewScale",
  2579. }),
  2580. }
  2581. tc2.runTest(t)
  2582. }
  2583. func TestScaleUpRCImmediately(t *testing.T) {
  2584. time := metav1.Time{Time: time.Now()}
  2585. tc := testCase{
  2586. minReplicas: 2,
  2587. maxReplicas: 6,
  2588. specReplicas: 1,
  2589. statusReplicas: 1,
  2590. expectedDesiredReplicas: 2,
  2591. verifyCPUCurrent: false,
  2592. reportedLevels: []uint64{0, 0, 0, 0},
  2593. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2594. useMetricsAPI: true,
  2595. lastScaleTime: &time,
  2596. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2597. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  2598. },
  2599. }
  2600. tc.runTest(t)
  2601. }
  2602. func TestScaleDownRCImmediately(t *testing.T) {
  2603. time := metav1.Time{Time: time.Now()}
  2604. tc := testCase{
  2605. minReplicas: 2,
  2606. maxReplicas: 5,
  2607. specReplicas: 6,
  2608. statusReplicas: 6,
  2609. expectedDesiredReplicas: 5,
  2610. CPUTarget: 50,
  2611. reportedLevels: []uint64{8000, 9500, 1000},
  2612. reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")},
  2613. useMetricsAPI: true,
  2614. lastScaleTime: &time,
  2615. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  2616. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "SucceededRescale"},
  2617. },
  2618. }
  2619. tc.runTest(t)
  2620. }
  2621. func TestAvoidUncessaryUpdates(t *testing.T) {
  2622. now := metav1.Time{Time: time.Now().Add(-time.Hour)}
  2623. tc := testCase{
  2624. minReplicas: 2,
  2625. maxReplicas: 6,
  2626. specReplicas: 2,
  2627. statusReplicas: 2,
  2628. expectedDesiredReplicas: 2,
  2629. CPUTarget: 30,
  2630. CPUCurrent: 40,
  2631. verifyCPUCurrent: true,
  2632. reportedLevels: []uint64{400, 500, 700},
  2633. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  2634. reportedPodStartTime: []metav1.Time{coolCpuCreationTime(), hotCpuCreationTime(), hotCpuCreationTime()},
  2635. useMetricsAPI: true,
  2636. lastScaleTime: &now,
  2637. recommendations: []timestampedRecommendation{},
  2638. }
  2639. testClient, _, _, _, _ := tc.prepareTestClient(t)
  2640. tc.testClient = testClient
  2641. testClient.PrependReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2642. tc.Lock()
  2643. defer tc.Unlock()
  2644. // fake out the verification logic and mark that we're done processing
  2645. go func() {
  2646. // wait a tick and then mark that we're finished (otherwise, we have no
  2647. // way to indicate that we're finished, because the function decides not to do anything)
  2648. time.Sleep(1 * time.Second)
  2649. tc.Lock()
  2650. tc.statusUpdated = true
  2651. tc.Unlock()
  2652. tc.processed <- "test-hpa"
  2653. }()
  2654. quantity := resource.MustParse("400m")
  2655. obj := &autoscalingv2.HorizontalPodAutoscalerList{
  2656. Items: []autoscalingv2.HorizontalPodAutoscaler{
  2657. {
  2658. ObjectMeta: metav1.ObjectMeta{
  2659. Name: "test-hpa",
  2660. Namespace: "test-namespace",
  2661. SelfLink: "experimental/v1/namespaces/test-namespace/horizontalpodautoscalers/test-hpa",
  2662. },
  2663. Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
  2664. ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
  2665. Kind: "ReplicationController",
  2666. Name: "test-rc",
  2667. APIVersion: "v1",
  2668. },
  2669. MinReplicas: &tc.minReplicas,
  2670. MaxReplicas: tc.maxReplicas,
  2671. },
  2672. Status: autoscalingv2.HorizontalPodAutoscalerStatus{
  2673. CurrentReplicas: tc.specReplicas,
  2674. DesiredReplicas: tc.specReplicas,
  2675. LastScaleTime: tc.lastScaleTime,
  2676. CurrentMetrics: []autoscalingv2.MetricStatus{
  2677. {
  2678. Type: autoscalingv2.ResourceMetricSourceType,
  2679. Resource: &autoscalingv2.ResourceMetricStatus{
  2680. Name: v1.ResourceCPU,
  2681. Current: autoscalingv2.MetricValueStatus{
  2682. AverageValue: &quantity,
  2683. AverageUtilization: &tc.CPUCurrent,
  2684. },
  2685. },
  2686. },
  2687. },
  2688. Conditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
  2689. {
  2690. Type: autoscalingv2.AbleToScale,
  2691. Status: v1.ConditionTrue,
  2692. LastTransitionTime: *tc.lastScaleTime,
  2693. Reason: "ReadyForNewScale",
  2694. Message: "recommended size matches current size",
  2695. },
  2696. {
  2697. Type: autoscalingv2.ScalingActive,
  2698. Status: v1.ConditionTrue,
  2699. LastTransitionTime: *tc.lastScaleTime,
  2700. Reason: "ValidMetricFound",
  2701. Message: "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)",
  2702. },
  2703. {
  2704. Type: autoscalingv2.ScalingLimited,
  2705. Status: v1.ConditionTrue,
  2706. LastTransitionTime: *tc.lastScaleTime,
  2707. Reason: "TooFewReplicas",
  2708. Message: "the desired replica count is less than the minimum replica count",
  2709. },
  2710. },
  2711. },
  2712. },
  2713. },
  2714. }
  2715. // and... convert to autoscaling v1 to return the right type
  2716. objv1, err := unsafeConvertToVersionVia(obj, autoscalingv1.SchemeGroupVersion)
  2717. if err != nil {
  2718. return true, nil, err
  2719. }
  2720. return true, objv1, nil
  2721. })
  2722. testClient.PrependReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  2723. assert.Fail(t, "should not have attempted to update the HPA when nothing changed")
  2724. // mark that we've processed this HPA
  2725. tc.processed <- ""
  2726. return true, nil, fmt.Errorf("unexpected call")
  2727. })
  2728. controller, informerFactory := tc.setupController(t)
  2729. tc.runTestWithController(t, controller, informerFactory)
  2730. }
  2731. func TestConvertDesiredReplicasWithRules(t *testing.T) {
  2732. conversionTestCases := []struct {
  2733. currentReplicas int32
  2734. expectedDesiredReplicas int32
  2735. hpaMinReplicas int32
  2736. hpaMaxReplicas int32
  2737. expectedConvertedDesiredReplicas int32
  2738. expectedCondition string
  2739. annotation string
  2740. }{
  2741. {
  2742. currentReplicas: 5,
  2743. expectedDesiredReplicas: 7,
  2744. hpaMinReplicas: 3,
  2745. hpaMaxReplicas: 8,
  2746. expectedConvertedDesiredReplicas: 7,
  2747. expectedCondition: "DesiredWithinRange",
  2748. annotation: "prenormalized desired replicas within range",
  2749. },
  2750. {
  2751. currentReplicas: 3,
  2752. expectedDesiredReplicas: 1,
  2753. hpaMinReplicas: 2,
  2754. hpaMaxReplicas: 8,
  2755. expectedConvertedDesiredReplicas: 2,
  2756. expectedCondition: "TooFewReplicas",
  2757. annotation: "prenormalized desired replicas < minReplicas",
  2758. },
  2759. {
  2760. currentReplicas: 1,
  2761. expectedDesiredReplicas: 0,
  2762. hpaMinReplicas: 0,
  2763. hpaMaxReplicas: 10,
  2764. expectedConvertedDesiredReplicas: 0,
  2765. expectedCondition: "DesiredWithinRange",
  2766. annotation: "prenormalized desired zeroed replicas within range",
  2767. },
  2768. {
  2769. currentReplicas: 20,
  2770. expectedDesiredReplicas: 1000,
  2771. hpaMinReplicas: 1,
  2772. hpaMaxReplicas: 10,
  2773. expectedConvertedDesiredReplicas: 10,
  2774. expectedCondition: "TooManyReplicas",
  2775. annotation: "maxReplicas is the limit because maxReplicas < scaleUpLimit",
  2776. },
  2777. {
  2778. currentReplicas: 3,
  2779. expectedDesiredReplicas: 1000,
  2780. hpaMinReplicas: 1,
  2781. hpaMaxReplicas: 2000,
  2782. expectedConvertedDesiredReplicas: calculateScaleUpLimit(3),
  2783. expectedCondition: "ScaleUpLimit",
  2784. annotation: "scaleUpLimit is the limit because scaleUpLimit < maxReplicas",
  2785. },
  2786. }
  2787. for _, ctc := range conversionTestCases {
  2788. t.Run(ctc.annotation, func(t *testing.T) {
  2789. actualConvertedDesiredReplicas, actualCondition, _ := convertDesiredReplicasWithRules(
  2790. ctc.currentReplicas, ctc.expectedDesiredReplicas, ctc.hpaMinReplicas, ctc.hpaMaxReplicas,
  2791. )
  2792. assert.Equal(t, ctc.expectedConvertedDesiredReplicas, actualConvertedDesiredReplicas, ctc.annotation)
  2793. assert.Equal(t, ctc.expectedCondition, actualCondition, ctc.annotation)
  2794. })
  2795. }
  2796. }
  2797. func generateScalingRules(pods, podsPeriod, percent, percentPeriod, stabilizationWindow int32) *autoscalingv2.HPAScalingRules {
  2798. policy := autoscalingv2.MaxPolicySelect
  2799. directionBehavior := autoscalingv2.HPAScalingRules{
  2800. StabilizationWindowSeconds: utilpointer.Int32Ptr(stabilizationWindow),
  2801. SelectPolicy: &policy,
  2802. }
  2803. if pods != 0 {
  2804. directionBehavior.Policies = append(directionBehavior.Policies,
  2805. autoscalingv2.HPAScalingPolicy{Type: autoscalingv2.PodsScalingPolicy, Value: pods, PeriodSeconds: podsPeriod})
  2806. }
  2807. if percent != 0 {
  2808. directionBehavior.Policies = append(directionBehavior.Policies,
  2809. autoscalingv2.HPAScalingPolicy{Type: autoscalingv2.PercentScalingPolicy, Value: percent, PeriodSeconds: percentPeriod})
  2810. }
  2811. return &directionBehavior
  2812. }
  2813. // generateEventsUniformDistribution generates events that uniformly spread in the time window
  2814. // time.Now()-periodSeconds ; time.Now()
  2815. // It split the time window into several segments (by the number of events) and put the event in the center of the segment
  2816. // it is needed if you want to create events for several policies (to check how "outdated" flag is set).
  2817. // E.g. generateEventsUniformDistribution([]int{1,2,3,4}, 120) will spread events uniformly for the last 120 seconds:
  2818. //
  2819. // 1 2 3 4
  2820. // -----------------------------------------------
  2821. // ^ ^ ^ ^ ^
  2822. // -120s -90s -60s -30s now()
  2823. // And we can safely have two different stabilizationWindows:
  2824. // - 60s (guaranteed to have last half of events)
  2825. // - 120s (guaranteed to have all events)
  2826. func generateEventsUniformDistribution(rawEvents []int, periodSeconds int) []timestampedScaleEvent {
  2827. events := make([]timestampedScaleEvent, len(rawEvents))
  2828. segmentDuration := float64(periodSeconds) / float64(len(rawEvents))
  2829. for idx, event := range rawEvents {
  2830. segmentBoundary := time.Duration(float64(periodSeconds) - segmentDuration*float64(idx+1) + segmentDuration/float64(2))
  2831. events[idx] = timestampedScaleEvent{
  2832. replicaChange: int32(event),
  2833. timestamp: time.Now().Add(-time.Second * segmentBoundary),
  2834. }
  2835. }
  2836. return events
  2837. }
  2838. func TestNormalizeDesiredReplicas(t *testing.T) {
  2839. tests := []struct {
  2840. name string
  2841. key string
  2842. recommendations []timestampedRecommendation
  2843. prenormalizedDesiredReplicas int32
  2844. expectedStabilizedReplicas int32
  2845. expectedLogLength int
  2846. }{
  2847. {
  2848. "empty log",
  2849. "",
  2850. []timestampedRecommendation{},
  2851. 5,
  2852. 5,
  2853. 1,
  2854. },
  2855. {
  2856. "stabilize",
  2857. "",
  2858. []timestampedRecommendation{
  2859. {4, time.Now().Add(-2 * time.Minute)},
  2860. {5, time.Now().Add(-1 * time.Minute)},
  2861. },
  2862. 3,
  2863. 5,
  2864. 3,
  2865. },
  2866. {
  2867. "no stabilize",
  2868. "",
  2869. []timestampedRecommendation{
  2870. {1, time.Now().Add(-2 * time.Minute)},
  2871. {2, time.Now().Add(-1 * time.Minute)},
  2872. },
  2873. 3,
  2874. 3,
  2875. 3,
  2876. },
  2877. {
  2878. "no stabilize - old recommendations",
  2879. "",
  2880. []timestampedRecommendation{
  2881. {10, time.Now().Add(-10 * time.Minute)},
  2882. {9, time.Now().Add(-9 * time.Minute)},
  2883. },
  2884. 3,
  2885. 3,
  2886. 2,
  2887. },
  2888. {
  2889. "stabilize - old recommendations",
  2890. "",
  2891. []timestampedRecommendation{
  2892. {10, time.Now().Add(-10 * time.Minute)},
  2893. {4, time.Now().Add(-1 * time.Minute)},
  2894. {5, time.Now().Add(-2 * time.Minute)},
  2895. {9, time.Now().Add(-9 * time.Minute)},
  2896. },
  2897. 3,
  2898. 5,
  2899. 4,
  2900. },
  2901. }
  2902. for _, tc := range tests {
  2903. hc := HorizontalController{
  2904. downscaleStabilisationWindow: 5 * time.Minute,
  2905. recommendations: map[string][]timestampedRecommendation{
  2906. tc.key: tc.recommendations,
  2907. },
  2908. }
  2909. r := hc.stabilizeRecommendation(tc.key, tc.prenormalizedDesiredReplicas)
  2910. if r != tc.expectedStabilizedReplicas {
  2911. t.Errorf("[%s] got %d stabilized replicas, expected %d", tc.name, r, tc.expectedStabilizedReplicas)
  2912. }
  2913. if len(hc.recommendations[tc.key]) != tc.expectedLogLength {
  2914. t.Errorf("[%s] after stabilization recommendations log has %d entries, expected %d", tc.name, len(hc.recommendations[tc.key]), tc.expectedLogLength)
  2915. }
  2916. }
  2917. }
  2918. func TestScalingWithRules(t *testing.T) {
  2919. type TestCase struct {
  2920. name string
  2921. key string
  2922. // controller arguments
  2923. scaleUpEvents []timestampedScaleEvent
  2924. scaleDownEvents []timestampedScaleEvent
  2925. // HPA Spec arguments
  2926. specMinReplicas int32
  2927. specMaxReplicas int32
  2928. scaleUpRules *autoscalingv2.HPAScalingRules
  2929. scaleDownRules *autoscalingv2.HPAScalingRules
  2930. // external world state
  2931. currentReplicas int32
  2932. prenormalizedDesiredReplicas int32
  2933. // test expected result
  2934. expectedReplicas int32
  2935. expectedCondition string
  2936. testThis bool
  2937. }
  2938. tests := []TestCase{
  2939. {
  2940. currentReplicas: 5,
  2941. prenormalizedDesiredReplicas: 7,
  2942. specMinReplicas: 3,
  2943. specMaxReplicas: 8,
  2944. expectedReplicas: 7,
  2945. expectedCondition: "DesiredWithinRange",
  2946. name: "prenormalized desired replicas within range",
  2947. },
  2948. {
  2949. currentReplicas: 3,
  2950. prenormalizedDesiredReplicas: 1,
  2951. specMinReplicas: 2,
  2952. specMaxReplicas: 8,
  2953. expectedReplicas: 2,
  2954. expectedCondition: "TooFewReplicas",
  2955. name: "prenormalized desired replicas < minReplicas",
  2956. },
  2957. {
  2958. currentReplicas: 1,
  2959. prenormalizedDesiredReplicas: 0,
  2960. specMinReplicas: 0,
  2961. specMaxReplicas: 10,
  2962. expectedReplicas: 0,
  2963. expectedCondition: "DesiredWithinRange",
  2964. name: "prenormalized desired replicas within range when minReplicas is 0",
  2965. },
  2966. {
  2967. currentReplicas: 20,
  2968. prenormalizedDesiredReplicas: 1000,
  2969. specMinReplicas: 1,
  2970. specMaxReplicas: 10,
  2971. expectedReplicas: 10,
  2972. expectedCondition: "TooManyReplicas",
  2973. name: "maxReplicas is the limit because maxReplicas < scaleUpLimit",
  2974. },
  2975. {
  2976. currentReplicas: 100,
  2977. prenormalizedDesiredReplicas: 1000,
  2978. specMinReplicas: 100,
  2979. specMaxReplicas: 150,
  2980. expectedReplicas: 150,
  2981. expectedCondition: "TooManyReplicas",
  2982. name: "desired replica count is more than the maximum replica count",
  2983. },
  2984. {
  2985. currentReplicas: 3,
  2986. prenormalizedDesiredReplicas: 1000,
  2987. specMinReplicas: 1,
  2988. specMaxReplicas: 2000,
  2989. expectedReplicas: 4,
  2990. expectedCondition: "ScaleUpLimit",
  2991. scaleUpRules: generateScalingRules(0, 0, 1, 60, 0),
  2992. name: "scaleUpLimit is the limit because scaleUpLimit < maxReplicas with user policies",
  2993. },
  2994. {
  2995. currentReplicas: 1000,
  2996. prenormalizedDesiredReplicas: 3,
  2997. specMinReplicas: 3,
  2998. specMaxReplicas: 2000,
  2999. scaleDownRules: generateScalingRules(20, 60, 0, 0, 0),
  3000. expectedReplicas: 980,
  3001. expectedCondition: "ScaleDownLimit",
  3002. name: "scaleDownLimit is the limit because scaleDownLimit > minReplicas with user defined policies",
  3003. testThis: true,
  3004. },
  3005. // ScaleUp without PeriodSeconds usage
  3006. {
  3007. name: "scaleUp with default behavior",
  3008. specMinReplicas: 1,
  3009. specMaxReplicas: 1000,
  3010. currentReplicas: 10,
  3011. prenormalizedDesiredReplicas: 50,
  3012. expectedReplicas: 20,
  3013. expectedCondition: "ScaleUpLimit",
  3014. },
  3015. {
  3016. name: "scaleUp with pods policy larger than percent policy",
  3017. specMinReplicas: 1,
  3018. specMaxReplicas: 1000,
  3019. scaleUpRules: generateScalingRules(100, 60, 100, 60, 0),
  3020. currentReplicas: 10,
  3021. prenormalizedDesiredReplicas: 500,
  3022. expectedReplicas: 110,
  3023. expectedCondition: "ScaleUpLimit",
  3024. },
  3025. {
  3026. name: "scaleUp with percent policy larger than pods policy",
  3027. specMinReplicas: 1,
  3028. specMaxReplicas: 1000,
  3029. scaleUpRules: generateScalingRules(2, 60, 100, 60, 0),
  3030. currentReplicas: 10,
  3031. prenormalizedDesiredReplicas: 500,
  3032. expectedReplicas: 20,
  3033. expectedCondition: "ScaleUpLimit",
  3034. },
  3035. {
  3036. name: "scaleUp with spec MaxReplicas limitation with large pod policy",
  3037. specMinReplicas: 1,
  3038. specMaxReplicas: 1000,
  3039. scaleUpRules: generateScalingRules(100, 60, 0, 0, 0),
  3040. currentReplicas: 10,
  3041. prenormalizedDesiredReplicas: 50,
  3042. expectedReplicas: 50,
  3043. expectedCondition: "DesiredWithinRange",
  3044. },
  3045. {
  3046. name: "scaleUp with spec MaxReplicas limitation with large percent policy",
  3047. specMinReplicas: 1,
  3048. specMaxReplicas: 1000,
  3049. scaleUpRules: generateScalingRules(10000, 60, 0, 0, 0),
  3050. currentReplicas: 10,
  3051. prenormalizedDesiredReplicas: 50,
  3052. expectedReplicas: 50,
  3053. expectedCondition: "DesiredWithinRange",
  3054. },
  3055. {
  3056. name: "scaleUp with pod policy limitation",
  3057. specMinReplicas: 1,
  3058. specMaxReplicas: 1000,
  3059. scaleUpRules: generateScalingRules(30, 60, 0, 0, 0),
  3060. currentReplicas: 10,
  3061. prenormalizedDesiredReplicas: 50,
  3062. expectedReplicas: 40,
  3063. expectedCondition: "ScaleUpLimit",
  3064. },
  3065. {
  3066. name: "scaleUp with percent policy limitation",
  3067. specMinReplicas: 1,
  3068. specMaxReplicas: 1000,
  3069. scaleUpRules: generateScalingRules(0, 0, 200, 60, 0),
  3070. currentReplicas: 10,
  3071. prenormalizedDesiredReplicas: 50,
  3072. expectedReplicas: 30,
  3073. expectedCondition: "ScaleUpLimit",
  3074. },
  3075. {
  3076. name: "scaleDown with percent policy larger than pod policy",
  3077. specMinReplicas: 1,
  3078. specMaxReplicas: 1000,
  3079. scaleDownRules: generateScalingRules(20, 60, 1, 60, 300),
  3080. currentReplicas: 100,
  3081. prenormalizedDesiredReplicas: 2,
  3082. expectedReplicas: 80,
  3083. expectedCondition: "ScaleDownLimit",
  3084. },
  3085. {
  3086. name: "scaleDown with pod policy larger than percent policy",
  3087. specMinReplicas: 1,
  3088. specMaxReplicas: 1000,
  3089. scaleDownRules: generateScalingRules(2, 60, 1, 60, 300),
  3090. currentReplicas: 100,
  3091. prenormalizedDesiredReplicas: 2,
  3092. expectedReplicas: 98,
  3093. expectedCondition: "ScaleDownLimit",
  3094. },
  3095. {
  3096. name: "scaleDown with spec MinReplicas=nil limitation with large pod policy",
  3097. specMinReplicas: 1,
  3098. specMaxReplicas: 1000,
  3099. scaleDownRules: generateScalingRules(100, 60, 0, 0, 300),
  3100. currentReplicas: 10,
  3101. prenormalizedDesiredReplicas: 0,
  3102. expectedReplicas: 1,
  3103. expectedCondition: "TooFewReplicas",
  3104. },
  3105. {
  3106. name: "scaleDown with spec MinReplicas limitation with large pod policy",
  3107. specMinReplicas: 1,
  3108. specMaxReplicas: 1000,
  3109. scaleDownRules: generateScalingRules(100, 60, 0, 0, 300),
  3110. currentReplicas: 10,
  3111. prenormalizedDesiredReplicas: 0,
  3112. expectedReplicas: 1,
  3113. expectedCondition: "TooFewReplicas",
  3114. },
  3115. {
  3116. name: "scaleDown with spec MinReplicas limitation with large percent policy",
  3117. specMinReplicas: 5,
  3118. specMaxReplicas: 1000,
  3119. scaleDownRules: generateScalingRules(0, 0, 100, 60, 300),
  3120. currentReplicas: 10,
  3121. prenormalizedDesiredReplicas: 2,
  3122. expectedReplicas: 5,
  3123. expectedCondition: "TooFewReplicas",
  3124. },
  3125. {
  3126. name: "scaleDown with pod policy limitation",
  3127. specMinReplicas: 1,
  3128. specMaxReplicas: 1000,
  3129. scaleDownRules: generateScalingRules(5, 60, 0, 0, 300),
  3130. currentReplicas: 10,
  3131. prenormalizedDesiredReplicas: 2,
  3132. expectedReplicas: 5,
  3133. expectedCondition: "ScaleDownLimit",
  3134. },
  3135. {
  3136. name: "scaleDown with percent policy limitation",
  3137. specMinReplicas: 1,
  3138. specMaxReplicas: 1000,
  3139. scaleDownRules: generateScalingRules(0, 0, 50, 60, 300),
  3140. currentReplicas: 10,
  3141. prenormalizedDesiredReplicas: 5,
  3142. expectedReplicas: 5,
  3143. expectedCondition: "DesiredWithinRange",
  3144. },
  3145. {
  3146. name: "scaleUp with spec MaxReplicas limitation with large pod policy and events",
  3147. scaleUpEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3148. specMinReplicas: 1,
  3149. specMaxReplicas: 200,
  3150. scaleUpRules: generateScalingRules(300, 60, 0, 0, 0),
  3151. currentReplicas: 100,
  3152. prenormalizedDesiredReplicas: 500,
  3153. expectedReplicas: 200, // 200 < 100 - 15 + 300
  3154. expectedCondition: "TooManyReplicas",
  3155. },
  3156. {
  3157. name: "scaleUp with spec MaxReplicas limitation with large percent policy and events",
  3158. scaleUpEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3159. specMinReplicas: 1,
  3160. specMaxReplicas: 200,
  3161. scaleUpRules: generateScalingRules(0, 0, 10000, 60, 0),
  3162. currentReplicas: 100,
  3163. prenormalizedDesiredReplicas: 500,
  3164. expectedReplicas: 200,
  3165. expectedCondition: "TooManyReplicas",
  3166. },
  3167. {
  3168. // corner case for calculating the scaleUpLimit, when we changed pod policy after a lot of scaleUp events
  3169. // in this case we shouldn't allow scale up, though, the naive formula will suggest that scaleUplimit is less then CurrentReplicas (100-15+5 < 100)
  3170. name: "scaleUp with currentReplicas limitation with rate.PeriodSeconds with a lot of recent scale up events",
  3171. scaleUpEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3172. specMinReplicas: 1,
  3173. specMaxReplicas: 1000,
  3174. scaleUpRules: generateScalingRules(5, 120, 0, 0, 0),
  3175. currentReplicas: 100,
  3176. prenormalizedDesiredReplicas: 500,
  3177. expectedReplicas: 100, // 120 seconds ago we had (100 - 15) replicas, now the rate.Pods = 5,
  3178. expectedCondition: "ScaleUpLimit",
  3179. },
  3180. {
  3181. name: "scaleUp with pod policy and previous scale up events",
  3182. scaleUpEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3183. specMinReplicas: 1,
  3184. specMaxReplicas: 1000,
  3185. scaleUpRules: generateScalingRules(150, 120, 0, 0, 0),
  3186. currentReplicas: 100,
  3187. prenormalizedDesiredReplicas: 500,
  3188. expectedReplicas: 235, // 100 - 15 + 150
  3189. expectedCondition: "ScaleUpLimit",
  3190. },
  3191. {
  3192. name: "scaleUp with percent policy and previous scale up events",
  3193. scaleUpEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3194. specMinReplicas: 1,
  3195. specMaxReplicas: 1000,
  3196. scaleUpRules: generateScalingRules(0, 0, 200, 120, 0),
  3197. currentReplicas: 100,
  3198. prenormalizedDesiredReplicas: 500,
  3199. expectedReplicas: 255, // (100 - 15) + 200%
  3200. expectedCondition: "ScaleUpLimit",
  3201. },
  3202. // ScaleDown with PeriodSeconds usage
  3203. {
  3204. name: "scaleDown with default policy and previous events",
  3205. scaleDownEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3206. specMinReplicas: 1,
  3207. specMaxReplicas: 1000,
  3208. currentReplicas: 10,
  3209. prenormalizedDesiredReplicas: 5,
  3210. expectedReplicas: 5, // without scaleDown rate limitations the PeriodSeconds does not influence anything
  3211. expectedCondition: "DesiredWithinRange",
  3212. },
  3213. {
  3214. name: "scaleDown with spec MinReplicas=nil limitation with large pod policy and previous events",
  3215. scaleDownEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3216. specMinReplicas: 1,
  3217. specMaxReplicas: 1000,
  3218. scaleDownRules: generateScalingRules(115, 120, 0, 0, 300),
  3219. currentReplicas: 100,
  3220. prenormalizedDesiredReplicas: 0,
  3221. expectedReplicas: 1,
  3222. expectedCondition: "TooFewReplicas",
  3223. },
  3224. {
  3225. name: "scaleDown with spec MinReplicas limitation with large pod policy and previous events",
  3226. scaleDownEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3227. specMinReplicas: 5,
  3228. specMaxReplicas: 1000,
  3229. scaleDownRules: generateScalingRules(130, 120, 0, 0, 300),
  3230. currentReplicas: 100,
  3231. prenormalizedDesiredReplicas: 0,
  3232. expectedReplicas: 5,
  3233. expectedCondition: "TooFewReplicas",
  3234. },
  3235. {
  3236. name: "scaleDown with spec MinReplicas limitation with large percent policy and previous events",
  3237. scaleDownEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3238. specMinReplicas: 5,
  3239. specMaxReplicas: 1000,
  3240. scaleDownRules: generateScalingRules(0, 0, 100, 120, 300), // 100% removal - is always to 0 => limited by MinReplicas
  3241. currentReplicas: 100,
  3242. prenormalizedDesiredReplicas: 2,
  3243. expectedReplicas: 5,
  3244. expectedCondition: "TooFewReplicas",
  3245. },
  3246. {
  3247. name: "scaleDown with pod policy limitation and previous events",
  3248. scaleDownEvents: generateEventsUniformDistribution([]int{1, 5, 9}, 120),
  3249. specMinReplicas: 1,
  3250. specMaxReplicas: 1000,
  3251. scaleDownRules: generateScalingRules(5, 120, 0, 0, 300),
  3252. currentReplicas: 100,
  3253. prenormalizedDesiredReplicas: 2,
  3254. expectedReplicas: 100, // 100 + 15 - 5
  3255. expectedCondition: "ScaleDownLimit",
  3256. },
  3257. {
  3258. name: "scaleDown with percent policy limitation and previous events",
  3259. scaleDownEvents: generateEventsUniformDistribution([]int{2, 4, 6}, 120),
  3260. specMinReplicas: 1,
  3261. specMaxReplicas: 1000,
  3262. scaleDownRules: generateScalingRules(0, 0, 50, 120, 300),
  3263. currentReplicas: 100,
  3264. prenormalizedDesiredReplicas: 0,
  3265. expectedReplicas: 56, // (100 + 12) - 50%
  3266. expectedCondition: "ScaleDownLimit",
  3267. },
  3268. {
  3269. // corner case for calculating the scaleDownLimit, when we changed pod or percent policy after a lot of scaleDown events
  3270. // in this case we shouldn't allow scale down, though, the naive formula will suggest that scaleDownlimit is more then CurrentReplicas (100+30-10% > 100)
  3271. name: "scaleDown with previous events preventing further scale down",
  3272. scaleDownEvents: generateEventsUniformDistribution([]int{10, 10, 10}, 120),
  3273. specMinReplicas: 1,
  3274. specMaxReplicas: 1000,
  3275. scaleDownRules: generateScalingRules(0, 0, 10, 120, 300),
  3276. currentReplicas: 100,
  3277. prenormalizedDesiredReplicas: 0,
  3278. expectedReplicas: 100, // (100 + 30) - 10% = 117 is more then 100 (currentReplicas), keep 100
  3279. expectedCondition: "ScaleDownLimit",
  3280. },
  3281. {
  3282. // corner case, the same as above, but calculation shows that we should go below zero
  3283. name: "scaleDown with with previous events still allowing more scale down",
  3284. scaleDownEvents: generateEventsUniformDistribution([]int{10, 10, 10}, 120),
  3285. specMinReplicas: 1,
  3286. specMaxReplicas: 1000,
  3287. scaleDownRules: generateScalingRules(0, 0, 1000, 120, 300),
  3288. currentReplicas: 10,
  3289. prenormalizedDesiredReplicas: 5,
  3290. expectedReplicas: 5, // (10 + 30) - 1000% = -360 is less than 0 and less then 5 (desired by metrics), set 5
  3291. expectedCondition: "DesiredWithinRange",
  3292. },
  3293. {
  3294. name: "check 'outdated' flag for events for one behavior for up",
  3295. scaleUpEvents: generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  3296. specMinReplicas: 1,
  3297. specMaxReplicas: 1000,
  3298. scaleUpRules: generateScalingRules(1000, 60, 0, 0, 0),
  3299. currentReplicas: 100,
  3300. prenormalizedDesiredReplicas: 200,
  3301. expectedReplicas: 200,
  3302. expectedCondition: "DesiredWithinRange",
  3303. },
  3304. {
  3305. name: "check that events were not marked 'outdated' for two different policies in the behavior for up",
  3306. scaleUpEvents: generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  3307. specMinReplicas: 1,
  3308. specMaxReplicas: 1000,
  3309. scaleUpRules: generateScalingRules(1000, 120, 100, 60, 0),
  3310. currentReplicas: 100,
  3311. prenormalizedDesiredReplicas: 200,
  3312. expectedReplicas: 200,
  3313. expectedCondition: "DesiredWithinRange",
  3314. },
  3315. {
  3316. name: "check that events were marked 'outdated' for two different policies in the behavior for up",
  3317. scaleUpEvents: generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  3318. specMinReplicas: 1,
  3319. specMaxReplicas: 1000,
  3320. scaleUpRules: generateScalingRules(1000, 30, 100, 60, 0),
  3321. currentReplicas: 100,
  3322. prenormalizedDesiredReplicas: 200,
  3323. expectedReplicas: 200,
  3324. expectedCondition: "DesiredWithinRange",
  3325. },
  3326. {
  3327. name: "check 'outdated' flag for events for one behavior for down",
  3328. scaleDownEvents: generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  3329. specMinReplicas: 1,
  3330. specMaxReplicas: 1000,
  3331. scaleDownRules: generateScalingRules(1000, 60, 0, 0, 300),
  3332. currentReplicas: 100,
  3333. prenormalizedDesiredReplicas: 5,
  3334. expectedReplicas: 5,
  3335. expectedCondition: "DesiredWithinRange",
  3336. },
  3337. {
  3338. name: "check that events were not marked 'outdated' for two different policies in the behavior for down",
  3339. scaleDownEvents: generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  3340. specMinReplicas: 1,
  3341. specMaxReplicas: 1000,
  3342. scaleDownRules: generateScalingRules(1000, 120, 100, 60, 300),
  3343. currentReplicas: 100,
  3344. prenormalizedDesiredReplicas: 5,
  3345. expectedReplicas: 5,
  3346. expectedCondition: "DesiredWithinRange",
  3347. },
  3348. {
  3349. name: "check that events were marked 'outdated' for two different policies in the behavior for down",
  3350. scaleDownEvents: generateEventsUniformDistribution([]int{8, 12, 9, 11}, 120),
  3351. specMinReplicas: 1,
  3352. specMaxReplicas: 1000,
  3353. scaleDownRules: generateScalingRules(1000, 30, 100, 60, 300),
  3354. currentReplicas: 100,
  3355. prenormalizedDesiredReplicas: 5,
  3356. expectedReplicas: 5,
  3357. expectedCondition: "DesiredWithinRange",
  3358. },
  3359. }
  3360. for _, tc := range tests {
  3361. t.Run(tc.name, func(t *testing.T) {
  3362. if tc.testThis {
  3363. return
  3364. }
  3365. hc := HorizontalController{
  3366. scaleUpEvents: map[string][]timestampedScaleEvent{
  3367. tc.key: tc.scaleUpEvents,
  3368. },
  3369. scaleDownEvents: map[string][]timestampedScaleEvent{
  3370. tc.key: tc.scaleDownEvents,
  3371. },
  3372. }
  3373. arg := NormalizationArg{
  3374. Key: tc.key,
  3375. ScaleUpBehavior: autoscalingapiv2beta2.GenerateHPAScaleUpRules(tc.scaleUpRules),
  3376. ScaleDownBehavior: autoscalingapiv2beta2.GenerateHPAScaleDownRules(tc.scaleDownRules),
  3377. MinReplicas: tc.specMinReplicas,
  3378. MaxReplicas: tc.specMaxReplicas,
  3379. DesiredReplicas: tc.prenormalizedDesiredReplicas,
  3380. CurrentReplicas: tc.currentReplicas,
  3381. }
  3382. replicas, condition, _ := hc.convertDesiredReplicasWithBehaviorRate(arg)
  3383. assert.Equal(t, tc.expectedReplicas, replicas, "expected replicas do not match with converted replicas")
  3384. assert.Equal(t, tc.expectedCondition, condition, "HPA condition does not match with expected condition")
  3385. })
  3386. }
  3387. }
  3388. // TestStoreScaleEvents tests events storage and usage
  3389. func TestStoreScaleEvents(t *testing.T) {
  3390. type TestCase struct {
  3391. name string
  3392. key string
  3393. replicaChange int32
  3394. prevScaleEvents []timestampedScaleEvent
  3395. newScaleEvents []timestampedScaleEvent
  3396. scalingRules *autoscalingv2.HPAScalingRules
  3397. expectedReplicasChange int32
  3398. }
  3399. tests := []TestCase{
  3400. {
  3401. name: "empty entries with default behavior",
  3402. replicaChange: 5,
  3403. prevScaleEvents: []timestampedScaleEvent{}, // no history -> 0 replica change
  3404. newScaleEvents: []timestampedScaleEvent{}, // no behavior -> no events are stored
  3405. expectedReplicasChange: 0,
  3406. },
  3407. {
  3408. name: "empty entries with two-policy-behavior",
  3409. replicaChange: 5,
  3410. prevScaleEvents: []timestampedScaleEvent{}, // no history -> 0 replica change
  3411. newScaleEvents: []timestampedScaleEvent{{5, time.Now(), false}},
  3412. scalingRules: generateScalingRules(10, 60, 100, 60, 0),
  3413. expectedReplicasChange: 0,
  3414. },
  3415. {
  3416. name: "one outdated entry to be kept untouched without behavior",
  3417. replicaChange: 5,
  3418. prevScaleEvents: []timestampedScaleEvent{
  3419. {7, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be replaced
  3420. },
  3421. newScaleEvents: []timestampedScaleEvent{
  3422. {7, time.Now(), false}, // no behavior -> we don't touch stored events
  3423. },
  3424. expectedReplicasChange: 0,
  3425. },
  3426. {
  3427. name: "one outdated entry to be replaced with behavior",
  3428. replicaChange: 5,
  3429. prevScaleEvents: []timestampedScaleEvent{
  3430. {7, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be replaced
  3431. },
  3432. newScaleEvents: []timestampedScaleEvent{
  3433. {5, time.Now(), false},
  3434. },
  3435. scalingRules: generateScalingRules(10, 60, 100, 60, 0),
  3436. expectedReplicasChange: 0,
  3437. },
  3438. {
  3439. name: "one actual entry to be not touched with behavior",
  3440. replicaChange: 5,
  3441. prevScaleEvents: []timestampedScaleEvent{
  3442. {7, time.Now().Add(-time.Second * time.Duration(58)), false},
  3443. },
  3444. newScaleEvents: []timestampedScaleEvent{
  3445. {7, time.Now(), false},
  3446. {5, time.Now(), false},
  3447. },
  3448. scalingRules: generateScalingRules(10, 60, 100, 60, 0),
  3449. expectedReplicasChange: 7,
  3450. },
  3451. {
  3452. name: "two entries, one of them to be replaced",
  3453. replicaChange: 5,
  3454. prevScaleEvents: []timestampedScaleEvent{
  3455. {7, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be replaced
  3456. {6, time.Now().Add(-time.Second * time.Duration(59)), false},
  3457. },
  3458. newScaleEvents: []timestampedScaleEvent{
  3459. {5, time.Now(), false},
  3460. {6, time.Now(), false},
  3461. },
  3462. scalingRules: generateScalingRules(10, 60, 0, 0, 0),
  3463. expectedReplicasChange: 6,
  3464. },
  3465. {
  3466. name: "replace one entry, use policies with different periods",
  3467. replicaChange: 5,
  3468. prevScaleEvents: []timestampedScaleEvent{
  3469. {8, time.Now().Add(-time.Second * time.Duration(29)), false},
  3470. {6, time.Now().Add(-time.Second * time.Duration(59)), false},
  3471. {7, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be marked as outdated
  3472. {9, time.Now().Add(-time.Second * time.Duration(61)), false}, // outdated event, should be replaced
  3473. },
  3474. newScaleEvents: []timestampedScaleEvent{
  3475. {8, time.Now(), false},
  3476. {6, time.Now(), false},
  3477. {7, time.Now(), true},
  3478. {5, time.Now(), false},
  3479. },
  3480. scalingRules: generateScalingRules(10, 60, 100, 30, 0),
  3481. expectedReplicasChange: 14,
  3482. },
  3483. {
  3484. name: "two entries, both actual",
  3485. replicaChange: 5,
  3486. prevScaleEvents: []timestampedScaleEvent{
  3487. {7, time.Now().Add(-time.Second * time.Duration(58)), false},
  3488. {6, time.Now().Add(-time.Second * time.Duration(59)), false},
  3489. },
  3490. newScaleEvents: []timestampedScaleEvent{
  3491. {7, time.Now(), false},
  3492. {6, time.Now(), false},
  3493. {5, time.Now(), false},
  3494. },
  3495. scalingRules: generateScalingRules(10, 120, 100, 30, 0),
  3496. expectedReplicasChange: 13,
  3497. },
  3498. }
  3499. for _, tc := range tests {
  3500. t.Run(tc.name, func(t *testing.T) {
  3501. // testing scale up
  3502. var behaviorUp *autoscalingv2.HorizontalPodAutoscalerBehavior
  3503. if tc.scalingRules != nil {
  3504. behaviorUp = &autoscalingv2.HorizontalPodAutoscalerBehavior{
  3505. ScaleUp: tc.scalingRules,
  3506. }
  3507. }
  3508. hcUp := HorizontalController{
  3509. scaleUpEvents: map[string][]timestampedScaleEvent{
  3510. tc.key: append([]timestampedScaleEvent{}, tc.prevScaleEvents...),
  3511. },
  3512. }
  3513. gotReplicasChangeUp := getReplicasChangePerPeriod(60, hcUp.scaleUpEvents[tc.key])
  3514. assert.Equal(t, tc.expectedReplicasChange, gotReplicasChangeUp)
  3515. hcUp.storeScaleEvent(behaviorUp, tc.key, 10, 10+tc.replicaChange)
  3516. if !assert.Len(t, hcUp.scaleUpEvents[tc.key], len(tc.newScaleEvents), "up: scale events differ in length") {
  3517. return
  3518. }
  3519. for i, gotEvent := range hcUp.scaleUpEvents[tc.key] {
  3520. expEvent := tc.newScaleEvents[i]
  3521. assert.Equal(t, expEvent.replicaChange, gotEvent.replicaChange, "up: idx:%v replicaChange", i)
  3522. assert.Equal(t, expEvent.outdated, gotEvent.outdated, "up: idx:%v outdated", i)
  3523. }
  3524. // testing scale down
  3525. var behaviorDown *autoscalingv2.HorizontalPodAutoscalerBehavior
  3526. if tc.scalingRules != nil {
  3527. behaviorDown = &autoscalingv2.HorizontalPodAutoscalerBehavior{
  3528. ScaleDown: tc.scalingRules,
  3529. }
  3530. }
  3531. hcDown := HorizontalController{
  3532. scaleDownEvents: map[string][]timestampedScaleEvent{
  3533. tc.key: append([]timestampedScaleEvent{}, tc.prevScaleEvents...),
  3534. },
  3535. }
  3536. gotReplicasChangeDown := getReplicasChangePerPeriod(60, hcDown.scaleDownEvents[tc.key])
  3537. assert.Equal(t, tc.expectedReplicasChange, gotReplicasChangeDown)
  3538. hcDown.storeScaleEvent(behaviorDown, tc.key, 10, 10-tc.replicaChange)
  3539. if !assert.Len(t, hcDown.scaleDownEvents[tc.key], len(tc.newScaleEvents), "down: scale events differ in length") {
  3540. return
  3541. }
  3542. for i, gotEvent := range hcDown.scaleDownEvents[tc.key] {
  3543. expEvent := tc.newScaleEvents[i]
  3544. assert.Equal(t, expEvent.replicaChange, gotEvent.replicaChange, "down: idx:%v replicaChange", i)
  3545. assert.Equal(t, expEvent.outdated, gotEvent.outdated, "down: idx:%v outdated", i)
  3546. }
  3547. })
  3548. }
  3549. }
  3550. func TestNormalizeDesiredReplicasWithBehavior(t *testing.T) {
  3551. type TestCase struct {
  3552. name string
  3553. key string
  3554. recommendations []timestampedRecommendation
  3555. currentReplicas int32
  3556. prenormalizedDesiredReplicas int32
  3557. expectedStabilizedReplicas int32
  3558. expectedRecommendations []timestampedRecommendation
  3559. scaleUpStabilizationWindowSeconds int32
  3560. scaleDownStabilizationWindowSeconds int32
  3561. }
  3562. tests := []TestCase{
  3563. {
  3564. name: "empty recommendations for scaling down",
  3565. key: "",
  3566. recommendations: []timestampedRecommendation{},
  3567. currentReplicas: 100,
  3568. prenormalizedDesiredReplicas: 5,
  3569. expectedStabilizedReplicas: 5,
  3570. expectedRecommendations: []timestampedRecommendation{
  3571. {5, time.Now()},
  3572. },
  3573. },
  3574. {
  3575. name: "simple scale down stabilization",
  3576. key: "",
  3577. recommendations: []timestampedRecommendation{
  3578. {4, time.Now().Add(-2 * time.Minute)},
  3579. {5, time.Now().Add(-1 * time.Minute)}},
  3580. currentReplicas: 100,
  3581. prenormalizedDesiredReplicas: 3,
  3582. expectedStabilizedReplicas: 5,
  3583. expectedRecommendations: []timestampedRecommendation{
  3584. {4, time.Now()},
  3585. {5, time.Now()},
  3586. {3, time.Now()},
  3587. },
  3588. scaleDownStabilizationWindowSeconds: 60 * 3,
  3589. },
  3590. {
  3591. name: "simple scale up stabilization",
  3592. key: "",
  3593. recommendations: []timestampedRecommendation{
  3594. {4, time.Now().Add(-2 * time.Minute)},
  3595. {5, time.Now().Add(-1 * time.Minute)}},
  3596. currentReplicas: 1,
  3597. prenormalizedDesiredReplicas: 7,
  3598. expectedStabilizedReplicas: 4,
  3599. expectedRecommendations: []timestampedRecommendation{
  3600. {4, time.Now()},
  3601. {5, time.Now()},
  3602. {7, time.Now()},
  3603. },
  3604. scaleUpStabilizationWindowSeconds: 60 * 5,
  3605. },
  3606. {
  3607. name: "no scale down stabilization",
  3608. key: "",
  3609. recommendations: []timestampedRecommendation{
  3610. {1, time.Now().Add(-2 * time.Minute)},
  3611. {2, time.Now().Add(-1 * time.Minute)}},
  3612. currentReplicas: 100, // to apply scaleDown delay we should have current > desired
  3613. prenormalizedDesiredReplicas: 3,
  3614. expectedStabilizedReplicas: 3,
  3615. expectedRecommendations: []timestampedRecommendation{
  3616. {1, time.Now()},
  3617. {2, time.Now()},
  3618. {3, time.Now()},
  3619. },
  3620. scaleUpStabilizationWindowSeconds: 60 * 5,
  3621. },
  3622. {
  3623. name: "no scale up stabilization",
  3624. key: "",
  3625. recommendations: []timestampedRecommendation{
  3626. {4, time.Now().Add(-2 * time.Minute)},
  3627. {5, time.Now().Add(-1 * time.Minute)}},
  3628. currentReplicas: 1, // to apply scaleDown delay we should have current > desired
  3629. prenormalizedDesiredReplicas: 3,
  3630. expectedStabilizedReplicas: 3,
  3631. expectedRecommendations: []timestampedRecommendation{
  3632. {4, time.Now()},
  3633. {5, time.Now()},
  3634. {3, time.Now()},
  3635. },
  3636. scaleDownStabilizationWindowSeconds: 60 * 5,
  3637. },
  3638. {
  3639. name: "no scale down stabilization, reuse recommendation element",
  3640. key: "",
  3641. recommendations: []timestampedRecommendation{
  3642. {10, time.Now().Add(-10 * time.Minute)},
  3643. {9, time.Now().Add(-9 * time.Minute)}},
  3644. currentReplicas: 100, // to apply scaleDown delay we should have current > desired
  3645. prenormalizedDesiredReplicas: 3,
  3646. expectedStabilizedReplicas: 3,
  3647. expectedRecommendations: []timestampedRecommendation{
  3648. {10, time.Now()},
  3649. {3, time.Now()},
  3650. },
  3651. },
  3652. {
  3653. name: "no scale up stabilization, reuse recommendation element",
  3654. key: "",
  3655. recommendations: []timestampedRecommendation{
  3656. {10, time.Now().Add(-10 * time.Minute)},
  3657. {9, time.Now().Add(-9 * time.Minute)}},
  3658. currentReplicas: 1,
  3659. prenormalizedDesiredReplicas: 100,
  3660. expectedStabilizedReplicas: 100,
  3661. expectedRecommendations: []timestampedRecommendation{
  3662. {10, time.Now()},
  3663. {100, time.Now()},
  3664. },
  3665. },
  3666. {
  3667. name: "scale down stabilization, reuse one of obsolete recommendation element",
  3668. key: "",
  3669. recommendations: []timestampedRecommendation{
  3670. {10, time.Now().Add(-10 * time.Minute)},
  3671. {4, time.Now().Add(-1 * time.Minute)},
  3672. {5, time.Now().Add(-2 * time.Minute)},
  3673. {9, time.Now().Add(-9 * time.Minute)}},
  3674. currentReplicas: 100,
  3675. prenormalizedDesiredReplicas: 3,
  3676. expectedStabilizedReplicas: 5,
  3677. expectedRecommendations: []timestampedRecommendation{
  3678. {10, time.Now()},
  3679. {4, time.Now()},
  3680. {5, time.Now()},
  3681. {3, time.Now()},
  3682. },
  3683. scaleDownStabilizationWindowSeconds: 3 * 60,
  3684. },
  3685. {
  3686. // we can reuse only the first recommendation element
  3687. // as the scale up delay = 150 (set in test), scale down delay = 300 (by default)
  3688. // hence, only the first recommendation is obsolete for both scale up and scale down
  3689. name: "scale up stabilization, reuse one of obsolete recommendation element",
  3690. key: "",
  3691. recommendations: []timestampedRecommendation{
  3692. {10, time.Now().Add(-100 * time.Minute)},
  3693. {6, time.Now().Add(-1 * time.Minute)},
  3694. {5, time.Now().Add(-2 * time.Minute)},
  3695. {9, time.Now().Add(-3 * time.Minute)}},
  3696. currentReplicas: 1,
  3697. prenormalizedDesiredReplicas: 100,
  3698. expectedStabilizedReplicas: 5,
  3699. expectedRecommendations: []timestampedRecommendation{
  3700. {100, time.Now()},
  3701. {6, time.Now()},
  3702. {5, time.Now()},
  3703. {9, time.Now()},
  3704. },
  3705. scaleUpStabilizationWindowSeconds: 300,
  3706. },
  3707. }
  3708. for _, tc := range tests {
  3709. t.Run(tc.name, func(t *testing.T) {
  3710. hc := HorizontalController{
  3711. recommendations: map[string][]timestampedRecommendation{
  3712. tc.key: tc.recommendations,
  3713. },
  3714. }
  3715. arg := NormalizationArg{
  3716. Key: tc.key,
  3717. DesiredReplicas: tc.prenormalizedDesiredReplicas,
  3718. CurrentReplicas: tc.currentReplicas,
  3719. ScaleUpBehavior: &autoscalingv2.HPAScalingRules{
  3720. StabilizationWindowSeconds: &tc.scaleUpStabilizationWindowSeconds,
  3721. },
  3722. ScaleDownBehavior: &autoscalingv2.HPAScalingRules{
  3723. StabilizationWindowSeconds: &tc.scaleDownStabilizationWindowSeconds,
  3724. },
  3725. }
  3726. r, _, _ := hc.stabilizeRecommendationWithBehaviors(arg)
  3727. assert.Equal(t, tc.expectedStabilizedReplicas, r, "expected replicas do not match")
  3728. if !assert.Len(t, hc.recommendations[tc.key], len(tc.expectedRecommendations), "stored recommendations differ in length") {
  3729. return
  3730. }
  3731. for i, r := range hc.recommendations[tc.key] {
  3732. expectedRecommendation := tc.expectedRecommendations[i]
  3733. assert.Equal(t, expectedRecommendation.recommendation, r.recommendation, "stored recommendation differs at position %d", i)
  3734. }
  3735. })
  3736. }
  3737. }
  3738. func TestScaleUpOneMetricEmpty(t *testing.T) {
  3739. tc := testCase{
  3740. minReplicas: 2,
  3741. maxReplicas: 6,
  3742. specReplicas: 3,
  3743. statusReplicas: 3,
  3744. expectedDesiredReplicas: 4,
  3745. CPUTarget: 30,
  3746. verifyCPUCurrent: true,
  3747. metricsTarget: []autoscalingv2.MetricSpec{
  3748. {
  3749. Type: autoscalingv2.ExternalMetricSourceType,
  3750. External: &autoscalingv2.ExternalMetricSource{
  3751. Metric: autoscalingv2.MetricIdentifier{
  3752. Name: "qps",
  3753. Selector: &metav1.LabelSelector{},
  3754. },
  3755. Target: autoscalingv2.MetricTarget{
  3756. Value: resource.NewMilliQuantity(100, resource.DecimalSI),
  3757. },
  3758. },
  3759. },
  3760. },
  3761. reportedLevels: []uint64{300, 400, 500},
  3762. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3763. }
  3764. _, _, _, testEMClient, _ := tc.prepareTestClient(t)
  3765. testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3766. return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  3767. })
  3768. tc.testEMClient = testEMClient
  3769. tc.runTest(t)
  3770. }
  3771. func TestNoScaleDownOneMetricInvalid(t *testing.T) {
  3772. tc := testCase{
  3773. minReplicas: 2,
  3774. maxReplicas: 6,
  3775. specReplicas: 5,
  3776. statusReplicas: 5,
  3777. expectedDesiredReplicas: 5,
  3778. CPUTarget: 50,
  3779. metricsTarget: []autoscalingv2.MetricSpec{
  3780. {
  3781. Type: "CheddarCheese",
  3782. },
  3783. },
  3784. reportedLevels: []uint64{100, 300, 500, 250, 250},
  3785. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3786. useMetricsAPI: true,
  3787. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  3788. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "ScaleDownStabilized"},
  3789. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"},
  3790. {Type: autoscalingv1.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"},
  3791. },
  3792. }
  3793. tc.runTest(t)
  3794. }
  3795. func TestNoScaleDownOneMetricEmpty(t *testing.T) {
  3796. tc := testCase{
  3797. minReplicas: 2,
  3798. maxReplicas: 6,
  3799. specReplicas: 5,
  3800. statusReplicas: 5,
  3801. expectedDesiredReplicas: 5,
  3802. CPUTarget: 50,
  3803. metricsTarget: []autoscalingv2.MetricSpec{
  3804. {
  3805. Type: autoscalingv2.ExternalMetricSourceType,
  3806. External: &autoscalingv2.ExternalMetricSource{
  3807. Metric: autoscalingv2.MetricIdentifier{
  3808. Name: "qps",
  3809. Selector: &metav1.LabelSelector{},
  3810. },
  3811. Target: autoscalingv2.MetricTarget{
  3812. Value: resource.NewMilliQuantity(1000, resource.DecimalSI),
  3813. },
  3814. },
  3815. },
  3816. },
  3817. reportedLevels: []uint64{100, 300, 500, 250, 250},
  3818. reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")},
  3819. useMetricsAPI: true,
  3820. expectedConditions: []autoscalingv1.HorizontalPodAutoscalerCondition{
  3821. {Type: autoscalingv1.AbleToScale, Status: v1.ConditionTrue, Reason: "ScaleDownStabilized"},
  3822. {Type: autoscalingv1.ScalingActive, Status: v1.ConditionTrue, Reason: "ValidMetricFound"},
  3823. {Type: autoscalingv1.ScalingLimited, Status: v1.ConditionFalse, Reason: "DesiredWithinRange"},
  3824. },
  3825. }
  3826. _, _, _, testEMClient, _ := tc.prepareTestClient(t)
  3827. testEMClient.PrependReactor("list", "*", func(action core.Action) (handled bool, ret runtime.Object, err error) {
  3828. return true, &emapi.ExternalMetricValueList{}, fmt.Errorf("something went wrong")
  3829. })
  3830. tc.testEMClient = testEMClient
  3831. tc.runTest(t)
  3832. }