helpers_test.go 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419
  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. "testing"
  18. "time"
  19. "k8s.io/kubernetes/pkg/api"
  20. "k8s.io/kubernetes/pkg/api/resource"
  21. "k8s.io/kubernetes/pkg/api/unversioned"
  22. statsapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats"
  23. "k8s.io/kubernetes/pkg/quota"
  24. "k8s.io/kubernetes/pkg/types"
  25. )
  26. func quantityMustParse(value string) *resource.Quantity {
  27. q := resource.MustParse(value)
  28. return &q
  29. }
  30. func TestParseThresholdConfig(t *testing.T) {
  31. gracePeriod, _ := time.ParseDuration("30s")
  32. testCases := map[string]struct {
  33. evictionHard string
  34. evictionSoft string
  35. evictionSoftGracePeriod string
  36. evictionMinReclaim string
  37. expectErr bool
  38. expectThresholds []Threshold
  39. }{
  40. "no values": {
  41. evictionHard: "",
  42. evictionSoft: "",
  43. evictionSoftGracePeriod: "",
  44. evictionMinReclaim: "",
  45. expectErr: false,
  46. expectThresholds: []Threshold{},
  47. },
  48. "all flag values": {
  49. evictionHard: "memory.available<150Mi",
  50. evictionSoft: "memory.available<300Mi",
  51. evictionSoftGracePeriod: "memory.available=30s",
  52. evictionMinReclaim: "memory.available=0",
  53. expectErr: false,
  54. expectThresholds: []Threshold{
  55. {
  56. Signal: SignalMemoryAvailable,
  57. Operator: OpLessThan,
  58. Value: ThresholdValue{
  59. Quantity: quantityMustParse("150Mi"),
  60. },
  61. MinReclaim: quantityMustParse("0"),
  62. },
  63. {
  64. Signal: SignalMemoryAvailable,
  65. Operator: OpLessThan,
  66. Value: ThresholdValue{
  67. Quantity: quantityMustParse("300Mi"),
  68. },
  69. GracePeriod: gracePeriod,
  70. MinReclaim: quantityMustParse("0"),
  71. },
  72. },
  73. },
  74. "all flag values in percentages": {
  75. evictionHard: "memory.available<10%",
  76. evictionSoft: "memory.available<30%",
  77. evictionSoftGracePeriod: "memory.available=30s",
  78. evictionMinReclaim: "memory.available=0",
  79. expectErr: false,
  80. expectThresholds: []Threshold{
  81. {
  82. Signal: SignalMemoryAvailable,
  83. Operator: OpLessThan,
  84. Value: ThresholdValue{
  85. Percentage: 0.1,
  86. },
  87. MinReclaim: quantityMustParse("0"),
  88. },
  89. {
  90. Signal: SignalMemoryAvailable,
  91. Operator: OpLessThan,
  92. Value: ThresholdValue{
  93. Percentage: 0.3,
  94. },
  95. GracePeriod: gracePeriod,
  96. MinReclaim: quantityMustParse("0"),
  97. },
  98. },
  99. },
  100. "disk flag values": {
  101. evictionHard: "imagefs.available<150Mi,nodefs.available<100Mi",
  102. evictionSoft: "imagefs.available<300Mi,nodefs.available<200Mi",
  103. evictionSoftGracePeriod: "imagefs.available=30s,nodefs.available=30s",
  104. evictionMinReclaim: "imagefs.available=2Gi,nodefs.available=1Gi",
  105. expectErr: false,
  106. expectThresholds: []Threshold{
  107. {
  108. Signal: SignalImageFsAvailable,
  109. Operator: OpLessThan,
  110. Value: ThresholdValue{
  111. Quantity: quantityMustParse("150Mi"),
  112. },
  113. MinReclaim: quantityMustParse("2Gi"),
  114. },
  115. {
  116. Signal: SignalNodeFsAvailable,
  117. Operator: OpLessThan,
  118. Value: ThresholdValue{
  119. Quantity: quantityMustParse("100Mi"),
  120. },
  121. MinReclaim: quantityMustParse("1Gi"),
  122. },
  123. {
  124. Signal: SignalImageFsAvailable,
  125. Operator: OpLessThan,
  126. Value: ThresholdValue{
  127. Quantity: quantityMustParse("300Mi"),
  128. },
  129. GracePeriod: gracePeriod,
  130. MinReclaim: quantityMustParse("2Gi"),
  131. },
  132. {
  133. Signal: SignalNodeFsAvailable,
  134. Operator: OpLessThan,
  135. Value: ThresholdValue{
  136. Quantity: quantityMustParse("200Mi"),
  137. },
  138. GracePeriod: gracePeriod,
  139. MinReclaim: quantityMustParse("1Gi"),
  140. },
  141. },
  142. },
  143. "disk flag values in percentages": {
  144. evictionHard: "imagefs.available<15%,nodefs.available<10.5%",
  145. evictionSoft: "imagefs.available<30%,nodefs.available<20.5%",
  146. evictionSoftGracePeriod: "imagefs.available=30s,nodefs.available=30s",
  147. evictionMinReclaim: "imagefs.available=2Gi,nodefs.available=1Gi",
  148. expectErr: false,
  149. expectThresholds: []Threshold{
  150. {
  151. Signal: SignalImageFsAvailable,
  152. Operator: OpLessThan,
  153. Value: ThresholdValue{
  154. Percentage: 0.15,
  155. },
  156. MinReclaim: quantityMustParse("2Gi"),
  157. },
  158. {
  159. Signal: SignalNodeFsAvailable,
  160. Operator: OpLessThan,
  161. Value: ThresholdValue{
  162. Percentage: 0.105,
  163. },
  164. MinReclaim: quantityMustParse("1Gi"),
  165. },
  166. {
  167. Signal: SignalImageFsAvailable,
  168. Operator: OpLessThan,
  169. Value: ThresholdValue{
  170. Percentage: 0.3,
  171. },
  172. GracePeriod: gracePeriod,
  173. MinReclaim: quantityMustParse("2Gi"),
  174. },
  175. {
  176. Signal: SignalNodeFsAvailable,
  177. Operator: OpLessThan,
  178. Value: ThresholdValue{
  179. Percentage: 0.205,
  180. },
  181. GracePeriod: gracePeriod,
  182. MinReclaim: quantityMustParse("1Gi"),
  183. },
  184. },
  185. },
  186. "inode flag values": {
  187. evictionHard: "imagefs.inodesFree<150Mi,nodefs.inodesFree<100Mi",
  188. evictionSoft: "imagefs.inodesFree<300Mi,nodefs.inodesFree<200Mi",
  189. evictionSoftGracePeriod: "imagefs.inodesFree=30s,nodefs.inodesFree=30s",
  190. evictionMinReclaim: "imagefs.inodesFree=2Gi,nodefs.inodesFree=1Gi",
  191. expectErr: false,
  192. expectThresholds: []Threshold{
  193. {
  194. Signal: SignalImageFsInodesFree,
  195. Operator: OpLessThan,
  196. Value: ThresholdValue{
  197. Quantity: quantityMustParse("150Mi"),
  198. },
  199. MinReclaim: quantityMustParse("2Gi"),
  200. },
  201. {
  202. Signal: SignalNodeFsInodesFree,
  203. Operator: OpLessThan,
  204. Value: ThresholdValue{
  205. Quantity: quantityMustParse("100Mi"),
  206. },
  207. MinReclaim: quantityMustParse("1Gi"),
  208. },
  209. {
  210. Signal: SignalImageFsInodesFree,
  211. Operator: OpLessThan,
  212. Value: ThresholdValue{
  213. Quantity: quantityMustParse("300Mi"),
  214. },
  215. GracePeriod: gracePeriod,
  216. MinReclaim: quantityMustParse("2Gi"),
  217. },
  218. {
  219. Signal: SignalNodeFsInodesFree,
  220. Operator: OpLessThan,
  221. Value: ThresholdValue{
  222. Quantity: quantityMustParse("200Mi"),
  223. },
  224. GracePeriod: gracePeriod,
  225. MinReclaim: quantityMustParse("1Gi"),
  226. },
  227. },
  228. },
  229. "invalid-signal": {
  230. evictionHard: "mem.available<150Mi",
  231. evictionSoft: "",
  232. evictionSoftGracePeriod: "",
  233. evictionMinReclaim: "",
  234. expectErr: true,
  235. expectThresholds: []Threshold{},
  236. },
  237. "hard-signal-negative": {
  238. evictionHard: "memory.available<-150Mi",
  239. evictionSoft: "",
  240. evictionSoftGracePeriod: "",
  241. evictionMinReclaim: "",
  242. expectErr: true,
  243. expectThresholds: []Threshold{},
  244. },
  245. "hard-signal-negative-percentage": {
  246. evictionHard: "memory.available<-15%",
  247. evictionSoft: "",
  248. evictionSoftGracePeriod: "",
  249. evictionMinReclaim: "",
  250. expectErr: true,
  251. expectThresholds: []Threshold{},
  252. },
  253. "soft-signal-negative": {
  254. evictionHard: "",
  255. evictionSoft: "memory.available<-150Mi",
  256. evictionSoftGracePeriod: "",
  257. evictionMinReclaim: "",
  258. expectErr: true,
  259. expectThresholds: []Threshold{},
  260. },
  261. "duplicate-signal": {
  262. evictionHard: "memory.available<150Mi,memory.available<100Mi",
  263. evictionSoft: "",
  264. evictionSoftGracePeriod: "",
  265. evictionMinReclaim: "",
  266. expectErr: true,
  267. expectThresholds: []Threshold{},
  268. },
  269. "valid-and-invalid-signal": {
  270. evictionHard: "memory.available<150Mi,invalid.foo<150Mi",
  271. evictionSoft: "",
  272. evictionSoftGracePeriod: "",
  273. evictionMinReclaim: "",
  274. expectErr: true,
  275. expectThresholds: []Threshold{},
  276. },
  277. "soft-no-grace-period": {
  278. evictionHard: "",
  279. evictionSoft: "memory.available<150Mi",
  280. evictionSoftGracePeriod: "",
  281. evictionMinReclaim: "",
  282. expectErr: true,
  283. expectThresholds: []Threshold{},
  284. },
  285. "soft-neg-grace-period": {
  286. evictionHard: "",
  287. evictionSoft: "memory.available<150Mi",
  288. evictionSoftGracePeriod: "memory.available=-30s",
  289. evictionMinReclaim: "",
  290. expectErr: true,
  291. expectThresholds: []Threshold{},
  292. },
  293. "neg-reclaim": {
  294. evictionHard: "",
  295. evictionSoft: "",
  296. evictionSoftGracePeriod: "",
  297. evictionMinReclaim: "memory.available=-300Mi",
  298. expectErr: true,
  299. expectThresholds: []Threshold{},
  300. },
  301. "duplicate-reclaim": {
  302. evictionHard: "",
  303. evictionSoft: "",
  304. evictionSoftGracePeriod: "",
  305. evictionMinReclaim: "memory.available=-300Mi,memory.available=-100Mi",
  306. expectErr: true,
  307. expectThresholds: []Threshold{},
  308. },
  309. }
  310. for testName, testCase := range testCases {
  311. thresholds, err := ParseThresholdConfig(testCase.evictionHard, testCase.evictionSoft, testCase.evictionSoftGracePeriod, testCase.evictionMinReclaim)
  312. if testCase.expectErr != (err != nil) {
  313. t.Errorf("Err not as expected, test: %v, error expected: %v, actual: %v", testName, testCase.expectErr, err)
  314. }
  315. if !thresholdsEqual(testCase.expectThresholds, thresholds) {
  316. t.Errorf("thresholds not as expected, test: %v, expected: %v, actual: %v", testName, testCase.expectThresholds, thresholds)
  317. }
  318. }
  319. }
  320. func thresholdsEqual(expected []Threshold, actual []Threshold) bool {
  321. if len(expected) != len(actual) {
  322. return false
  323. }
  324. for _, aThreshold := range expected {
  325. equal := false
  326. for _, bThreshold := range actual {
  327. if thresholdEqual(aThreshold, bThreshold) {
  328. equal = true
  329. }
  330. }
  331. if !equal {
  332. return false
  333. }
  334. }
  335. for _, aThreshold := range actual {
  336. equal := false
  337. for _, bThreshold := range expected {
  338. if thresholdEqual(aThreshold, bThreshold) {
  339. equal = true
  340. }
  341. }
  342. if !equal {
  343. return false
  344. }
  345. }
  346. return true
  347. }
  348. func thresholdEqual(a Threshold, b Threshold) bool {
  349. return a.GracePeriod == b.GracePeriod &&
  350. a.Operator == b.Operator &&
  351. a.Signal == b.Signal &&
  352. a.MinReclaim.Cmp(*b.MinReclaim) == 0 &&
  353. compareThresholdValue(a.Value, b.Value)
  354. }
  355. // TestOrderedByQoS ensures we order BestEffort < Burstable < Guaranteed
  356. func TestOrderedByQoS(t *testing.T) {
  357. bestEffort := newPod("best-effort", []api.Container{
  358. newContainer("best-effort", newResourceList("", ""), newResourceList("", "")),
  359. }, nil)
  360. burstable := newPod("burstable", []api.Container{
  361. newContainer("burstable", newResourceList("100m", "100Mi"), newResourceList("200m", "200Mi")),
  362. }, nil)
  363. guaranteed := newPod("guaranteed", []api.Container{
  364. newContainer("guaranteed", newResourceList("200m", "200Mi"), newResourceList("200m", "200Mi")),
  365. }, nil)
  366. pods := []*api.Pod{guaranteed, burstable, bestEffort}
  367. orderedBy(qosComparator).Sort(pods)
  368. expected := []*api.Pod{bestEffort, burstable, guaranteed}
  369. for i := range expected {
  370. if pods[i] != expected[i] {
  371. t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
  372. }
  373. }
  374. }
  375. // TestOrderedByDisk ensures we order pods by greediest disk consumer
  376. func TestOrderedByDisk(t *testing.T) {
  377. pod1 := newPod("best-effort-high", []api.Container{
  378. newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")),
  379. }, []api.Volume{
  380. newVolume("local-volume", api.VolumeSource{
  381. EmptyDir: &api.EmptyDirVolumeSource{},
  382. }),
  383. })
  384. pod2 := newPod("best-effort-low", []api.Container{
  385. newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")),
  386. }, []api.Volume{
  387. newVolume("local-volume", api.VolumeSource{
  388. EmptyDir: &api.EmptyDirVolumeSource{},
  389. }),
  390. })
  391. pod3 := newPod("burstable-high", []api.Container{
  392. newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
  393. }, []api.Volume{
  394. newVolume("local-volume", api.VolumeSource{
  395. EmptyDir: &api.EmptyDirVolumeSource{},
  396. }),
  397. })
  398. pod4 := newPod("burstable-low", []api.Container{
  399. newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
  400. }, []api.Volume{
  401. newVolume("local-volume", api.VolumeSource{
  402. EmptyDir: &api.EmptyDirVolumeSource{},
  403. }),
  404. })
  405. pod5 := newPod("guaranteed-high", []api.Container{
  406. newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
  407. }, []api.Volume{
  408. newVolume("local-volume", api.VolumeSource{
  409. EmptyDir: &api.EmptyDirVolumeSource{},
  410. }),
  411. })
  412. pod6 := newPod("guaranteed-low", []api.Container{
  413. newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
  414. }, []api.Volume{
  415. newVolume("local-volume", api.VolumeSource{
  416. EmptyDir: &api.EmptyDirVolumeSource{},
  417. }),
  418. })
  419. stats := map[*api.Pod]statsapi.PodStats{
  420. pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 200Mi
  421. pod2: newPodDiskStats(pod2, resource.MustParse("100Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 300Mi
  422. pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 400Mi
  423. pod4: newPodDiskStats(pod4, resource.MustParse("300Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 450Mi
  424. pod5: newPodDiskStats(pod5, resource.MustParse("400Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 550Mi
  425. pod6: newPodDiskStats(pod6, resource.MustParse("500Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 650Mi
  426. }
  427. statsFn := func(pod *api.Pod) (statsapi.PodStats, bool) {
  428. result, found := stats[pod]
  429. return result, found
  430. }
  431. pods := []*api.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
  432. orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceDisk)).Sort(pods)
  433. expected := []*api.Pod{pod6, pod5, pod4, pod3, pod2, pod1}
  434. for i := range expected {
  435. if pods[i] != expected[i] {
  436. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  437. }
  438. }
  439. }
  440. // TestOrderedByQoSDisk ensures we order pods by qos and then greediest disk consumer
  441. func TestOrderedByQoSDisk(t *testing.T) {
  442. pod1 := newPod("best-effort-high", []api.Container{
  443. newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")),
  444. }, []api.Volume{
  445. newVolume("local-volume", api.VolumeSource{
  446. EmptyDir: &api.EmptyDirVolumeSource{},
  447. }),
  448. })
  449. pod2 := newPod("best-effort-low", []api.Container{
  450. newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")),
  451. }, []api.Volume{
  452. newVolume("local-volume", api.VolumeSource{
  453. EmptyDir: &api.EmptyDirVolumeSource{},
  454. }),
  455. })
  456. pod3 := newPod("burstable-high", []api.Container{
  457. newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
  458. }, []api.Volume{
  459. newVolume("local-volume", api.VolumeSource{
  460. EmptyDir: &api.EmptyDirVolumeSource{},
  461. }),
  462. })
  463. pod4 := newPod("burstable-low", []api.Container{
  464. newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
  465. }, []api.Volume{
  466. newVolume("local-volume", api.VolumeSource{
  467. EmptyDir: &api.EmptyDirVolumeSource{},
  468. }),
  469. })
  470. pod5 := newPod("guaranteed-high", []api.Container{
  471. newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
  472. }, []api.Volume{
  473. newVolume("local-volume", api.VolumeSource{
  474. EmptyDir: &api.EmptyDirVolumeSource{},
  475. }),
  476. })
  477. pod6 := newPod("guaranteed-low", []api.Container{
  478. newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
  479. }, []api.Volume{
  480. newVolume("local-volume", api.VolumeSource{
  481. EmptyDir: &api.EmptyDirVolumeSource{},
  482. }),
  483. })
  484. stats := map[*api.Pod]statsapi.PodStats{
  485. pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 200Mi
  486. pod2: newPodDiskStats(pod2, resource.MustParse("100Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 300Mi
  487. pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 400Mi
  488. pod4: newPodDiskStats(pod4, resource.MustParse("300Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 450Mi
  489. pod5: newPodDiskStats(pod5, resource.MustParse("400Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 550Mi
  490. pod6: newPodDiskStats(pod6, resource.MustParse("500Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 650Mi
  491. }
  492. statsFn := func(pod *api.Pod) (statsapi.PodStats, bool) {
  493. result, found := stats[pod]
  494. return result, found
  495. }
  496. pods := []*api.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
  497. orderedBy(qosComparator, disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceDisk)).Sort(pods)
  498. expected := []*api.Pod{pod2, pod1, pod4, pod3, pod6, pod5}
  499. for i := range expected {
  500. if pods[i] != expected[i] {
  501. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  502. }
  503. }
  504. }
  505. // TestOrderedByMemory ensures we order pods by greediest memory consumer relative to request.
  506. func TestOrderedByMemory(t *testing.T) {
  507. pod1 := newPod("best-effort-high", []api.Container{
  508. newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")),
  509. }, nil)
  510. pod2 := newPod("best-effort-low", []api.Container{
  511. newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")),
  512. }, nil)
  513. pod3 := newPod("burstable-high", []api.Container{
  514. newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
  515. }, nil)
  516. pod4 := newPod("burstable-low", []api.Container{
  517. newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
  518. }, nil)
  519. pod5 := newPod("guaranteed-high", []api.Container{
  520. newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
  521. }, nil)
  522. pod6 := newPod("guaranteed-low", []api.Container{
  523. newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
  524. }, nil)
  525. stats := map[*api.Pod]statsapi.PodStats{
  526. pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request
  527. pod2: newPodMemoryStats(pod2, resource.MustParse("300Mi")), // 300 relative to request
  528. pod3: newPodMemoryStats(pod3, resource.MustParse("800Mi")), // 700 relative to request
  529. pod4: newPodMemoryStats(pod4, resource.MustParse("300Mi")), // 200 relative to request
  530. pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request
  531. pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request
  532. }
  533. statsFn := func(pod *api.Pod) (statsapi.PodStats, bool) {
  534. result, found := stats[pod]
  535. return result, found
  536. }
  537. pods := []*api.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
  538. orderedBy(memory(statsFn)).Sort(pods)
  539. expected := []*api.Pod{pod3, pod1, pod2, pod4, pod5, pod6}
  540. for i := range expected {
  541. if pods[i] != expected[i] {
  542. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  543. }
  544. }
  545. }
  546. // TestOrderedByQoSMemory ensures we order by qosComparator and then memory consumption relative to request.
  547. func TestOrderedByQoSMemory(t *testing.T) {
  548. pod1 := newPod("best-effort-high", []api.Container{
  549. newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")),
  550. }, nil)
  551. pod2 := newPod("best-effort-low", []api.Container{
  552. newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")),
  553. }, nil)
  554. pod3 := newPod("burstable-high", []api.Container{
  555. newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
  556. }, nil)
  557. pod4 := newPod("burstable-low", []api.Container{
  558. newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
  559. }, nil)
  560. pod5 := newPod("guaranteed-high", []api.Container{
  561. newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
  562. }, nil)
  563. pod6 := newPod("guaranteed-low", []api.Container{
  564. newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
  565. }, nil)
  566. stats := map[*api.Pod]statsapi.PodStats{
  567. pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request
  568. pod2: newPodMemoryStats(pod2, resource.MustParse("50Mi")), // 50 relative to request
  569. pod3: newPodMemoryStats(pod3, resource.MustParse("50Mi")), // -50 relative to request
  570. pod4: newPodMemoryStats(pod4, resource.MustParse("300Mi")), // 200 relative to request
  571. pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request
  572. pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request
  573. }
  574. statsFn := func(pod *api.Pod) (statsapi.PodStats, bool) {
  575. result, found := stats[pod]
  576. return result, found
  577. }
  578. pods := []*api.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
  579. expected := []*api.Pod{pod1, pod2, pod4, pod3, pod5, pod6}
  580. orderedBy(qosComparator, memory(statsFn)).Sort(pods)
  581. for i := range expected {
  582. if pods[i] != expected[i] {
  583. t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
  584. }
  585. }
  586. }
  587. type fakeSummaryProvider struct {
  588. result *statsapi.Summary
  589. }
  590. func (f *fakeSummaryProvider) Get() (*statsapi.Summary, error) {
  591. return f.result, nil
  592. }
  593. // newPodStats returns a pod stat where each container is using the specified working set
  594. // each pod must have a Name, UID, Namespace
  595. func newPodStats(pod *api.Pod, containerWorkingSetBytes int64) statsapi.PodStats {
  596. result := statsapi.PodStats{
  597. PodRef: statsapi.PodReference{
  598. Name: pod.Name,
  599. Namespace: pod.Namespace,
  600. UID: string(pod.UID),
  601. },
  602. }
  603. val := uint64(containerWorkingSetBytes)
  604. for range pod.Spec.Containers {
  605. result.Containers = append(result.Containers, statsapi.ContainerStats{
  606. Memory: &statsapi.MemoryStats{
  607. WorkingSetBytes: &val,
  608. },
  609. })
  610. }
  611. return result
  612. }
  613. func TestMakeSignalObservations(t *testing.T) {
  614. podMaker := func(name, namespace, uid string, numContainers int) *api.Pod {
  615. pod := &api.Pod{}
  616. pod.Name = name
  617. pod.Namespace = namespace
  618. pod.UID = types.UID(uid)
  619. pod.Spec = api.PodSpec{}
  620. for i := 0; i < numContainers; i++ {
  621. pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
  622. Name: fmt.Sprintf("ctr%v", i),
  623. })
  624. }
  625. return pod
  626. }
  627. nodeAvailableBytes := uint64(1024 * 1024 * 1024)
  628. nodeWorkingSetBytes := uint64(1024 * 1024 * 1024)
  629. imageFsAvailableBytes := uint64(1024 * 1024)
  630. imageFsCapacityBytes := uint64(1024 * 1024 * 2)
  631. nodeFsAvailableBytes := uint64(1024)
  632. nodeFsCapacityBytes := uint64(1024 * 2)
  633. imageFsInodesFree := uint64(1024)
  634. imageFsInodes := uint64(1024 * 1024)
  635. nodeFsInodesFree := uint64(1024)
  636. nodeFsInodes := uint64(1024 * 1024)
  637. fakeStats := &statsapi.Summary{
  638. Node: statsapi.NodeStats{
  639. Memory: &statsapi.MemoryStats{
  640. AvailableBytes: &nodeAvailableBytes,
  641. WorkingSetBytes: &nodeWorkingSetBytes,
  642. },
  643. Runtime: &statsapi.RuntimeStats{
  644. ImageFs: &statsapi.FsStats{
  645. AvailableBytes: &imageFsAvailableBytes,
  646. CapacityBytes: &imageFsCapacityBytes,
  647. InodesFree: &imageFsInodesFree,
  648. Inodes: &imageFsInodes,
  649. },
  650. },
  651. Fs: &statsapi.FsStats{
  652. AvailableBytes: &nodeFsAvailableBytes,
  653. CapacityBytes: &nodeFsCapacityBytes,
  654. InodesFree: &nodeFsInodesFree,
  655. Inodes: &nodeFsInodes,
  656. },
  657. },
  658. Pods: []statsapi.PodStats{},
  659. }
  660. provider := &fakeSummaryProvider{
  661. result: fakeStats,
  662. }
  663. pods := []*api.Pod{
  664. podMaker("pod1", "ns1", "uuid1", 1),
  665. podMaker("pod1", "ns2", "uuid2", 1),
  666. podMaker("pod3", "ns3", "uuid3", 1),
  667. }
  668. containerWorkingSetBytes := int64(1024 * 1024)
  669. for _, pod := range pods {
  670. fakeStats.Pods = append(fakeStats.Pods, newPodStats(pod, containerWorkingSetBytes))
  671. }
  672. actualObservations, statsFunc, err := makeSignalObservations(provider)
  673. if err != nil {
  674. t.Errorf("Unexpected err: %v", err)
  675. }
  676. memQuantity, found := actualObservations[SignalMemoryAvailable]
  677. if !found {
  678. t.Errorf("Expected available memory observation: %v", err)
  679. }
  680. if expectedBytes := int64(nodeAvailableBytes); memQuantity.available.Value() != expectedBytes {
  681. t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.available.Value())
  682. }
  683. if expectedBytes := int64(nodeWorkingSetBytes + nodeAvailableBytes); memQuantity.capacity.Value() != expectedBytes {
  684. t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.capacity.Value())
  685. }
  686. nodeFsQuantity, found := actualObservations[SignalNodeFsAvailable]
  687. if !found {
  688. t.Errorf("Expected available nodefs observation: %v", err)
  689. }
  690. if expectedBytes := int64(nodeFsAvailableBytes); nodeFsQuantity.available.Value() != expectedBytes {
  691. t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.available.Value())
  692. }
  693. if expectedBytes := int64(nodeFsCapacityBytes); nodeFsQuantity.capacity.Value() != expectedBytes {
  694. t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.capacity.Value())
  695. }
  696. nodeFsInodesQuantity, found := actualObservations[SignalNodeFsInodesFree]
  697. if !found {
  698. t.Errorf("Expected inodes free nodefs observation: %v", err)
  699. }
  700. if expected := int64(nodeFsInodesFree); nodeFsInodesQuantity.available.Value() != expected {
  701. t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.available.Value())
  702. }
  703. if expected := int64(nodeFsInodes); nodeFsInodesQuantity.capacity.Value() != expected {
  704. t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.capacity.Value())
  705. }
  706. imageFsQuantity, found := actualObservations[SignalImageFsAvailable]
  707. if !found {
  708. t.Errorf("Expected available imagefs observation: %v", err)
  709. }
  710. if expectedBytes := int64(imageFsAvailableBytes); imageFsQuantity.available.Value() != expectedBytes {
  711. t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.available.Value())
  712. }
  713. if expectedBytes := int64(imageFsCapacityBytes); imageFsQuantity.capacity.Value() != expectedBytes {
  714. t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.capacity.Value())
  715. }
  716. imageFsInodesQuantity, found := actualObservations[SignalImageFsInodesFree]
  717. if !found {
  718. t.Errorf("Expected inodes free imagefs observation: %v", err)
  719. }
  720. if expected := int64(imageFsInodesFree); imageFsInodesQuantity.available.Value() != expected {
  721. t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.available.Value())
  722. }
  723. if expected := int64(imageFsInodes); imageFsInodesQuantity.capacity.Value() != expected {
  724. t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.capacity.Value())
  725. }
  726. for _, pod := range pods {
  727. podStats, found := statsFunc(pod)
  728. if !found {
  729. t.Errorf("Pod stats were not found for pod %v", pod.UID)
  730. }
  731. for _, container := range podStats.Containers {
  732. actual := int64(*container.Memory.WorkingSetBytes)
  733. if containerWorkingSetBytes != actual {
  734. t.Errorf("Container working set expected %v, actual: %v", containerWorkingSetBytes, actual)
  735. }
  736. }
  737. }
  738. }
  739. func TestThresholdsMet(t *testing.T) {
  740. hardThreshold := Threshold{
  741. Signal: SignalMemoryAvailable,
  742. Operator: OpLessThan,
  743. Value: ThresholdValue{
  744. Quantity: quantityMustParse("1Gi"),
  745. },
  746. MinReclaim: quantityMustParse("500Mi"),
  747. }
  748. testCases := map[string]struct {
  749. enforceMinReclaim bool
  750. thresholds []Threshold
  751. observations signalObservations
  752. result []Threshold
  753. }{
  754. "empty": {
  755. enforceMinReclaim: false,
  756. thresholds: []Threshold{},
  757. observations: signalObservations{},
  758. result: []Threshold{},
  759. },
  760. "threshold-met-memory": {
  761. enforceMinReclaim: false,
  762. thresholds: []Threshold{hardThreshold},
  763. observations: signalObservations{
  764. SignalMemoryAvailable: signalObservation{
  765. available: quantityMustParse("500Mi"),
  766. },
  767. },
  768. result: []Threshold{hardThreshold},
  769. },
  770. "threshold-not-met": {
  771. enforceMinReclaim: false,
  772. thresholds: []Threshold{hardThreshold},
  773. observations: signalObservations{
  774. SignalMemoryAvailable: signalObservation{
  775. available: quantityMustParse("2Gi"),
  776. },
  777. },
  778. result: []Threshold{},
  779. },
  780. "threshold-met-with-min-reclaim": {
  781. enforceMinReclaim: true,
  782. thresholds: []Threshold{hardThreshold},
  783. observations: signalObservations{
  784. SignalMemoryAvailable: signalObservation{
  785. available: quantityMustParse("1.05Gi"),
  786. },
  787. },
  788. result: []Threshold{hardThreshold},
  789. },
  790. "threshold-not-met-with-min-reclaim": {
  791. enforceMinReclaim: true,
  792. thresholds: []Threshold{hardThreshold},
  793. observations: signalObservations{
  794. SignalMemoryAvailable: signalObservation{
  795. available: quantityMustParse("2Gi"),
  796. },
  797. },
  798. result: []Threshold{},
  799. },
  800. }
  801. for testName, testCase := range testCases {
  802. actual := thresholdsMet(testCase.thresholds, testCase.observations, testCase.enforceMinReclaim)
  803. if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  804. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  805. }
  806. }
  807. }
  808. func TestPercentageThresholdsMet(t *testing.T) {
  809. specifiecThresholds := []Threshold{
  810. {
  811. Signal: SignalMemoryAvailable,
  812. Operator: OpLessThan,
  813. Value: ThresholdValue{
  814. Percentage: 0.2,
  815. },
  816. },
  817. {
  818. Signal: SignalNodeFsAvailable,
  819. Operator: OpLessThan,
  820. Value: ThresholdValue{
  821. Percentage: 0.3,
  822. },
  823. },
  824. }
  825. testCases := map[string]struct {
  826. thresholds []Threshold
  827. observations signalObservations
  828. result []Threshold
  829. }{
  830. "BothMet": {
  831. thresholds: specifiecThresholds,
  832. observations: signalObservations{
  833. SignalMemoryAvailable: signalObservation{
  834. available: quantityMustParse("100Mi"),
  835. capacity: quantityMustParse("1000Mi"),
  836. },
  837. SignalNodeFsAvailable: signalObservation{
  838. available: quantityMustParse("100Gi"),
  839. capacity: quantityMustParse("1000Gi"),
  840. },
  841. },
  842. result: specifiecThresholds,
  843. },
  844. "NoneMet": {
  845. thresholds: specifiecThresholds,
  846. observations: signalObservations{
  847. SignalMemoryAvailable: signalObservation{
  848. available: quantityMustParse("300Mi"),
  849. capacity: quantityMustParse("1000Mi"),
  850. },
  851. SignalNodeFsAvailable: signalObservation{
  852. available: quantityMustParse("400Gi"),
  853. capacity: quantityMustParse("1000Gi"),
  854. },
  855. },
  856. result: []Threshold{},
  857. },
  858. "DiskMet": {
  859. thresholds: specifiecThresholds,
  860. observations: signalObservations{
  861. SignalMemoryAvailable: signalObservation{
  862. available: quantityMustParse("300Mi"),
  863. capacity: quantityMustParse("1000Mi"),
  864. },
  865. SignalNodeFsAvailable: signalObservation{
  866. available: quantityMustParse("100Gi"),
  867. capacity: quantityMustParse("1000Gi"),
  868. },
  869. },
  870. result: []Threshold{specifiecThresholds[1]},
  871. },
  872. "MemoryMet": {
  873. thresholds: specifiecThresholds,
  874. observations: signalObservations{
  875. SignalMemoryAvailable: signalObservation{
  876. available: quantityMustParse("100Mi"),
  877. capacity: quantityMustParse("1000Mi"),
  878. },
  879. SignalNodeFsAvailable: signalObservation{
  880. available: quantityMustParse("400Gi"),
  881. capacity: quantityMustParse("1000Gi"),
  882. },
  883. },
  884. result: []Threshold{specifiecThresholds[0]},
  885. },
  886. }
  887. for testName, testCase := range testCases {
  888. actual := thresholdsMet(testCase.thresholds, testCase.observations, false)
  889. if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  890. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  891. }
  892. }
  893. }
  894. func TestThresholdsFirstObservedAt(t *testing.T) {
  895. hardThreshold := Threshold{
  896. Signal: SignalMemoryAvailable,
  897. Operator: OpLessThan,
  898. Value: ThresholdValue{
  899. Quantity: quantityMustParse("1Gi"),
  900. },
  901. }
  902. now := unversioned.Now()
  903. oldTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute))
  904. testCases := map[string]struct {
  905. thresholds []Threshold
  906. lastObservedAt thresholdsObservedAt
  907. now time.Time
  908. result thresholdsObservedAt
  909. }{
  910. "empty": {
  911. thresholds: []Threshold{},
  912. lastObservedAt: thresholdsObservedAt{},
  913. now: now.Time,
  914. result: thresholdsObservedAt{},
  915. },
  916. "no-previous-observation": {
  917. thresholds: []Threshold{hardThreshold},
  918. lastObservedAt: thresholdsObservedAt{},
  919. now: now.Time,
  920. result: thresholdsObservedAt{
  921. hardThreshold: now.Time,
  922. },
  923. },
  924. "previous-observation": {
  925. thresholds: []Threshold{hardThreshold},
  926. lastObservedAt: thresholdsObservedAt{
  927. hardThreshold: oldTime.Time,
  928. },
  929. now: now.Time,
  930. result: thresholdsObservedAt{
  931. hardThreshold: oldTime.Time,
  932. },
  933. },
  934. }
  935. for testName, testCase := range testCases {
  936. actual := thresholdsFirstObservedAt(testCase.thresholds, testCase.lastObservedAt, testCase.now)
  937. if !reflect.DeepEqual(actual, testCase.result) {
  938. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  939. }
  940. }
  941. }
  942. func TestThresholdsMetGracePeriod(t *testing.T) {
  943. now := unversioned.Now()
  944. hardThreshold := Threshold{
  945. Signal: SignalMemoryAvailable,
  946. Operator: OpLessThan,
  947. Value: ThresholdValue{
  948. Quantity: quantityMustParse("1Gi"),
  949. },
  950. }
  951. softThreshold := Threshold{
  952. Signal: SignalMemoryAvailable,
  953. Operator: OpLessThan,
  954. Value: ThresholdValue{
  955. Quantity: quantityMustParse("2Gi"),
  956. },
  957. GracePeriod: 1 * time.Minute,
  958. }
  959. oldTime := unversioned.NewTime(now.Time.Add(-2 * time.Minute))
  960. testCases := map[string]struct {
  961. observedAt thresholdsObservedAt
  962. now time.Time
  963. result []Threshold
  964. }{
  965. "empty": {
  966. observedAt: thresholdsObservedAt{},
  967. now: now.Time,
  968. result: []Threshold{},
  969. },
  970. "hard-threshold-met": {
  971. observedAt: thresholdsObservedAt{
  972. hardThreshold: now.Time,
  973. },
  974. now: now.Time,
  975. result: []Threshold{hardThreshold},
  976. },
  977. "soft-threshold-not-met": {
  978. observedAt: thresholdsObservedAt{
  979. softThreshold: now.Time,
  980. },
  981. now: now.Time,
  982. result: []Threshold{},
  983. },
  984. "soft-threshold-met": {
  985. observedAt: thresholdsObservedAt{
  986. softThreshold: oldTime.Time,
  987. },
  988. now: now.Time,
  989. result: []Threshold{softThreshold},
  990. },
  991. }
  992. for testName, testCase := range testCases {
  993. actual := thresholdsMetGracePeriod(testCase.observedAt, now.Time)
  994. if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
  995. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  996. }
  997. }
  998. }
  999. func TestNodeConditions(t *testing.T) {
  1000. testCases := map[string]struct {
  1001. inputs []Threshold
  1002. result []api.NodeConditionType
  1003. }{
  1004. "empty-list": {
  1005. inputs: []Threshold{},
  1006. result: []api.NodeConditionType{},
  1007. },
  1008. "memory.available": {
  1009. inputs: []Threshold{
  1010. {Signal: SignalMemoryAvailable},
  1011. },
  1012. result: []api.NodeConditionType{api.NodeMemoryPressure},
  1013. },
  1014. }
  1015. for testName, testCase := range testCases {
  1016. actual := nodeConditions(testCase.inputs)
  1017. if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
  1018. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1019. }
  1020. }
  1021. }
  1022. func TestNodeConditionsLastObservedAt(t *testing.T) {
  1023. now := unversioned.Now()
  1024. oldTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute))
  1025. testCases := map[string]struct {
  1026. nodeConditions []api.NodeConditionType
  1027. lastObservedAt nodeConditionsObservedAt
  1028. now time.Time
  1029. result nodeConditionsObservedAt
  1030. }{
  1031. "no-previous-observation": {
  1032. nodeConditions: []api.NodeConditionType{api.NodeMemoryPressure},
  1033. lastObservedAt: nodeConditionsObservedAt{},
  1034. now: now.Time,
  1035. result: nodeConditionsObservedAt{
  1036. api.NodeMemoryPressure: now.Time,
  1037. },
  1038. },
  1039. "previous-observation": {
  1040. nodeConditions: []api.NodeConditionType{api.NodeMemoryPressure},
  1041. lastObservedAt: nodeConditionsObservedAt{
  1042. api.NodeMemoryPressure: oldTime.Time,
  1043. },
  1044. now: now.Time,
  1045. result: nodeConditionsObservedAt{
  1046. api.NodeMemoryPressure: now.Time,
  1047. },
  1048. },
  1049. "old-observation": {
  1050. nodeConditions: []api.NodeConditionType{},
  1051. lastObservedAt: nodeConditionsObservedAt{
  1052. api.NodeMemoryPressure: oldTime.Time,
  1053. },
  1054. now: now.Time,
  1055. result: nodeConditionsObservedAt{
  1056. api.NodeMemoryPressure: oldTime.Time,
  1057. },
  1058. },
  1059. }
  1060. for testName, testCase := range testCases {
  1061. actual := nodeConditionsLastObservedAt(testCase.nodeConditions, testCase.lastObservedAt, testCase.now)
  1062. if !reflect.DeepEqual(actual, testCase.result) {
  1063. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1064. }
  1065. }
  1066. }
  1067. func TestNodeConditionsObservedSince(t *testing.T) {
  1068. now := unversioned.Now()
  1069. observedTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute))
  1070. testCases := map[string]struct {
  1071. observedAt nodeConditionsObservedAt
  1072. period time.Duration
  1073. now time.Time
  1074. result []api.NodeConditionType
  1075. }{
  1076. "in-period": {
  1077. observedAt: nodeConditionsObservedAt{
  1078. api.NodeMemoryPressure: observedTime.Time,
  1079. },
  1080. period: 2 * time.Minute,
  1081. now: now.Time,
  1082. result: []api.NodeConditionType{api.NodeMemoryPressure},
  1083. },
  1084. "out-of-period": {
  1085. observedAt: nodeConditionsObservedAt{
  1086. api.NodeMemoryPressure: observedTime.Time,
  1087. },
  1088. period: 30 * time.Second,
  1089. now: now.Time,
  1090. result: []api.NodeConditionType{},
  1091. },
  1092. }
  1093. for testName, testCase := range testCases {
  1094. actual := nodeConditionsObservedSince(testCase.observedAt, testCase.period, testCase.now)
  1095. if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
  1096. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1097. }
  1098. }
  1099. }
  1100. func TestHasNodeConditions(t *testing.T) {
  1101. testCases := map[string]struct {
  1102. inputs []api.NodeConditionType
  1103. item api.NodeConditionType
  1104. result bool
  1105. }{
  1106. "has-condition": {
  1107. inputs: []api.NodeConditionType{api.NodeReady, api.NodeOutOfDisk, api.NodeMemoryPressure},
  1108. item: api.NodeMemoryPressure,
  1109. result: true,
  1110. },
  1111. "does-not-have-condition": {
  1112. inputs: []api.NodeConditionType{api.NodeReady, api.NodeOutOfDisk},
  1113. item: api.NodeMemoryPressure,
  1114. result: false,
  1115. },
  1116. }
  1117. for testName, testCase := range testCases {
  1118. if actual := hasNodeCondition(testCase.inputs, testCase.item); actual != testCase.result {
  1119. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
  1120. }
  1121. }
  1122. }
  1123. func TestGetStarvedResources(t *testing.T) {
  1124. testCases := map[string]struct {
  1125. inputs []Threshold
  1126. result []api.ResourceName
  1127. }{
  1128. "memory.available": {
  1129. inputs: []Threshold{
  1130. {Signal: SignalMemoryAvailable},
  1131. },
  1132. result: []api.ResourceName{api.ResourceMemory},
  1133. },
  1134. "imagefs.available": {
  1135. inputs: []Threshold{
  1136. {Signal: SignalImageFsAvailable},
  1137. },
  1138. result: []api.ResourceName{resourceImageFs},
  1139. },
  1140. "nodefs.available": {
  1141. inputs: []Threshold{
  1142. {Signal: SignalNodeFsAvailable},
  1143. },
  1144. result: []api.ResourceName{resourceNodeFs},
  1145. },
  1146. }
  1147. for testName, testCase := range testCases {
  1148. actual := getStarvedResources(testCase.inputs)
  1149. actualSet := quota.ToSet(actual)
  1150. expectedSet := quota.ToSet(testCase.result)
  1151. if !actualSet.Equal(expectedSet) {
  1152. t.Errorf("Test case: %s, expected: %v, actual: %v", testName, expectedSet, actualSet)
  1153. }
  1154. }
  1155. }
  1156. func testParsePercentage(t *testing.T) {
  1157. testCases := map[string]struct {
  1158. hasError bool
  1159. value float32
  1160. }{
  1161. "blah": {
  1162. hasError: true,
  1163. },
  1164. "25.5%": {
  1165. value: 0.255,
  1166. },
  1167. "foo%": {
  1168. hasError: true,
  1169. },
  1170. "12%345": {
  1171. hasError: true,
  1172. },
  1173. }
  1174. for input, expected := range testCases {
  1175. value, err := parsePercentage(input)
  1176. if (err != nil) != expected.hasError {
  1177. t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.hasError, err != nil)
  1178. }
  1179. if value != expected.value {
  1180. t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.value, value)
  1181. }
  1182. }
  1183. }
  1184. func testCompareThresholdValue(t *testing.T) {
  1185. testCases := []struct {
  1186. a, b ThresholdValue
  1187. equal bool
  1188. }{
  1189. {
  1190. a: ThresholdValue{
  1191. Quantity: resource.NewQuantity(123, resource.BinarySI),
  1192. },
  1193. b: ThresholdValue{
  1194. Quantity: resource.NewQuantity(123, resource.BinarySI),
  1195. },
  1196. equal: true,
  1197. },
  1198. {
  1199. a: ThresholdValue{
  1200. Quantity: resource.NewQuantity(123, resource.BinarySI),
  1201. },
  1202. b: ThresholdValue{
  1203. Quantity: resource.NewQuantity(456, resource.BinarySI),
  1204. },
  1205. equal: false,
  1206. },
  1207. {
  1208. a: ThresholdValue{
  1209. Quantity: resource.NewQuantity(123, resource.BinarySI),
  1210. },
  1211. b: ThresholdValue{
  1212. Percentage: 0.1,
  1213. },
  1214. equal: false,
  1215. },
  1216. {
  1217. a: ThresholdValue{
  1218. Percentage: 0.1,
  1219. },
  1220. b: ThresholdValue{
  1221. Percentage: 0.1,
  1222. },
  1223. equal: true,
  1224. },
  1225. {
  1226. a: ThresholdValue{
  1227. Percentage: 0.2,
  1228. },
  1229. b: ThresholdValue{
  1230. Percentage: 0.1,
  1231. },
  1232. equal: false,
  1233. },
  1234. }
  1235. for i, testCase := range testCases {
  1236. if compareThresholdValue(testCase.a, testCase.b) != testCase.equal ||
  1237. compareThresholdValue(testCase.b, testCase.a) != testCase.equal {
  1238. t.Errorf("Test case: %v failed", i)
  1239. }
  1240. }
  1241. }
  1242. // newPodInodeStats returns stats with specified usage amounts.
  1243. // TODO: in future, this should take a value for inodesUsed per container.
  1244. func newPodInodeStats(pod *api.Pod) statsapi.PodStats {
  1245. result := statsapi.PodStats{
  1246. PodRef: statsapi.PodReference{
  1247. Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  1248. },
  1249. }
  1250. for range pod.Spec.Containers {
  1251. result.Containers = append(result.Containers, statsapi.ContainerStats{
  1252. Rootfs: &statsapi.FsStats{},
  1253. })
  1254. }
  1255. return result
  1256. }
  1257. // newPodDiskStats returns stats with specified usage amounts.
  1258. func newPodDiskStats(pod *api.Pod, rootFsUsed, logsUsed, perLocalVolumeUsed resource.Quantity) statsapi.PodStats {
  1259. result := statsapi.PodStats{
  1260. PodRef: statsapi.PodReference{
  1261. Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  1262. },
  1263. }
  1264. rootFsUsedBytes := uint64(rootFsUsed.Value())
  1265. logsUsedBytes := uint64(logsUsed.Value())
  1266. for range pod.Spec.Containers {
  1267. result.Containers = append(result.Containers, statsapi.ContainerStats{
  1268. Rootfs: &statsapi.FsStats{
  1269. UsedBytes: &rootFsUsedBytes,
  1270. },
  1271. Logs: &statsapi.FsStats{
  1272. UsedBytes: &logsUsedBytes,
  1273. },
  1274. })
  1275. }
  1276. perLocalVolumeUsedBytes := uint64(perLocalVolumeUsed.Value())
  1277. for _, volumeName := range localVolumeNames(pod) {
  1278. result.VolumeStats = append(result.VolumeStats, statsapi.VolumeStats{
  1279. Name: volumeName,
  1280. FsStats: statsapi.FsStats{
  1281. UsedBytes: &perLocalVolumeUsedBytes,
  1282. },
  1283. })
  1284. }
  1285. return result
  1286. }
  1287. func newPodMemoryStats(pod *api.Pod, workingSet resource.Quantity) statsapi.PodStats {
  1288. result := statsapi.PodStats{
  1289. PodRef: statsapi.PodReference{
  1290. Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
  1291. },
  1292. }
  1293. for range pod.Spec.Containers {
  1294. workingSetBytes := uint64(workingSet.Value())
  1295. result.Containers = append(result.Containers, statsapi.ContainerStats{
  1296. Memory: &statsapi.MemoryStats{
  1297. WorkingSetBytes: &workingSetBytes,
  1298. },
  1299. })
  1300. }
  1301. return result
  1302. }
  1303. func newResourceList(cpu, memory string) api.ResourceList {
  1304. res := api.ResourceList{}
  1305. if cpu != "" {
  1306. res[api.ResourceCPU] = resource.MustParse(cpu)
  1307. }
  1308. if memory != "" {
  1309. res[api.ResourceMemory] = resource.MustParse(memory)
  1310. }
  1311. return res
  1312. }
  1313. func newResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
  1314. res := api.ResourceRequirements{}
  1315. res.Requests = requests
  1316. res.Limits = limits
  1317. return res
  1318. }
  1319. func newContainer(name string, requests api.ResourceList, limits api.ResourceList) api.Container {
  1320. return api.Container{
  1321. Name: name,
  1322. Resources: newResourceRequirements(requests, limits),
  1323. }
  1324. }
  1325. func newVolume(name string, volumeSource api.VolumeSource) api.Volume {
  1326. return api.Volume{
  1327. Name: name,
  1328. VolumeSource: volumeSource,
  1329. }
  1330. }
  1331. func newPod(name string, containers []api.Container, volumes []api.Volume) *api.Pod {
  1332. return &api.Pod{
  1333. ObjectMeta: api.ObjectMeta{
  1334. Name: name,
  1335. },
  1336. Spec: api.PodSpec{
  1337. Containers: containers,
  1338. Volumes: volumes,
  1339. },
  1340. }
  1341. }
  1342. // nodeConditionList is a simple alias to support equality checking independent of order
  1343. type nodeConditionList []api.NodeConditionType
  1344. // Equal adds the ability to check equality between two lists of node conditions.
  1345. func (s1 nodeConditionList) Equal(s2 nodeConditionList) bool {
  1346. if len(s1) != len(s2) {
  1347. return false
  1348. }
  1349. for _, item := range s1 {
  1350. if !hasNodeCondition(s2, item) {
  1351. return false
  1352. }
  1353. }
  1354. return true
  1355. }
  1356. // thresholdList is a simple alias to support equality checking independent of order
  1357. type thresholdList []Threshold
  1358. // Equal adds the ability to check equality between two lists of node conditions.
  1359. func (s1 thresholdList) Equal(s2 thresholdList) bool {
  1360. if len(s1) != len(s2) {
  1361. return false
  1362. }
  1363. for _, item := range s1 {
  1364. if !hasThreshold(s2, item) {
  1365. return false
  1366. }
  1367. }
  1368. return true
  1369. }