helpers_test.go 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968
  1. /*
  2. Copyright 2016 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 eviction
  14. import (
  15. "fmt"
  16. "reflect"
  17. "sort"
  18. "testing"
  19. "time"
  20. "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/resource"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/types"
  24. utilfeature "k8s.io/apiserver/pkg/util/feature"
  25. featuregatetesting "k8s.io/component-base/featuregate/testing"
  26. "k8s.io/kubernetes/pkg/features"
  27. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  28. evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
  29. kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
  30. )
  31. func quantityMustParse(value string) *resource.Quantity {
  32. q := resource.MustParse(value)
  33. return &q
  34. }
  35. func TestGetReclaimableThreshold(t *testing.T) {
  36. testCases := map[string]struct {
  37. thresholds []evictionapi.Threshold
  38. }{
  39. "": {
  40. thresholds: []evictionapi.Threshold{
  41. {
  42. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  43. Operator: evictionapi.OpLessThan,
  44. Value: evictionapi.ThresholdValue{
  45. Quantity: quantityMustParse("150Mi"),
  46. },
  47. MinReclaim: &evictionapi.ThresholdValue{
  48. Quantity: quantityMustParse("0"),
  49. },
  50. },
  51. {
  52. Signal: evictionapi.SignalMemoryAvailable,
  53. Operator: evictionapi.OpLessThan,
  54. Value: evictionapi.ThresholdValue{
  55. Quantity: quantityMustParse("150Mi"),
  56. },
  57. MinReclaim: &evictionapi.ThresholdValue{
  58. Quantity: quantityMustParse("0"),
  59. },
  60. },
  61. {
  62. Signal: evictionapi.SignalImageFsAvailable,
  63. Operator: evictionapi.OpLessThan,
  64. Value: evictionapi.ThresholdValue{
  65. Quantity: quantityMustParse("150Mi"),
  66. },
  67. MinReclaim: &evictionapi.ThresholdValue{
  68. Quantity: quantityMustParse("2Gi"),
  69. },
  70. },
  71. {
  72. Signal: evictionapi.SignalNodeFsAvailable,
  73. Operator: evictionapi.OpLessThan,
  74. Value: evictionapi.ThresholdValue{
  75. Quantity: quantityMustParse("100Mi"),
  76. },
  77. MinReclaim: &evictionapi.ThresholdValue{
  78. Quantity: quantityMustParse("1Gi"),
  79. },
  80. },
  81. },
  82. },
  83. }
  84. for testName, testCase := range testCases {
  85. sort.Sort(byEvictionPriority(testCase.thresholds))
  86. _, resource, ok := getReclaimableThreshold(testCase.thresholds)
  87. print(resource)
  88. if !ok {
  89. t.Errorf("Didn't find reclaimable threshold, test: %v", testName)
  90. }
  91. }
  92. }
  93. func TestParseThresholdConfig(t *testing.T) {
  94. gracePeriod, _ := time.ParseDuration("30s")
  95. testCases := map[string]struct {
  96. allocatableConfig []string
  97. evictionHard map[string]string
  98. evictionSoft map[string]string
  99. evictionSoftGracePeriod map[string]string
  100. evictionMinReclaim map[string]string
  101. expectErr bool
  102. expectThresholds []evictionapi.Threshold
  103. }{
  104. "no values": {
  105. allocatableConfig: []string{},
  106. evictionHard: map[string]string{},
  107. evictionSoft: map[string]string{},
  108. evictionSoftGracePeriod: map[string]string{},
  109. evictionMinReclaim: map[string]string{},
  110. expectErr: false,
  111. expectThresholds: []evictionapi.Threshold{},
  112. },
  113. "all memory eviction values": {
  114. allocatableConfig: []string{kubetypes.NodeAllocatableEnforcementKey},
  115. evictionHard: map[string]string{"memory.available": "150Mi"},
  116. evictionSoft: map[string]string{"memory.available": "300Mi"},
  117. evictionSoftGracePeriod: map[string]string{"memory.available": "30s"},
  118. evictionMinReclaim: map[string]string{"memory.available": "0"},
  119. expectErr: false,
  120. expectThresholds: []evictionapi.Threshold{
  121. {
  122. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  123. Operator: evictionapi.OpLessThan,
  124. Value: evictionapi.ThresholdValue{
  125. Quantity: quantityMustParse("150Mi"),
  126. },
  127. MinReclaim: &evictionapi.ThresholdValue{
  128. Quantity: quantityMustParse("0"),
  129. },
  130. },
  131. {
  132. Signal: evictionapi.SignalMemoryAvailable,
  133. Operator: evictionapi.OpLessThan,
  134. Value: evictionapi.ThresholdValue{
  135. Quantity: quantityMustParse("150Mi"),
  136. },
  137. MinReclaim: &evictionapi.ThresholdValue{
  138. Quantity: quantityMustParse("0"),
  139. },
  140. },
  141. {
  142. Signal: evictionapi.SignalMemoryAvailable,
  143. Operator: evictionapi.OpLessThan,
  144. Value: evictionapi.ThresholdValue{
  145. Quantity: quantityMustParse("300Mi"),
  146. },
  147. GracePeriod: gracePeriod,
  148. MinReclaim: &evictionapi.ThresholdValue{
  149. Quantity: quantityMustParse("0"),
  150. },
  151. },
  152. },
  153. },
  154. "all memory eviction values in percentages": {
  155. allocatableConfig: []string{},
  156. evictionHard: map[string]string{"memory.available": "10%"},
  157. evictionSoft: map[string]string{"memory.available": "30%"},
  158. evictionSoftGracePeriod: map[string]string{"memory.available": "30s"},
  159. evictionMinReclaim: map[string]string{"memory.available": "5%"},
  160. expectErr: false,
  161. expectThresholds: []evictionapi.Threshold{
  162. {
  163. Signal: evictionapi.SignalMemoryAvailable,
  164. Operator: evictionapi.OpLessThan,
  165. Value: evictionapi.ThresholdValue{
  166. Percentage: 0.1,
  167. },
  168. MinReclaim: &evictionapi.ThresholdValue{
  169. Percentage: 0.05,
  170. },
  171. },
  172. {
  173. Signal: evictionapi.SignalMemoryAvailable,
  174. Operator: evictionapi.OpLessThan,
  175. Value: evictionapi.ThresholdValue{
  176. Percentage: 0.3,
  177. },
  178. GracePeriod: gracePeriod,
  179. MinReclaim: &evictionapi.ThresholdValue{
  180. Percentage: 0.05,
  181. },
  182. },
  183. },
  184. },
  185. "disk eviction values": {
  186. allocatableConfig: []string{},
  187. evictionHard: map[string]string{"imagefs.available": "150Mi", "nodefs.available": "100Mi"},
  188. evictionSoft: map[string]string{"imagefs.available": "300Mi", "nodefs.available": "200Mi"},
  189. evictionSoftGracePeriod: map[string]string{"imagefs.available": "30s", "nodefs.available": "30s"},
  190. evictionMinReclaim: map[string]string{"imagefs.available": "2Gi", "nodefs.available": "1Gi"},
  191. expectErr: false,
  192. expectThresholds: []evictionapi.Threshold{
  193. {
  194. Signal: evictionapi.SignalImageFsAvailable,
  195. Operator: evictionapi.OpLessThan,
  196. Value: evictionapi.ThresholdValue{
  197. Quantity: quantityMustParse("150Mi"),
  198. },
  199. MinReclaim: &evictionapi.ThresholdValue{
  200. Quantity: quantityMustParse("2Gi"),
  201. },
  202. },
  203. {
  204. Signal: evictionapi.SignalNodeFsAvailable,
  205. Operator: evictionapi.OpLessThan,
  206. Value: evictionapi.ThresholdValue{
  207. Quantity: quantityMustParse("100Mi"),
  208. },
  209. MinReclaim: &evictionapi.ThresholdValue{
  210. Quantity: quantityMustParse("1Gi"),
  211. },
  212. },
  213. {
  214. Signal: evictionapi.SignalImageFsAvailable,
  215. Operator: evictionapi.OpLessThan,
  216. Value: evictionapi.ThresholdValue{
  217. Quantity: quantityMustParse("300Mi"),
  218. },
  219. GracePeriod: gracePeriod,
  220. MinReclaim: &evictionapi.ThresholdValue{
  221. Quantity: quantityMustParse("2Gi"),
  222. },
  223. },
  224. {
  225. Signal: evictionapi.SignalNodeFsAvailable,
  226. Operator: evictionapi.OpLessThan,
  227. Value: evictionapi.ThresholdValue{
  228. Quantity: quantityMustParse("200Mi"),
  229. },
  230. GracePeriod: gracePeriod,
  231. MinReclaim: &evictionapi.ThresholdValue{
  232. Quantity: quantityMustParse("1Gi"),
  233. },
  234. },
  235. },
  236. },
  237. "disk eviction values in percentages": {
  238. allocatableConfig: []string{},
  239. evictionHard: map[string]string{"imagefs.available": "15%", "nodefs.available": "10.5%"},
  240. evictionSoft: map[string]string{"imagefs.available": "30%", "nodefs.available": "20.5%"},
  241. evictionSoftGracePeriod: map[string]string{"imagefs.available": "30s", "nodefs.available": "30s"},
  242. evictionMinReclaim: map[string]string{"imagefs.available": "10%", "nodefs.available": "5%"},
  243. expectErr: false,
  244. expectThresholds: []evictionapi.Threshold{
  245. {
  246. Signal: evictionapi.SignalImageFsAvailable,
  247. Operator: evictionapi.OpLessThan,
  248. Value: evictionapi.ThresholdValue{
  249. Percentage: 0.15,
  250. },
  251. MinReclaim: &evictionapi.ThresholdValue{
  252. Percentage: 0.1,
  253. },
  254. },
  255. {
  256. Signal: evictionapi.SignalNodeFsAvailable,
  257. Operator: evictionapi.OpLessThan,
  258. Value: evictionapi.ThresholdValue{
  259. Percentage: 0.105,
  260. },
  261. MinReclaim: &evictionapi.ThresholdValue{
  262. Percentage: 0.05,
  263. },
  264. },
  265. {
  266. Signal: evictionapi.SignalImageFsAvailable,
  267. Operator: evictionapi.OpLessThan,
  268. Value: evictionapi.ThresholdValue{
  269. Percentage: 0.3,
  270. },
  271. GracePeriod: gracePeriod,
  272. MinReclaim: &evictionapi.ThresholdValue{
  273. Percentage: 0.1,
  274. },
  275. },
  276. {
  277. Signal: evictionapi.SignalNodeFsAvailable,
  278. Operator: evictionapi.OpLessThan,
  279. Value: evictionapi.ThresholdValue{
  280. Percentage: 0.205,
  281. },
  282. GracePeriod: gracePeriod,
  283. MinReclaim: &evictionapi.ThresholdValue{
  284. Percentage: 0.05,
  285. },
  286. },
  287. },
  288. },
  289. "inode eviction values": {
  290. allocatableConfig: []string{},
  291. evictionHard: map[string]string{"imagefs.inodesFree": "150Mi", "nodefs.inodesFree": "100Mi"},
  292. evictionSoft: map[string]string{"imagefs.inodesFree": "300Mi", "nodefs.inodesFree": "200Mi"},
  293. evictionSoftGracePeriod: map[string]string{"imagefs.inodesFree": "30s", "nodefs.inodesFree": "30s"},
  294. evictionMinReclaim: map[string]string{"imagefs.inodesFree": "2Gi", "nodefs.inodesFree": "1Gi"},
  295. expectErr: false,
  296. expectThresholds: []evictionapi.Threshold{
  297. {
  298. Signal: evictionapi.SignalImageFsInodesFree,
  299. Operator: evictionapi.OpLessThan,
  300. Value: evictionapi.ThresholdValue{
  301. Quantity: quantityMustParse("150Mi"),
  302. },
  303. MinReclaim: &evictionapi.ThresholdValue{
  304. Quantity: quantityMustParse("2Gi"),
  305. },
  306. },
  307. {
  308. Signal: evictionapi.SignalNodeFsInodesFree,
  309. Operator: evictionapi.OpLessThan,
  310. Value: evictionapi.ThresholdValue{
  311. Quantity: quantityMustParse("100Mi"),
  312. },
  313. MinReclaim: &evictionapi.ThresholdValue{
  314. Quantity: quantityMustParse("1Gi"),
  315. },
  316. },
  317. {
  318. Signal: evictionapi.SignalImageFsInodesFree,
  319. Operator: evictionapi.OpLessThan,
  320. Value: evictionapi.ThresholdValue{
  321. Quantity: quantityMustParse("300Mi"),
  322. },
  323. GracePeriod: gracePeriod,
  324. MinReclaim: &evictionapi.ThresholdValue{
  325. Quantity: quantityMustParse("2Gi"),
  326. },
  327. },
  328. {
  329. Signal: evictionapi.SignalNodeFsInodesFree,
  330. Operator: evictionapi.OpLessThan,
  331. Value: evictionapi.ThresholdValue{
  332. Quantity: quantityMustParse("200Mi"),
  333. },
  334. GracePeriod: gracePeriod,
  335. MinReclaim: &evictionapi.ThresholdValue{
  336. Quantity: quantityMustParse("1Gi"),
  337. },
  338. },
  339. },
  340. },
  341. "disable via 0%": {
  342. allocatableConfig: []string{},
  343. evictionHard: map[string]string{"memory.available": "0%"},
  344. evictionSoft: map[string]string{"memory.available": "0%"},
  345. expectErr: false,
  346. expectThresholds: []evictionapi.Threshold{},
  347. },
  348. "disable via 100%": {
  349. allocatableConfig: []string{},
  350. evictionHard: map[string]string{"memory.available": "100%"},
  351. evictionSoft: map[string]string{"memory.available": "100%"},
  352. expectErr: false,
  353. expectThresholds: []evictionapi.Threshold{},
  354. },
  355. "invalid-signal": {
  356. allocatableConfig: []string{},
  357. evictionHard: map[string]string{"mem.available": "150Mi"},
  358. evictionSoft: map[string]string{},
  359. evictionSoftGracePeriod: map[string]string{},
  360. evictionMinReclaim: map[string]string{},
  361. expectErr: true,
  362. expectThresholds: []evictionapi.Threshold{},
  363. },
  364. "hard-signal-negative": {
  365. allocatableConfig: []string{},
  366. evictionHard: map[string]string{"memory.available": "-150Mi"},
  367. evictionSoft: map[string]string{},
  368. evictionSoftGracePeriod: map[string]string{},
  369. evictionMinReclaim: map[string]string{},
  370. expectErr: true,
  371. expectThresholds: []evictionapi.Threshold{},
  372. },
  373. "hard-signal-negative-percentage": {
  374. allocatableConfig: []string{},
  375. evictionHard: map[string]string{"memory.available": "-15%"},
  376. evictionSoft: map[string]string{},
  377. evictionSoftGracePeriod: map[string]string{},
  378. evictionMinReclaim: map[string]string{},
  379. expectErr: true,
  380. expectThresholds: []evictionapi.Threshold{},
  381. },
  382. "soft-signal-negative": {
  383. allocatableConfig: []string{},
  384. evictionHard: map[string]string{},
  385. evictionSoft: map[string]string{"memory.available": "-150Mi"},
  386. evictionSoftGracePeriod: map[string]string{},
  387. evictionMinReclaim: map[string]string{},
  388. expectErr: true,
  389. expectThresholds: []evictionapi.Threshold{},
  390. },
  391. "valid-and-invalid-signal": {
  392. allocatableConfig: []string{},
  393. evictionHard: map[string]string{"memory.available": "150Mi", "invalid.foo": "150Mi"},
  394. evictionSoft: map[string]string{},
  395. evictionSoftGracePeriod: map[string]string{},
  396. evictionMinReclaim: map[string]string{},
  397. expectErr: true,
  398. expectThresholds: []evictionapi.Threshold{},
  399. },
  400. "soft-no-grace-period": {
  401. allocatableConfig: []string{},
  402. evictionHard: map[string]string{},
  403. evictionSoft: map[string]string{"memory.available": "150Mi"},
  404. evictionSoftGracePeriod: map[string]string{},
  405. evictionMinReclaim: map[string]string{},
  406. expectErr: true,
  407. expectThresholds: []evictionapi.Threshold{},
  408. },
  409. "soft-negative-grace-period": {
  410. allocatableConfig: []string{},
  411. evictionHard: map[string]string{},
  412. evictionSoft: map[string]string{"memory.available": "150Mi"},
  413. evictionSoftGracePeriod: map[string]string{"memory.available": "-30s"},
  414. evictionMinReclaim: map[string]string{},
  415. expectErr: true,
  416. expectThresholds: []evictionapi.Threshold{},
  417. },
  418. "negative-reclaim": {
  419. allocatableConfig: []string{},
  420. evictionHard: map[string]string{},
  421. evictionSoft: map[string]string{},
  422. evictionSoftGracePeriod: map[string]string{},
  423. evictionMinReclaim: map[string]string{"memory.available": "-300Mi"},
  424. expectErr: true,
  425. expectThresholds: []evictionapi.Threshold{},
  426. },
  427. }
  428. for testName, testCase := range testCases {
  429. thresholds, err := ParseThresholdConfig(testCase.allocatableConfig, testCase.evictionHard, testCase.evictionSoft, testCase.evictionSoftGracePeriod, testCase.evictionMinReclaim)
  430. if testCase.expectErr != (err != nil) {
  431. t.Errorf("Err not as expected, test: %v, error expected: %v, actual: %v", testName, testCase.expectErr, err)
  432. }
  433. if !thresholdsEqual(testCase.expectThresholds, thresholds) {
  434. t.Errorf("thresholds not as expected, test: %v, expected: %v, actual: %v", testName, testCase.expectThresholds, thresholds)
  435. }
  436. }
  437. }
  438. func thresholdsEqual(expected []evictionapi.Threshold, actual []evictionapi.Threshold) bool {
  439. if len(expected) != len(actual) {
  440. return false
  441. }
  442. for _, aThreshold := range expected {
  443. equal := false
  444. for _, bThreshold := range actual {
  445. if thresholdEqual(aThreshold, bThreshold) {
  446. equal = true
  447. }
  448. }
  449. if !equal {
  450. return false
  451. }
  452. }
  453. for _, aThreshold := range actual {
  454. equal := false
  455. for _, bThreshold := range expected {
  456. if thresholdEqual(aThreshold, bThreshold) {
  457. equal = true
  458. }
  459. }
  460. if !equal {
  461. return false
  462. }
  463. }
  464. return true
  465. }
  466. func thresholdEqual(a evictionapi.Threshold, b evictionapi.Threshold) bool {
  467. return a.GracePeriod == b.GracePeriod &&
  468. a.Operator == b.Operator &&
  469. a.Signal == b.Signal &&
  470. compareThresholdValue(*a.MinReclaim, *b.MinReclaim) &&
  471. compareThresholdValue(a.Value, b.Value)
  472. }
  473. func TestOrderedByExceedsRequestMemory(t *testing.T) {
  474. below := newPod("below-requests", -1, []v1.Container{
  475. newContainer("below-requests", newResourceList("", "200Mi", ""), newResourceList("", "", "")),
  476. }, nil)
  477. exceeds := newPod("exceeds-requests", 1, []v1.Container{
  478. newContainer("exceeds-requests", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
  479. }, nil)
  480. stats := map[*v1.Pod]statsapi.PodStats{
  481. below: newPodMemoryStats(below, resource.MustParse("199Mi")), // -1 relative to request
  482. exceeds: newPodMemoryStats(exceeds, resource.MustParse("101Mi")), // 1 relative to request
  483. }
  484. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  485. result, found := stats[pod]
  486. return result, found
  487. }
  488. pods := []*v1.Pod{below, exceeds}
  489. orderedBy(exceedMemoryRequests(statsFn)).Sort(pods)
  490. expected := []*v1.Pod{exceeds, below}
  491. for i := range expected {
  492. if pods[i] != expected[i] {
  493. t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
  494. }
  495. }
  496. }
  497. func TestOrderedByExceedsRequestDisk(t *testing.T) {
  498. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LocalStorageCapacityIsolation, true)()
  499. below := newPod("below-requests", -1, []v1.Container{
  500. newContainer("below-requests", v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("200Mi")}, newResourceList("", "", "")),
  501. }, nil)
  502. exceeds := newPod("exceeds-requests", 1, []v1.Container{
  503. newContainer("exceeds-requests", v1.ResourceList{v1.ResourceEphemeralStorage: resource.MustParse("100Mi")}, newResourceList("", "", "")),
  504. }, nil)
  505. stats := map[*v1.Pod]statsapi.PodStats{
  506. below: newPodDiskStats(below, resource.MustParse("100Mi"), resource.MustParse("99Mi"), resource.MustParse("0Mi")), // -1 relative to request
  507. exceeds: newPodDiskStats(exceeds, resource.MustParse("90Mi"), resource.MustParse("11Mi"), resource.MustParse("0Mi")), // 1 relative to request
  508. }
  509. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  510. result, found := stats[pod]
  511. return result, found
  512. }
  513. pods := []*v1.Pod{below, exceeds}
  514. orderedBy(exceedDiskRequests(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)).Sort(pods)
  515. expected := []*v1.Pod{exceeds, below}
  516. for i := range expected {
  517. if pods[i] != expected[i] {
  518. t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
  519. }
  520. }
  521. }
  522. func TestOrderedByPriority(t *testing.T) {
  523. low := newPod("low-priority", -134, []v1.Container{
  524. newContainer("low-priority", newResourceList("", "", ""), newResourceList("", "", "")),
  525. }, nil)
  526. medium := newPod("medium-priority", 1, []v1.Container{
  527. newContainer("medium-priority", newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", "")),
  528. }, nil)
  529. high := newPod("high-priority", 12534, []v1.Container{
  530. newContainer("high-priority", newResourceList("200m", "200Mi", ""), newResourceList("200m", "200Mi", "")),
  531. }, nil)
  532. pods := []*v1.Pod{high, medium, low}
  533. orderedBy(priority).Sort(pods)
  534. expected := []*v1.Pod{low, medium, high}
  535. for i := range expected {
  536. if pods[i] != expected[i] {
  537. t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
  538. }
  539. }
  540. }
  541. func TestOrderedbyDisk(t *testing.T) {
  542. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LocalStorageCapacityIsolation, true)()
  543. pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{
  544. newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")),
  545. }, []v1.Volume{
  546. newVolume("local-volume", v1.VolumeSource{
  547. EmptyDir: &v1.EmptyDirVolumeSource{},
  548. }),
  549. })
  550. pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{
  551. newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")),
  552. }, []v1.Volume{
  553. newVolume("local-volume", v1.VolumeSource{
  554. EmptyDir: &v1.EmptyDirVolumeSource{},
  555. }),
  556. })
  557. pod3 := newPod("burstable-high", defaultPriority, []v1.Container{
  558. newContainer("burstable-high", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")),
  559. }, []v1.Volume{
  560. newVolume("local-volume", v1.VolumeSource{
  561. EmptyDir: &v1.EmptyDirVolumeSource{},
  562. }),
  563. })
  564. pod4 := newPod("burstable-low", defaultPriority, []v1.Container{
  565. newContainer("burstable-low", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")),
  566. }, []v1.Volume{
  567. newVolume("local-volume", v1.VolumeSource{
  568. EmptyDir: &v1.EmptyDirVolumeSource{},
  569. }),
  570. })
  571. pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{
  572. newContainer("guaranteed-high", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")),
  573. }, []v1.Volume{
  574. newVolume("local-volume", v1.VolumeSource{
  575. EmptyDir: &v1.EmptyDirVolumeSource{},
  576. }),
  577. })
  578. pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{
  579. newContainer("guaranteed-low", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")),
  580. }, []v1.Volume{
  581. newVolume("local-volume", v1.VolumeSource{
  582. EmptyDir: &v1.EmptyDirVolumeSource{},
  583. }),
  584. })
  585. stats := map[*v1.Pod]statsapi.PodStats{
  586. pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("150Mi")), // 300Mi - 0 = 300Mi
  587. pod2: newPodDiskStats(pod2, resource.MustParse("25Mi"), resource.MustParse("25Mi"), resource.MustParse("50Mi")), // 100Mi - 0 = 100Mi
  588. pod3: newPodDiskStats(pod3, resource.MustParse("150Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 350Mi - 100Mi = 250Mi
  589. pod4: newPodDiskStats(pod4, resource.MustParse("25Mi"), resource.MustParse("35Mi"), resource.MustParse("50Mi")), // 110Mi - 100Mi = 10Mi
  590. pod5: newPodDiskStats(pod5, resource.MustParse("225Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 375Mi - 400Mi = -25Mi
  591. pod6: newPodDiskStats(pod6, resource.MustParse("25Mi"), resource.MustParse("45Mi"), resource.MustParse("50Mi")), // 120Mi - 400Mi = -280Mi
  592. }
  593. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  594. result, found := stats[pod]
  595. return result, found
  596. }
  597. pods := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
  598. orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)).Sort(pods)
  599. expected := []*v1.Pod{pod1, pod3, pod2, pod4, pod5, pod6}
  600. for i := range expected {
  601. if pods[i] != expected[i] {
  602. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  603. }
  604. }
  605. }
  606. // Tests that we correctly ignore disk requests when the local storage feature gate is disabled.
  607. func TestOrderedbyDiskDisableLocalStorage(t *testing.T) {
  608. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LocalStorageCapacityIsolation, false)()
  609. pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{
  610. newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")),
  611. }, []v1.Volume{
  612. newVolume("local-volume", v1.VolumeSource{
  613. EmptyDir: &v1.EmptyDirVolumeSource{},
  614. }),
  615. })
  616. pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{
  617. newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")),
  618. }, []v1.Volume{
  619. newVolume("local-volume", v1.VolumeSource{
  620. EmptyDir: &v1.EmptyDirVolumeSource{},
  621. }),
  622. })
  623. pod3 := newPod("burstable-high", defaultPriority, []v1.Container{
  624. newContainer("burstable-high", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")),
  625. }, []v1.Volume{
  626. newVolume("local-volume", v1.VolumeSource{
  627. EmptyDir: &v1.EmptyDirVolumeSource{},
  628. }),
  629. })
  630. pod4 := newPod("burstable-low", defaultPriority, []v1.Container{
  631. newContainer("burstable-low", newResourceList("", "", "100Mi"), newResourceList("", "", "400Mi")),
  632. }, []v1.Volume{
  633. newVolume("local-volume", v1.VolumeSource{
  634. EmptyDir: &v1.EmptyDirVolumeSource{},
  635. }),
  636. })
  637. pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{
  638. newContainer("guaranteed-high", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")),
  639. }, []v1.Volume{
  640. newVolume("local-volume", v1.VolumeSource{
  641. EmptyDir: &v1.EmptyDirVolumeSource{},
  642. }),
  643. })
  644. pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{
  645. newContainer("guaranteed-low", newResourceList("", "", "400Mi"), newResourceList("", "", "400Mi")),
  646. }, []v1.Volume{
  647. newVolume("local-volume", v1.VolumeSource{
  648. EmptyDir: &v1.EmptyDirVolumeSource{},
  649. }),
  650. })
  651. stats := map[*v1.Pod]statsapi.PodStats{
  652. pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("150Mi")), // 300Mi
  653. pod2: newPodDiskStats(pod2, resource.MustParse("25Mi"), resource.MustParse("25Mi"), resource.MustParse("50Mi")), // 100Mi
  654. pod3: newPodDiskStats(pod3, resource.MustParse("150Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 350Mi
  655. pod4: newPodDiskStats(pod4, resource.MustParse("25Mi"), resource.MustParse("35Mi"), resource.MustParse("50Mi")), // 110Mi
  656. pod5: newPodDiskStats(pod5, resource.MustParse("225Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 375Mi
  657. pod6: newPodDiskStats(pod6, resource.MustParse("25Mi"), resource.MustParse("45Mi"), resource.MustParse("50Mi")), // 120Mi
  658. }
  659. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  660. result, found := stats[pod]
  661. return result, found
  662. }
  663. pods := []*v1.Pod{pod1, pod3, pod2, pod4, pod5, pod6}
  664. orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)).Sort(pods)
  665. expected := []*v1.Pod{pod5, pod3, pod1, pod6, pod4, pod2}
  666. for i := range expected {
  667. if pods[i] != expected[i] {
  668. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  669. }
  670. }
  671. }
  672. func TestOrderedbyInodes(t *testing.T) {
  673. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LocalStorageCapacityIsolation, true)()
  674. low := newPod("low", defaultPriority, []v1.Container{
  675. newContainer("low", newResourceList("", "", ""), newResourceList("", "", "")),
  676. }, []v1.Volume{
  677. newVolume("local-volume", v1.VolumeSource{
  678. EmptyDir: &v1.EmptyDirVolumeSource{},
  679. }),
  680. })
  681. medium := newPod("medium", defaultPriority, []v1.Container{
  682. newContainer("medium", newResourceList("", "", ""), newResourceList("", "", "")),
  683. }, []v1.Volume{
  684. newVolume("local-volume", v1.VolumeSource{
  685. EmptyDir: &v1.EmptyDirVolumeSource{},
  686. }),
  687. })
  688. high := newPod("high", defaultPriority, []v1.Container{
  689. newContainer("high", newResourceList("", "", ""), newResourceList("", "", "")),
  690. }, []v1.Volume{
  691. newVolume("local-volume", v1.VolumeSource{
  692. EmptyDir: &v1.EmptyDirVolumeSource{},
  693. }),
  694. })
  695. stats := map[*v1.Pod]statsapi.PodStats{
  696. low: newPodInodeStats(low, resource.MustParse("50000"), resource.MustParse("100000"), resource.MustParse("50000")), // 200000
  697. medium: newPodInodeStats(medium, resource.MustParse("100000"), resource.MustParse("150000"), resource.MustParse("50000")), // 300000
  698. high: newPodInodeStats(high, resource.MustParse("200000"), resource.MustParse("150000"), resource.MustParse("50000")), // 400000
  699. }
  700. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  701. result, found := stats[pod]
  702. return result, found
  703. }
  704. pods := []*v1.Pod{low, medium, high}
  705. orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)).Sort(pods)
  706. expected := []*v1.Pod{high, medium, low}
  707. for i := range expected {
  708. if pods[i] != expected[i] {
  709. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  710. }
  711. }
  712. }
  713. // TestOrderedByPriorityDisk ensures we order pods by priority and then greediest resource consumer
  714. func TestOrderedByPriorityDisk(t *testing.T) {
  715. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LocalStorageCapacityIsolation, true)()
  716. pod1 := newPod("above-requests-low-priority-high-usage", lowPriority, []v1.Container{
  717. newContainer("above-requests-low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  718. }, []v1.Volume{
  719. newVolume("local-volume", v1.VolumeSource{
  720. EmptyDir: &v1.EmptyDirVolumeSource{},
  721. }),
  722. })
  723. pod2 := newPod("above-requests-low-priority-low-usage", lowPriority, []v1.Container{
  724. newContainer("above-requests-low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  725. }, []v1.Volume{
  726. newVolume("local-volume", v1.VolumeSource{
  727. EmptyDir: &v1.EmptyDirVolumeSource{},
  728. }),
  729. })
  730. pod3 := newPod("above-requests-high-priority-high-usage", highPriority, []v1.Container{
  731. newContainer("above-requests-high-priority-high-usage", newResourceList("", "", "100Mi"), newResourceList("", "", "")),
  732. }, []v1.Volume{
  733. newVolume("local-volume", v1.VolumeSource{
  734. EmptyDir: &v1.EmptyDirVolumeSource{},
  735. }),
  736. })
  737. pod4 := newPod("above-requests-high-priority-low-usage", highPriority, []v1.Container{
  738. newContainer("above-requests-high-priority-low-usage", newResourceList("", "", "100Mi"), newResourceList("", "", "")),
  739. }, []v1.Volume{
  740. newVolume("local-volume", v1.VolumeSource{
  741. EmptyDir: &v1.EmptyDirVolumeSource{},
  742. }),
  743. })
  744. pod5 := newPod("below-requests-low-priority-high-usage", lowPriority, []v1.Container{
  745. newContainer("below-requests-low-priority-high-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
  746. }, []v1.Volume{
  747. newVolume("local-volume", v1.VolumeSource{
  748. EmptyDir: &v1.EmptyDirVolumeSource{},
  749. }),
  750. })
  751. pod6 := newPod("below-requests-low-priority-low-usage", lowPriority, []v1.Container{
  752. newContainer("below-requests-low-priority-low-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
  753. }, []v1.Volume{
  754. newVolume("local-volume", v1.VolumeSource{
  755. EmptyDir: &v1.EmptyDirVolumeSource{},
  756. }),
  757. })
  758. pod7 := newPod("below-requests-high-priority-high-usage", highPriority, []v1.Container{
  759. newContainer("below-requests-high-priority-high-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
  760. }, []v1.Volume{
  761. newVolume("local-volume", v1.VolumeSource{
  762. EmptyDir: &v1.EmptyDirVolumeSource{},
  763. }),
  764. })
  765. pod8 := newPod("below-requests-high-priority-low-usage", highPriority, []v1.Container{
  766. newContainer("below-requests-high-priority-low-usage", newResourceList("", "", "1Gi"), newResourceList("", "", "")),
  767. }, []v1.Volume{
  768. newVolume("local-volume", v1.VolumeSource{
  769. EmptyDir: &v1.EmptyDirVolumeSource{},
  770. }),
  771. })
  772. stats := map[*v1.Pod]statsapi.PodStats{
  773. pod1: newPodDiskStats(pod1, resource.MustParse("200Mi"), resource.MustParse("100Mi"), resource.MustParse("200Mi")), // 500 relative to request
  774. pod2: newPodDiskStats(pod2, resource.MustParse("10Mi"), resource.MustParse("10Mi"), resource.MustParse("30Mi")), // 50 relative to request
  775. pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("250Mi")), // 500 relative to request
  776. pod4: newPodDiskStats(pod4, resource.MustParse("90Mi"), resource.MustParse("50Mi"), resource.MustParse("10Mi")), // 50 relative to request
  777. pod5: newPodDiskStats(pod5, resource.MustParse("500Mi"), resource.MustParse("200Mi"), resource.MustParse("100Mi")), // -200 relative to request
  778. pod6: newPodDiskStats(pod6, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // -800 relative to request
  779. pod7: newPodDiskStats(pod7, resource.MustParse("250Mi"), resource.MustParse("500Mi"), resource.MustParse("50Mi")), // -200 relative to request
  780. pod8: newPodDiskStats(pod8, resource.MustParse("100Mi"), resource.MustParse("60Mi"), resource.MustParse("40Mi")), // -800 relative to request
  781. }
  782. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  783. result, found := stats[pod]
  784. return result, found
  785. }
  786. pods := []*v1.Pod{pod8, pod7, pod6, pod5, pod4, pod3, pod2, pod1}
  787. expected := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8}
  788. fsStatsToMeasure := []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}
  789. orderedBy(exceedDiskRequests(statsFn, fsStatsToMeasure, v1.ResourceEphemeralStorage), priority, disk(statsFn, fsStatsToMeasure, v1.ResourceEphemeralStorage)).Sort(pods)
  790. for i := range expected {
  791. if pods[i] != expected[i] {
  792. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  793. }
  794. }
  795. }
  796. // TestOrderedByPriorityInodes ensures we order pods by priority and then greediest resource consumer
  797. func TestOrderedByPriorityInodes(t *testing.T) {
  798. pod1 := newPod("low-priority-high-usage", lowPriority, []v1.Container{
  799. newContainer("low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  800. }, []v1.Volume{
  801. newVolume("local-volume", v1.VolumeSource{
  802. EmptyDir: &v1.EmptyDirVolumeSource{},
  803. }),
  804. })
  805. pod2 := newPod("low-priority-low-usage", lowPriority, []v1.Container{
  806. newContainer("low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  807. }, []v1.Volume{
  808. newVolume("local-volume", v1.VolumeSource{
  809. EmptyDir: &v1.EmptyDirVolumeSource{},
  810. }),
  811. })
  812. pod3 := newPod("high-priority-high-usage", highPriority, []v1.Container{
  813. newContainer("high-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  814. }, []v1.Volume{
  815. newVolume("local-volume", v1.VolumeSource{
  816. EmptyDir: &v1.EmptyDirVolumeSource{},
  817. }),
  818. })
  819. pod4 := newPod("high-priority-low-usage", highPriority, []v1.Container{
  820. newContainer("high-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  821. }, []v1.Volume{
  822. newVolume("local-volume", v1.VolumeSource{
  823. EmptyDir: &v1.EmptyDirVolumeSource{},
  824. }),
  825. })
  826. stats := map[*v1.Pod]statsapi.PodStats{
  827. pod1: newPodInodeStats(pod1, resource.MustParse("50000"), resource.MustParse("100000"), resource.MustParse("250000")), // 400000
  828. pod2: newPodInodeStats(pod2, resource.MustParse("60000"), resource.MustParse("30000"), resource.MustParse("10000")), // 100000
  829. pod3: newPodInodeStats(pod3, resource.MustParse("150000"), resource.MustParse("150000"), resource.MustParse("50000")), // 350000
  830. pod4: newPodInodeStats(pod4, resource.MustParse("10000"), resource.MustParse("40000"), resource.MustParse("100000")), // 150000
  831. }
  832. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  833. result, found := stats[pod]
  834. return result, found
  835. }
  836. pods := []*v1.Pod{pod4, pod3, pod2, pod1}
  837. orderedBy(priority, disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)).Sort(pods)
  838. expected := []*v1.Pod{pod1, pod2, pod3, pod4}
  839. for i := range expected {
  840. if pods[i] != expected[i] {
  841. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  842. }
  843. }
  844. }
  845. // TestOrderedByMemory ensures we order pods by greediest memory consumer relative to request.
  846. func TestOrderedByMemory(t *testing.T) {
  847. pod1 := newPod("best-effort-high", defaultPriority, []v1.Container{
  848. newContainer("best-effort-high", newResourceList("", "", ""), newResourceList("", "", "")),
  849. }, nil)
  850. pod2 := newPod("best-effort-low", defaultPriority, []v1.Container{
  851. newContainer("best-effort-low", newResourceList("", "", ""), newResourceList("", "", "")),
  852. }, nil)
  853. pod3 := newPod("burstable-high", defaultPriority, []v1.Container{
  854. newContainer("burstable-high", newResourceList("", "100Mi", ""), newResourceList("", "1Gi", "")),
  855. }, nil)
  856. pod4 := newPod("burstable-low", defaultPriority, []v1.Container{
  857. newContainer("burstable-low", newResourceList("", "100Mi", ""), newResourceList("", "1Gi", "")),
  858. }, nil)
  859. pod5 := newPod("guaranteed-high", defaultPriority, []v1.Container{
  860. newContainer("guaranteed-high", newResourceList("", "1Gi", ""), newResourceList("", "1Gi", "")),
  861. }, nil)
  862. pod6 := newPod("guaranteed-low", defaultPriority, []v1.Container{
  863. newContainer("guaranteed-low", newResourceList("", "1Gi", ""), newResourceList("", "1Gi", "")),
  864. }, nil)
  865. stats := map[*v1.Pod]statsapi.PodStats{
  866. pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request
  867. pod2: newPodMemoryStats(pod2, resource.MustParse("300Mi")), // 300 relative to request
  868. pod3: newPodMemoryStats(pod3, resource.MustParse("800Mi")), // 700 relative to request
  869. pod4: newPodMemoryStats(pod4, resource.MustParse("300Mi")), // 200 relative to request
  870. pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request
  871. pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request
  872. }
  873. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  874. result, found := stats[pod]
  875. return result, found
  876. }
  877. pods := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
  878. orderedBy(memory(statsFn)).Sort(pods)
  879. expected := []*v1.Pod{pod3, pod1, pod2, pod4, pod5, pod6}
  880. for i := range expected {
  881. if pods[i] != expected[i] {
  882. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  883. }
  884. }
  885. }
  886. // TestOrderedByPriorityMemory ensures we order by priority and then memory consumption relative to request.
  887. func TestOrderedByPriorityMemory(t *testing.T) {
  888. pod1 := newPod("above-requests-low-priority-high-usage", lowPriority, []v1.Container{
  889. newContainer("above-requests-low-priority-high-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  890. }, nil)
  891. pod2 := newPod("above-requests-low-priority-low-usage", lowPriority, []v1.Container{
  892. newContainer("above-requests-low-priority-low-usage", newResourceList("", "", ""), newResourceList("", "", "")),
  893. }, nil)
  894. pod3 := newPod("above-requests-high-priority-high-usage", highPriority, []v1.Container{
  895. newContainer("above-requests-high-priority-high-usage", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
  896. }, nil)
  897. pod4 := newPod("above-requests-high-priority-low-usage", highPriority, []v1.Container{
  898. newContainer("above-requests-high-priority-low-usage", newResourceList("", "100Mi", ""), newResourceList("", "", "")),
  899. }, nil)
  900. pod5 := newPod("below-requests-low-priority-high-usage", lowPriority, []v1.Container{
  901. newContainer("below-requests-low-priority-high-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
  902. }, nil)
  903. pod6 := newPod("below-requests-low-priority-low-usage", lowPriority, []v1.Container{
  904. newContainer("below-requests-low-priority-low-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
  905. }, nil)
  906. pod7 := newPod("below-requests-high-priority-high-usage", highPriority, []v1.Container{
  907. newContainer("below-requests-high-priority-high-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
  908. }, nil)
  909. pod8 := newPod("below-requests-high-priority-low-usage", highPriority, []v1.Container{
  910. newContainer("below-requests-high-priority-low-usage", newResourceList("", "1Gi", ""), newResourceList("", "", "")),
  911. }, nil)
  912. stats := map[*v1.Pod]statsapi.PodStats{
  913. pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request
  914. pod2: newPodMemoryStats(pod2, resource.MustParse("50Mi")), // 50 relative to request
  915. pod3: newPodMemoryStats(pod3, resource.MustParse("600Mi")), // 500 relative to request
  916. pod4: newPodMemoryStats(pod4, resource.MustParse("150Mi")), // 50 relative to request
  917. pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request
  918. pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request
  919. pod7: newPodMemoryStats(pod7, resource.MustParse("800Mi")), // -200 relative to request
  920. pod8: newPodMemoryStats(pod8, resource.MustParse("200Mi")), // -800 relative to request
  921. }
  922. statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
  923. result, found := stats[pod]
  924. return result, found
  925. }
  926. pods := []*v1.Pod{pod8, pod7, pod6, pod5, pod4, pod3, pod2, pod1}
  927. expected := []*v1.Pod{pod1, pod2, pod3, pod4, pod5, pod6, pod7, pod8}
  928. orderedBy(exceedMemoryRequests(statsFn), priority, memory(statsFn)).Sort(pods)
  929. for i := range expected {
  930. if pods[i] != expected[i] {
  931. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  932. }
  933. }
  934. }
  935. func TestSortByEvictionPriority(t *testing.T) {
  936. for _, tc := range []struct {
  937. name string
  938. thresholds []evictionapi.Threshold
  939. expected []evictionapi.Threshold
  940. }{
  941. {
  942. name: "empty threshold list",
  943. thresholds: []evictionapi.Threshold{},
  944. expected: []evictionapi.Threshold{},
  945. },
  946. {
  947. name: "memory first",
  948. thresholds: []evictionapi.Threshold{
  949. {
  950. Signal: evictionapi.SignalNodeFsAvailable,
  951. },
  952. {
  953. Signal: evictionapi.SignalPIDAvailable,
  954. },
  955. {
  956. Signal: evictionapi.SignalMemoryAvailable,
  957. },
  958. },
  959. expected: []evictionapi.Threshold{
  960. {
  961. Signal: evictionapi.SignalMemoryAvailable,
  962. },
  963. {
  964. Signal: evictionapi.SignalNodeFsAvailable,
  965. },
  966. {
  967. Signal: evictionapi.SignalPIDAvailable,
  968. },
  969. },
  970. },
  971. {
  972. name: "allocatable memory first",
  973. thresholds: []evictionapi.Threshold{
  974. {
  975. Signal: evictionapi.SignalNodeFsAvailable,
  976. },
  977. {
  978. Signal: evictionapi.SignalPIDAvailable,
  979. },
  980. {
  981. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  982. },
  983. },
  984. expected: []evictionapi.Threshold{
  985. {
  986. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  987. },
  988. {
  989. Signal: evictionapi.SignalNodeFsAvailable,
  990. },
  991. {
  992. Signal: evictionapi.SignalPIDAvailable,
  993. },
  994. },
  995. },
  996. } {
  997. t.Run(tc.name, func(t *testing.T) {
  998. sort.Sort(byEvictionPriority(tc.thresholds))
  999. for i := range tc.expected {
  1000. if tc.thresholds[i].Signal != tc.expected[i].Signal {
  1001. t.Errorf("At index %d, expected threshold with signal %s, but got %s", i, tc.expected[i].Signal, tc.thresholds[i].Signal)
  1002. }
  1003. }
  1004. })
  1005. }
  1006. }
  1007. type fakeSummaryProvider struct {
  1008. result *statsapi.Summary
  1009. }
  1010. func (f *fakeSummaryProvider) Get(updateStats bool) (*statsapi.Summary, error) {
  1011. return f.result, nil
  1012. }
  1013. func (f *fakeSummaryProvider) GetCPUAndMemoryStats() (*statsapi.Summary, error) {
  1014. return f.result, nil
  1015. }
  1016. // newPodStats returns a pod stat where each container is using the specified working set
  1017. // each pod must have a Name, UID, Namespace
  1018. func newPodStats(pod *v1.Pod, podWorkingSetBytes uint64) statsapi.PodStats {
  1019. return statsapi.PodStats{
  1020. PodRef: statsapi.PodReference{
  1021. Name: pod.Name,
  1022. Namespace: pod.Namespace,
  1023. UID: string(pod.UID),
  1024. },
  1025. Memory: &statsapi.MemoryStats{
  1026. WorkingSetBytes: &podWorkingSetBytes,
  1027. },
  1028. }
  1029. }
  1030. func TestMakeSignalObservations(t *testing.T) {
  1031. podMaker := func(name, namespace, uid string, numContainers int) *v1.Pod {
  1032. pod := &v1.Pod{}
  1033. pod.Name = name
  1034. pod.Namespace = namespace
  1035. pod.UID = types.UID(uid)
  1036. pod.Spec = v1.PodSpec{}
  1037. for i := 0; i < numContainers; i++ {
  1038. pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{
  1039. Name: fmt.Sprintf("ctr%v", i),
  1040. })
  1041. }
  1042. return pod
  1043. }
  1044. nodeAvailableBytes := uint64(1024 * 1024 * 1024)
  1045. nodeWorkingSetBytes := uint64(1024 * 1024 * 1024)
  1046. allocatableMemoryCapacity := uint64(5 * 1024 * 1024 * 1024)
  1047. imageFsAvailableBytes := uint64(1024 * 1024)
  1048. imageFsCapacityBytes := uint64(1024 * 1024 * 2)
  1049. nodeFsAvailableBytes := uint64(1024)
  1050. nodeFsCapacityBytes := uint64(1024 * 2)
  1051. imageFsInodesFree := uint64(1024)
  1052. imageFsInodes := uint64(1024 * 1024)
  1053. nodeFsInodesFree := uint64(1024)
  1054. nodeFsInodes := uint64(1024 * 1024)
  1055. fakeStats := &statsapi.Summary{
  1056. Node: statsapi.NodeStats{
  1057. Memory: &statsapi.MemoryStats{
  1058. AvailableBytes: &nodeAvailableBytes,
  1059. WorkingSetBytes: &nodeWorkingSetBytes,
  1060. },
  1061. Runtime: &statsapi.RuntimeStats{
  1062. ImageFs: &statsapi.FsStats{
  1063. AvailableBytes: &imageFsAvailableBytes,
  1064. CapacityBytes: &imageFsCapacityBytes,
  1065. InodesFree: &imageFsInodesFree,
  1066. Inodes: &imageFsInodes,
  1067. },
  1068. },
  1069. Fs: &statsapi.FsStats{
  1070. AvailableBytes: &nodeFsAvailableBytes,
  1071. CapacityBytes: &nodeFsCapacityBytes,
  1072. InodesFree: &nodeFsInodesFree,
  1073. Inodes: &nodeFsInodes,
  1074. },
  1075. SystemContainers: []statsapi.ContainerStats{
  1076. {
  1077. Name: statsapi.SystemContainerPods,
  1078. Memory: &statsapi.MemoryStats{
  1079. AvailableBytes: &nodeAvailableBytes,
  1080. WorkingSetBytes: &nodeWorkingSetBytes,
  1081. },
  1082. },
  1083. },
  1084. },
  1085. Pods: []statsapi.PodStats{},
  1086. }
  1087. pods := []*v1.Pod{
  1088. podMaker("pod1", "ns1", "uuid1", 1),
  1089. podMaker("pod1", "ns2", "uuid2", 1),
  1090. podMaker("pod3", "ns3", "uuid3", 1),
  1091. }
  1092. podWorkingSetBytes := uint64(1024 * 1024 * 1024)
  1093. for _, pod := range pods {
  1094. fakeStats.Pods = append(fakeStats.Pods, newPodStats(pod, podWorkingSetBytes))
  1095. }
  1096. res := quantityMustParse("5Gi")
  1097. // Allocatable thresholds are always 100%. Verify that Threshold == Capacity.
  1098. if res.CmpInt64(int64(allocatableMemoryCapacity)) != 0 {
  1099. t.Errorf("Expected Threshold %v to be equal to value %v", res.Value(), allocatableMemoryCapacity)
  1100. }
  1101. actualObservations, statsFunc := makeSignalObservations(fakeStats)
  1102. allocatableMemQuantity, found := actualObservations[evictionapi.SignalAllocatableMemoryAvailable]
  1103. if !found {
  1104. t.Errorf("Expected allocatable memory observation, but didnt find one")
  1105. }
  1106. if expectedBytes := int64(nodeAvailableBytes); allocatableMemQuantity.available.Value() != expectedBytes {
  1107. t.Errorf("Expected %v, actual: %v", expectedBytes, allocatableMemQuantity.available.Value())
  1108. }
  1109. if expectedBytes := int64(nodeWorkingSetBytes + nodeAvailableBytes); allocatableMemQuantity.capacity.Value() != expectedBytes {
  1110. t.Errorf("Expected %v, actual: %v", expectedBytes, allocatableMemQuantity.capacity.Value())
  1111. }
  1112. memQuantity, found := actualObservations[evictionapi.SignalMemoryAvailable]
  1113. if !found {
  1114. t.Error("Expected available memory observation")
  1115. }
  1116. if expectedBytes := int64(nodeAvailableBytes); memQuantity.available.Value() != expectedBytes {
  1117. t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.available.Value())
  1118. }
  1119. if expectedBytes := int64(nodeWorkingSetBytes + nodeAvailableBytes); memQuantity.capacity.Value() != expectedBytes {
  1120. t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.capacity.Value())
  1121. }
  1122. nodeFsQuantity, found := actualObservations[evictionapi.SignalNodeFsAvailable]
  1123. if !found {
  1124. t.Error("Expected available nodefs observation")
  1125. }
  1126. if expectedBytes := int64(nodeFsAvailableBytes); nodeFsQuantity.available.Value() != expectedBytes {
  1127. t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.available.Value())
  1128. }
  1129. if expectedBytes := int64(nodeFsCapacityBytes); nodeFsQuantity.capacity.Value() != expectedBytes {
  1130. t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.capacity.Value())
  1131. }
  1132. nodeFsInodesQuantity, found := actualObservations[evictionapi.SignalNodeFsInodesFree]
  1133. if !found {
  1134. t.Error("Expected inodes free nodefs observation")
  1135. }
  1136. if expected := int64(nodeFsInodesFree); nodeFsInodesQuantity.available.Value() != expected {
  1137. t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.available.Value())
  1138. }
  1139. if expected := int64(nodeFsInodes); nodeFsInodesQuantity.capacity.Value() != expected {
  1140. t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.capacity.Value())
  1141. }
  1142. imageFsQuantity, found := actualObservations[evictionapi.SignalImageFsAvailable]
  1143. if !found {
  1144. t.Error("Expected available imagefs observation")
  1145. }
  1146. if expectedBytes := int64(imageFsAvailableBytes); imageFsQuantity.available.Value() != expectedBytes {
  1147. t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.available.Value())
  1148. }
  1149. if expectedBytes := int64(imageFsCapacityBytes); imageFsQuantity.capacity.Value() != expectedBytes {
  1150. t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.capacity.Value())
  1151. }
  1152. imageFsInodesQuantity, found := actualObservations[evictionapi.SignalImageFsInodesFree]
  1153. if !found {
  1154. t.Error("Expected inodes free imagefs observation")
  1155. }
  1156. if expected := int64(imageFsInodesFree); imageFsInodesQuantity.available.Value() != expected {
  1157. t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.available.Value())
  1158. }
  1159. if expected := int64(imageFsInodes); imageFsInodesQuantity.capacity.Value() != expected {
  1160. t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.capacity.Value())
  1161. }
  1162. for _, pod := range pods {
  1163. podStats, found := statsFunc(pod)
  1164. if !found {
  1165. t.Errorf("Pod stats were not found for pod %v", pod.UID)
  1166. }
  1167. if *podStats.Memory.WorkingSetBytes != podWorkingSetBytes {
  1168. t.Errorf("Pod working set expected %v, actual: %v", podWorkingSetBytes, *podStats.Memory.WorkingSetBytes)
  1169. }
  1170. }
  1171. }
  1172. func TestThresholdsMet(t *testing.T) {
  1173. hardThreshold := evictionapi.Threshold{
  1174. Signal: evictionapi.SignalMemoryAvailable,
  1175. Operator: evictionapi.OpLessThan,
  1176. Value: evictionapi.ThresholdValue{
  1177. Quantity: quantityMustParse("1Gi"),
  1178. },
  1179. MinReclaim: &evictionapi.ThresholdValue{
  1180. Quantity: quantityMustParse("500Mi"),
  1181. },
  1182. }
  1183. testCases := map[string]struct {
  1184. enforceMinReclaim bool
  1185. thresholds []evictionapi.Threshold
  1186. observations signalObservations
  1187. result []evictionapi.Threshold
  1188. }{
  1189. "empty": {
  1190. enforceMinReclaim: false,
  1191. thresholds: []evictionapi.Threshold{},
  1192. observations: signalObservations{},
  1193. result: []evictionapi.Threshold{},
  1194. },
  1195. "threshold-met-memory": {
  1196. enforceMinReclaim: false,
  1197. thresholds: []evictionapi.Threshold{hardThreshold},
  1198. observations: signalObservations{
  1199. evictionapi.SignalMemoryAvailable: signalObservation{
  1200. available: quantityMustParse("500Mi"),
  1201. },
  1202. },
  1203. result: []evictionapi.Threshold{hardThreshold},
  1204. },
  1205. "threshold-not-met": {
  1206. enforceMinReclaim: false,
  1207. thresholds: []evictionapi.Threshold{hardThreshold},
  1208. observations: signalObservations{
  1209. evictionapi.SignalMemoryAvailable: signalObservation{
  1210. available: quantityMustParse("2Gi"),
  1211. },
  1212. },
  1213. result: []evictionapi.Threshold{},
  1214. },
  1215. "threshold-met-with-min-reclaim": {
  1216. enforceMinReclaim: true,
  1217. thresholds: []evictionapi.Threshold{hardThreshold},
  1218. observations: signalObservations{
  1219. evictionapi.SignalMemoryAvailable: signalObservation{
  1220. available: quantityMustParse("1.05Gi"),
  1221. },
  1222. },
  1223. result: []evictionapi.Threshold{hardThreshold},
  1224. },
  1225. "threshold-not-met-with-min-reclaim": {
  1226. enforceMinReclaim: true,
  1227. thresholds: []evictionapi.Threshold{hardThreshold},
  1228. observations: signalObservations{
  1229. evictionapi.SignalMemoryAvailable: signalObservation{
  1230. available: quantityMustParse("2Gi"),
  1231. },
  1232. },
  1233. result: []evictionapi.Threshold{},
  1234. },
  1235. }
  1236. for testName, testCase := range testCases {
  1237. actual := thresholdsMet(testCase.thresholds, testCase.observations, testCase.enforceMinReclaim)
  1238. if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  1239. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1240. }
  1241. }
  1242. }
  1243. func TestThresholdsUpdatedStats(t *testing.T) {
  1244. updatedThreshold := evictionapi.Threshold{
  1245. Signal: evictionapi.SignalMemoryAvailable,
  1246. }
  1247. locationUTC, err := time.LoadLocation("UTC")
  1248. if err != nil {
  1249. t.Error(err)
  1250. return
  1251. }
  1252. testCases := map[string]struct {
  1253. thresholds []evictionapi.Threshold
  1254. observations signalObservations
  1255. last signalObservations
  1256. result []evictionapi.Threshold
  1257. }{
  1258. "empty": {
  1259. thresholds: []evictionapi.Threshold{},
  1260. observations: signalObservations{},
  1261. last: signalObservations{},
  1262. result: []evictionapi.Threshold{},
  1263. },
  1264. "no-time": {
  1265. thresholds: []evictionapi.Threshold{updatedThreshold},
  1266. observations: signalObservations{
  1267. evictionapi.SignalMemoryAvailable: signalObservation{},
  1268. },
  1269. last: signalObservations{},
  1270. result: []evictionapi.Threshold{updatedThreshold},
  1271. },
  1272. "no-last-observation": {
  1273. thresholds: []evictionapi.Threshold{updatedThreshold},
  1274. observations: signalObservations{
  1275. evictionapi.SignalMemoryAvailable: signalObservation{
  1276. time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1277. },
  1278. },
  1279. last: signalObservations{},
  1280. result: []evictionapi.Threshold{updatedThreshold},
  1281. },
  1282. "time-machine": {
  1283. thresholds: []evictionapi.Threshold{updatedThreshold},
  1284. observations: signalObservations{
  1285. evictionapi.SignalMemoryAvailable: signalObservation{
  1286. time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1287. },
  1288. },
  1289. last: signalObservations{
  1290. evictionapi.SignalMemoryAvailable: signalObservation{
  1291. time: metav1.Date(2016, 1, 1, 0, 1, 0, 0, locationUTC),
  1292. },
  1293. },
  1294. result: []evictionapi.Threshold{},
  1295. },
  1296. "same-observation": {
  1297. thresholds: []evictionapi.Threshold{updatedThreshold},
  1298. observations: signalObservations{
  1299. evictionapi.SignalMemoryAvailable: signalObservation{
  1300. time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1301. },
  1302. },
  1303. last: signalObservations{
  1304. evictionapi.SignalMemoryAvailable: signalObservation{
  1305. time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1306. },
  1307. },
  1308. result: []evictionapi.Threshold{},
  1309. },
  1310. "new-observation": {
  1311. thresholds: []evictionapi.Threshold{updatedThreshold},
  1312. observations: signalObservations{
  1313. evictionapi.SignalMemoryAvailable: signalObservation{
  1314. time: metav1.Date(2016, 1, 1, 0, 1, 0, 0, locationUTC),
  1315. },
  1316. },
  1317. last: signalObservations{
  1318. evictionapi.SignalMemoryAvailable: signalObservation{
  1319. time: metav1.Date(2016, 1, 1, 0, 0, 0, 0, locationUTC),
  1320. },
  1321. },
  1322. result: []evictionapi.Threshold{updatedThreshold},
  1323. },
  1324. }
  1325. for testName, testCase := range testCases {
  1326. actual := thresholdsUpdatedStats(testCase.thresholds, testCase.observations, testCase.last)
  1327. if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  1328. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1329. }
  1330. }
  1331. }
  1332. func TestPercentageThresholdsMet(t *testing.T) {
  1333. specificThresholds := []evictionapi.Threshold{
  1334. {
  1335. Signal: evictionapi.SignalMemoryAvailable,
  1336. Operator: evictionapi.OpLessThan,
  1337. Value: evictionapi.ThresholdValue{
  1338. Percentage: 0.2,
  1339. },
  1340. MinReclaim: &evictionapi.ThresholdValue{
  1341. Percentage: 0.05,
  1342. },
  1343. },
  1344. {
  1345. Signal: evictionapi.SignalNodeFsAvailable,
  1346. Operator: evictionapi.OpLessThan,
  1347. Value: evictionapi.ThresholdValue{
  1348. Percentage: 0.3,
  1349. },
  1350. },
  1351. }
  1352. testCases := map[string]struct {
  1353. enforceMinRelaim bool
  1354. thresholds []evictionapi.Threshold
  1355. observations signalObservations
  1356. result []evictionapi.Threshold
  1357. }{
  1358. "BothMet": {
  1359. enforceMinRelaim: false,
  1360. thresholds: specificThresholds,
  1361. observations: signalObservations{
  1362. evictionapi.SignalMemoryAvailable: signalObservation{
  1363. available: quantityMustParse("100Mi"),
  1364. capacity: quantityMustParse("1000Mi"),
  1365. },
  1366. evictionapi.SignalNodeFsAvailable: signalObservation{
  1367. available: quantityMustParse("100Gi"),
  1368. capacity: quantityMustParse("1000Gi"),
  1369. },
  1370. },
  1371. result: specificThresholds,
  1372. },
  1373. "NoneMet": {
  1374. enforceMinRelaim: false,
  1375. thresholds: specificThresholds,
  1376. observations: signalObservations{
  1377. evictionapi.SignalMemoryAvailable: signalObservation{
  1378. available: quantityMustParse("300Mi"),
  1379. capacity: quantityMustParse("1000Mi"),
  1380. },
  1381. evictionapi.SignalNodeFsAvailable: signalObservation{
  1382. available: quantityMustParse("400Gi"),
  1383. capacity: quantityMustParse("1000Gi"),
  1384. },
  1385. },
  1386. result: []evictionapi.Threshold{},
  1387. },
  1388. "DiskMet": {
  1389. enforceMinRelaim: false,
  1390. thresholds: specificThresholds,
  1391. observations: signalObservations{
  1392. evictionapi.SignalMemoryAvailable: signalObservation{
  1393. available: quantityMustParse("300Mi"),
  1394. capacity: quantityMustParse("1000Mi"),
  1395. },
  1396. evictionapi.SignalNodeFsAvailable: signalObservation{
  1397. available: quantityMustParse("100Gi"),
  1398. capacity: quantityMustParse("1000Gi"),
  1399. },
  1400. },
  1401. result: []evictionapi.Threshold{specificThresholds[1]},
  1402. },
  1403. "MemoryMet": {
  1404. enforceMinRelaim: false,
  1405. thresholds: specificThresholds,
  1406. observations: signalObservations{
  1407. evictionapi.SignalMemoryAvailable: signalObservation{
  1408. available: quantityMustParse("100Mi"),
  1409. capacity: quantityMustParse("1000Mi"),
  1410. },
  1411. evictionapi.SignalNodeFsAvailable: signalObservation{
  1412. available: quantityMustParse("400Gi"),
  1413. capacity: quantityMustParse("1000Gi"),
  1414. },
  1415. },
  1416. result: []evictionapi.Threshold{specificThresholds[0]},
  1417. },
  1418. "MemoryMetWithMinReclaim": {
  1419. enforceMinRelaim: true,
  1420. thresholds: specificThresholds,
  1421. observations: signalObservations{
  1422. evictionapi.SignalMemoryAvailable: signalObservation{
  1423. available: quantityMustParse("225Mi"),
  1424. capacity: quantityMustParse("1000Mi"),
  1425. },
  1426. },
  1427. result: []evictionapi.Threshold{specificThresholds[0]},
  1428. },
  1429. "MemoryNotMetWithMinReclaim": {
  1430. enforceMinRelaim: true,
  1431. thresholds: specificThresholds,
  1432. observations: signalObservations{
  1433. evictionapi.SignalMemoryAvailable: signalObservation{
  1434. available: quantityMustParse("300Mi"),
  1435. capacity: quantityMustParse("1000Mi"),
  1436. },
  1437. },
  1438. result: []evictionapi.Threshold{},
  1439. },
  1440. }
  1441. for testName, testCase := range testCases {
  1442. actual := thresholdsMet(testCase.thresholds, testCase.observations, testCase.enforceMinRelaim)
  1443. if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  1444. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1445. }
  1446. }
  1447. }
  1448. func TestThresholdsFirstObservedAt(t *testing.T) {
  1449. hardThreshold := evictionapi.Threshold{
  1450. Signal: evictionapi.SignalMemoryAvailable,
  1451. Operator: evictionapi.OpLessThan,
  1452. Value: evictionapi.ThresholdValue{
  1453. Quantity: quantityMustParse("1Gi"),
  1454. },
  1455. }
  1456. now := metav1.Now()
  1457. oldTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
  1458. testCases := map[string]struct {
  1459. thresholds []evictionapi.Threshold
  1460. lastObservedAt thresholdsObservedAt
  1461. now time.Time
  1462. result thresholdsObservedAt
  1463. }{
  1464. "empty": {
  1465. thresholds: []evictionapi.Threshold{},
  1466. lastObservedAt: thresholdsObservedAt{},
  1467. now: now.Time,
  1468. result: thresholdsObservedAt{},
  1469. },
  1470. "no-previous-observation": {
  1471. thresholds: []evictionapi.Threshold{hardThreshold},
  1472. lastObservedAt: thresholdsObservedAt{},
  1473. now: now.Time,
  1474. result: thresholdsObservedAt{
  1475. hardThreshold: now.Time,
  1476. },
  1477. },
  1478. "previous-observation": {
  1479. thresholds: []evictionapi.Threshold{hardThreshold},
  1480. lastObservedAt: thresholdsObservedAt{
  1481. hardThreshold: oldTime.Time,
  1482. },
  1483. now: now.Time,
  1484. result: thresholdsObservedAt{
  1485. hardThreshold: oldTime.Time,
  1486. },
  1487. },
  1488. }
  1489. for testName, testCase := range testCases {
  1490. actual := thresholdsFirstObservedAt(testCase.thresholds, testCase.lastObservedAt, testCase.now)
  1491. if !reflect.DeepEqual(actual, testCase.result) {
  1492. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1493. }
  1494. }
  1495. }
  1496. func TestThresholdsMetGracePeriod(t *testing.T) {
  1497. now := metav1.Now()
  1498. hardThreshold := evictionapi.Threshold{
  1499. Signal: evictionapi.SignalMemoryAvailable,
  1500. Operator: evictionapi.OpLessThan,
  1501. Value: evictionapi.ThresholdValue{
  1502. Quantity: quantityMustParse("1Gi"),
  1503. },
  1504. }
  1505. softThreshold := evictionapi.Threshold{
  1506. Signal: evictionapi.SignalMemoryAvailable,
  1507. Operator: evictionapi.OpLessThan,
  1508. Value: evictionapi.ThresholdValue{
  1509. Quantity: quantityMustParse("2Gi"),
  1510. },
  1511. GracePeriod: 1 * time.Minute,
  1512. }
  1513. oldTime := metav1.NewTime(now.Time.Add(-2 * time.Minute))
  1514. testCases := map[string]struct {
  1515. observedAt thresholdsObservedAt
  1516. now time.Time
  1517. result []evictionapi.Threshold
  1518. }{
  1519. "empty": {
  1520. observedAt: thresholdsObservedAt{},
  1521. now: now.Time,
  1522. result: []evictionapi.Threshold{},
  1523. },
  1524. "hard-threshold-met": {
  1525. observedAt: thresholdsObservedAt{
  1526. hardThreshold: now.Time,
  1527. },
  1528. now: now.Time,
  1529. result: []evictionapi.Threshold{hardThreshold},
  1530. },
  1531. "soft-threshold-not-met": {
  1532. observedAt: thresholdsObservedAt{
  1533. softThreshold: now.Time,
  1534. },
  1535. now: now.Time,
  1536. result: []evictionapi.Threshold{},
  1537. },
  1538. "soft-threshold-met": {
  1539. observedAt: thresholdsObservedAt{
  1540. softThreshold: oldTime.Time,
  1541. },
  1542. now: now.Time,
  1543. result: []evictionapi.Threshold{softThreshold},
  1544. },
  1545. }
  1546. for testName, testCase := range testCases {
  1547. actual := thresholdsMetGracePeriod(testCase.observedAt, now.Time)
  1548. if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  1549. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1550. }
  1551. }
  1552. }
  1553. func TestNodeConditions(t *testing.T) {
  1554. testCases := map[string]struct {
  1555. inputs []evictionapi.Threshold
  1556. result []v1.NodeConditionType
  1557. }{
  1558. "empty-list": {
  1559. inputs: []evictionapi.Threshold{},
  1560. result: []v1.NodeConditionType{},
  1561. },
  1562. "memory.available": {
  1563. inputs: []evictionapi.Threshold{
  1564. {Signal: evictionapi.SignalMemoryAvailable},
  1565. },
  1566. result: []v1.NodeConditionType{v1.NodeMemoryPressure},
  1567. },
  1568. }
  1569. for testName, testCase := range testCases {
  1570. actual := nodeConditions(testCase.inputs)
  1571. if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
  1572. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1573. }
  1574. }
  1575. }
  1576. func TestNodeConditionsLastObservedAt(t *testing.T) {
  1577. now := metav1.Now()
  1578. oldTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
  1579. testCases := map[string]struct {
  1580. nodeConditions []v1.NodeConditionType
  1581. lastObservedAt nodeConditionsObservedAt
  1582. now time.Time
  1583. result nodeConditionsObservedAt
  1584. }{
  1585. "no-previous-observation": {
  1586. nodeConditions: []v1.NodeConditionType{v1.NodeMemoryPressure},
  1587. lastObservedAt: nodeConditionsObservedAt{},
  1588. now: now.Time,
  1589. result: nodeConditionsObservedAt{
  1590. v1.NodeMemoryPressure: now.Time,
  1591. },
  1592. },
  1593. "previous-observation": {
  1594. nodeConditions: []v1.NodeConditionType{v1.NodeMemoryPressure},
  1595. lastObservedAt: nodeConditionsObservedAt{
  1596. v1.NodeMemoryPressure: oldTime.Time,
  1597. },
  1598. now: now.Time,
  1599. result: nodeConditionsObservedAt{
  1600. v1.NodeMemoryPressure: now.Time,
  1601. },
  1602. },
  1603. "old-observation": {
  1604. nodeConditions: []v1.NodeConditionType{},
  1605. lastObservedAt: nodeConditionsObservedAt{
  1606. v1.NodeMemoryPressure: oldTime.Time,
  1607. },
  1608. now: now.Time,
  1609. result: nodeConditionsObservedAt{
  1610. v1.NodeMemoryPressure: oldTime.Time,
  1611. },
  1612. },
  1613. }
  1614. for testName, testCase := range testCases {
  1615. actual := nodeConditionsLastObservedAt(testCase.nodeConditions, testCase.lastObservedAt, testCase.now)
  1616. if !reflect.DeepEqual(actual, testCase.result) {
  1617. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1618. }
  1619. }
  1620. }
  1621. func TestNodeConditionsObservedSince(t *testing.T) {
  1622. now := metav1.Now()
  1623. observedTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
  1624. testCases := map[string]struct {
  1625. observedAt nodeConditionsObservedAt
  1626. period time.Duration
  1627. now time.Time
  1628. result []v1.NodeConditionType
  1629. }{
  1630. "in-period": {
  1631. observedAt: nodeConditionsObservedAt{
  1632. v1.NodeMemoryPressure: observedTime.Time,
  1633. },
  1634. period: 2 * time.Minute,
  1635. now: now.Time,
  1636. result: []v1.NodeConditionType{v1.NodeMemoryPressure},
  1637. },
  1638. "out-of-period": {
  1639. observedAt: nodeConditionsObservedAt{
  1640. v1.NodeMemoryPressure: observedTime.Time,
  1641. },
  1642. period: 30 * time.Second,
  1643. now: now.Time,
  1644. result: []v1.NodeConditionType{},
  1645. },
  1646. }
  1647. for testName, testCase := range testCases {
  1648. actual := nodeConditionsObservedSince(testCase.observedAt, testCase.period, testCase.now)
  1649. if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
  1650. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1651. }
  1652. }
  1653. }
  1654. func TestHasNodeConditions(t *testing.T) {
  1655. testCases := map[string]struct {
  1656. inputs []v1.NodeConditionType
  1657. item v1.NodeConditionType
  1658. result bool
  1659. }{
  1660. "has-condition": {
  1661. inputs: []v1.NodeConditionType{v1.NodeReady, v1.NodeDiskPressure, v1.NodeMemoryPressure},
  1662. item: v1.NodeMemoryPressure,
  1663. result: true,
  1664. },
  1665. "does-not-have-condition": {
  1666. inputs: []v1.NodeConditionType{v1.NodeReady, v1.NodeDiskPressure},
  1667. item: v1.NodeMemoryPressure,
  1668. result: false,
  1669. },
  1670. }
  1671. for testName, testCase := range testCases {
  1672. if actual := hasNodeCondition(testCase.inputs, testCase.item); actual != testCase.result {
  1673. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1674. }
  1675. }
  1676. }
  1677. func TestParsePercentage(t *testing.T) {
  1678. testCases := map[string]struct {
  1679. hasError bool
  1680. value float32
  1681. }{
  1682. "blah": {
  1683. hasError: true,
  1684. },
  1685. "25.5%": {
  1686. value: 0.255,
  1687. },
  1688. "foo%": {
  1689. hasError: true,
  1690. },
  1691. "12%345": {
  1692. hasError: true,
  1693. },
  1694. }
  1695. for input, expected := range testCases {
  1696. value, err := parsePercentage(input)
  1697. if (err != nil) != expected.hasError {
  1698. t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.hasError, err != nil)
  1699. }
  1700. if value != expected.value {
  1701. t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.value, value)
  1702. }
  1703. }
  1704. }
  1705. func TestCompareThresholdValue(t *testing.T) {
  1706. testCases := []struct {
  1707. a, b evictionapi.ThresholdValue
  1708. equal bool
  1709. }{
  1710. {
  1711. a: evictionapi.ThresholdValue{
  1712. Quantity: resource.NewQuantity(123, resource.BinarySI),
  1713. },
  1714. b: evictionapi.ThresholdValue{
  1715. Quantity: resource.NewQuantity(123, resource.BinarySI),
  1716. },
  1717. equal: true,
  1718. },
  1719. {
  1720. a: evictionapi.ThresholdValue{
  1721. Quantity: resource.NewQuantity(123, resource.BinarySI),
  1722. },
  1723. b: evictionapi.ThresholdValue{
  1724. Quantity: resource.NewQuantity(456, resource.BinarySI),
  1725. },
  1726. equal: false,
  1727. },
  1728. {
  1729. a: evictionapi.ThresholdValue{
  1730. Quantity: resource.NewQuantity(123, resource.BinarySI),
  1731. },
  1732. b: evictionapi.ThresholdValue{
  1733. Percentage: 0.1,
  1734. },
  1735. equal: false,
  1736. },
  1737. {
  1738. a: evictionapi.ThresholdValue{
  1739. Percentage: 0.1,
  1740. },
  1741. b: evictionapi.ThresholdValue{
  1742. Percentage: 0.1,
  1743. },
  1744. equal: true,
  1745. },
  1746. {
  1747. a: evictionapi.ThresholdValue{
  1748. Percentage: 0.2,
  1749. },
  1750. b: evictionapi.ThresholdValue{
  1751. Percentage: 0.1,
  1752. },
  1753. equal: false,
  1754. },
  1755. }
  1756. for i, testCase := range testCases {
  1757. if compareThresholdValue(testCase.a, testCase.b) != testCase.equal ||
  1758. compareThresholdValue(testCase.b, testCase.a) != testCase.equal {
  1759. t.Errorf("Test case: %v failed", i)
  1760. }
  1761. }
  1762. }
  1763. // newPodInodeStats returns stats with specified usage amounts.
  1764. func newPodInodeStats(pod *v1.Pod, rootFsInodesUsed, logsInodesUsed, perLocalVolumeInodesUsed resource.Quantity) statsapi.PodStats {
  1765. result := statsapi.PodStats{
  1766. PodRef: statsapi.PodReference{
  1767. Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  1768. },
  1769. }
  1770. rootFsUsed := uint64(rootFsInodesUsed.Value())
  1771. logsUsed := uint64(logsInodesUsed.Value())
  1772. for range pod.Spec.Containers {
  1773. result.Containers = append(result.Containers, statsapi.ContainerStats{
  1774. Rootfs: &statsapi.FsStats{
  1775. InodesUsed: &rootFsUsed,
  1776. },
  1777. Logs: &statsapi.FsStats{
  1778. InodesUsed: &logsUsed,
  1779. },
  1780. })
  1781. }
  1782. perLocalVolumeUsed := uint64(perLocalVolumeInodesUsed.Value())
  1783. for _, volumeName := range localVolumeNames(pod) {
  1784. result.VolumeStats = append(result.VolumeStats, statsapi.VolumeStats{
  1785. Name: volumeName,
  1786. FsStats: statsapi.FsStats{
  1787. InodesUsed: &perLocalVolumeUsed,
  1788. },
  1789. })
  1790. }
  1791. return result
  1792. }
  1793. // newPodDiskStats returns stats with specified usage amounts.
  1794. func newPodDiskStats(pod *v1.Pod, rootFsUsed, logsUsed, perLocalVolumeUsed resource.Quantity) statsapi.PodStats {
  1795. result := statsapi.PodStats{
  1796. PodRef: statsapi.PodReference{
  1797. Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  1798. },
  1799. }
  1800. rootFsUsedBytes := uint64(rootFsUsed.Value())
  1801. logsUsedBytes := uint64(logsUsed.Value())
  1802. for range pod.Spec.Containers {
  1803. result.Containers = append(result.Containers, statsapi.ContainerStats{
  1804. Rootfs: &statsapi.FsStats{
  1805. UsedBytes: &rootFsUsedBytes,
  1806. },
  1807. Logs: &statsapi.FsStats{
  1808. UsedBytes: &logsUsedBytes,
  1809. },
  1810. })
  1811. }
  1812. perLocalVolumeUsedBytes := uint64(perLocalVolumeUsed.Value())
  1813. for _, volumeName := range localVolumeNames(pod) {
  1814. result.VolumeStats = append(result.VolumeStats, statsapi.VolumeStats{
  1815. Name: volumeName,
  1816. FsStats: statsapi.FsStats{
  1817. UsedBytes: &perLocalVolumeUsedBytes,
  1818. },
  1819. })
  1820. }
  1821. return result
  1822. }
  1823. func newPodMemoryStats(pod *v1.Pod, workingSet resource.Quantity) statsapi.PodStats {
  1824. workingSetBytes := uint64(workingSet.Value())
  1825. return statsapi.PodStats{
  1826. PodRef: statsapi.PodReference{
  1827. Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  1828. },
  1829. Memory: &statsapi.MemoryStats{
  1830. WorkingSetBytes: &workingSetBytes,
  1831. },
  1832. }
  1833. }
  1834. func newResourceList(cpu, memory, disk string) v1.ResourceList {
  1835. res := v1.ResourceList{}
  1836. if cpu != "" {
  1837. res[v1.ResourceCPU] = resource.MustParse(cpu)
  1838. }
  1839. if memory != "" {
  1840. res[v1.ResourceMemory] = resource.MustParse(memory)
  1841. }
  1842. if disk != "" {
  1843. res[v1.ResourceEphemeralStorage] = resource.MustParse(disk)
  1844. }
  1845. return res
  1846. }
  1847. func newResourceRequirements(requests, limits v1.ResourceList) v1.ResourceRequirements {
  1848. res := v1.ResourceRequirements{}
  1849. res.Requests = requests
  1850. res.Limits = limits
  1851. return res
  1852. }
  1853. func newContainer(name string, requests v1.ResourceList, limits v1.ResourceList) v1.Container {
  1854. return v1.Container{
  1855. Name: name,
  1856. Resources: newResourceRequirements(requests, limits),
  1857. }
  1858. }
  1859. func newVolume(name string, volumeSource v1.VolumeSource) v1.Volume {
  1860. return v1.Volume{
  1861. Name: name,
  1862. VolumeSource: volumeSource,
  1863. }
  1864. }
  1865. // newPod uses the name as the uid. Make names unique for testing.
  1866. func newPod(name string, priority int32, containers []v1.Container, volumes []v1.Volume) *v1.Pod {
  1867. return &v1.Pod{
  1868. ObjectMeta: metav1.ObjectMeta{
  1869. Name: name,
  1870. UID: types.UID(name),
  1871. },
  1872. Spec: v1.PodSpec{
  1873. Containers: containers,
  1874. Volumes: volumes,
  1875. Priority: &priority,
  1876. },
  1877. }
  1878. }
  1879. // nodeConditionList is a simple alias to support equality checking independent of order
  1880. type nodeConditionList []v1.NodeConditionType
  1881. // Equal adds the ability to check equality between two lists of node conditions.
  1882. func (s1 nodeConditionList) Equal(s2 nodeConditionList) bool {
  1883. if len(s1) != len(s2) {
  1884. return false
  1885. }
  1886. for _, item := range s1 {
  1887. if !hasNodeCondition(s2, item) {
  1888. return false
  1889. }
  1890. }
  1891. return true
  1892. }
  1893. // thresholdList is a simple alias to support equality checking independent of order
  1894. type thresholdList []evictionapi.Threshold
  1895. // Equal adds the ability to check equality between two lists of node conditions.
  1896. func (s1 thresholdList) Equal(s2 thresholdList) bool {
  1897. if len(s1) != len(s2) {
  1898. return false
  1899. }
  1900. for _, item := range s1 {
  1901. if !hasThreshold(s2, item) {
  1902. return false
  1903. }
  1904. }
  1905. return true
  1906. }