helpers_test.go 69 KB

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