eviction_manager_test.go 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  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. "testing"
  16. "time"
  17. "k8s.io/kubernetes/pkg/api"
  18. "k8s.io/kubernetes/pkg/api/resource"
  19. "k8s.io/kubernetes/pkg/client/record"
  20. statsapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats"
  21. "k8s.io/kubernetes/pkg/kubelet/lifecycle"
  22. "k8s.io/kubernetes/pkg/types"
  23. "k8s.io/kubernetes/pkg/util/clock"
  24. )
  25. // mockPodKiller is used to testing which pod is killed
  26. type mockPodKiller struct {
  27. pod *api.Pod
  28. status api.PodStatus
  29. gracePeriodOverride *int64
  30. }
  31. // killPodNow records the pod that was killed
  32. func (m *mockPodKiller) killPodNow(pod *api.Pod, status api.PodStatus, gracePeriodOverride *int64) error {
  33. m.pod = pod
  34. m.status = status
  35. m.gracePeriodOverride = gracePeriodOverride
  36. return nil
  37. }
  38. // mockDiskInfoProvider is used to simulate testing.
  39. type mockDiskInfoProvider struct {
  40. dedicatedImageFs bool
  41. }
  42. // HasDedicatedImageFs returns the mocked value
  43. func (m *mockDiskInfoProvider) HasDedicatedImageFs() (bool, error) {
  44. return m.dedicatedImageFs, nil
  45. }
  46. // mockImageGC is used to simulate invoking image garbage collection.
  47. type mockImageGC struct {
  48. err error
  49. freed int64
  50. invoked bool
  51. }
  52. // DeleteUnusedImages returns the mocked values.
  53. func (m *mockImageGC) DeleteUnusedImages() (int64, error) {
  54. m.invoked = true
  55. return m.freed, m.err
  56. }
  57. // TestMemoryPressure
  58. func TestMemoryPressure(t *testing.T) {
  59. podMaker := func(name string, requests api.ResourceList, limits api.ResourceList, memoryWorkingSet string) (*api.Pod, statsapi.PodStats) {
  60. pod := newPod(name, []api.Container{
  61. newContainer(name, requests, limits),
  62. }, nil)
  63. podStats := newPodMemoryStats(pod, resource.MustParse(memoryWorkingSet))
  64. return pod, podStats
  65. }
  66. summaryStatsMaker := func(nodeAvailableBytes string, podStats map[*api.Pod]statsapi.PodStats) *statsapi.Summary {
  67. val := resource.MustParse(nodeAvailableBytes)
  68. availableBytes := uint64(val.Value())
  69. WorkingSetBytes := uint64(val.Value())
  70. result := &statsapi.Summary{
  71. Node: statsapi.NodeStats{
  72. Memory: &statsapi.MemoryStats{
  73. AvailableBytes: &availableBytes,
  74. WorkingSetBytes: &WorkingSetBytes,
  75. },
  76. },
  77. Pods: []statsapi.PodStats{},
  78. }
  79. for _, podStat := range podStats {
  80. result.Pods = append(result.Pods, podStat)
  81. }
  82. return result
  83. }
  84. podsToMake := []struct {
  85. name string
  86. requests api.ResourceList
  87. limits api.ResourceList
  88. memoryWorkingSet string
  89. }{
  90. {name: "best-effort-high", requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "500Mi"},
  91. {name: "best-effort-low", requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "300Mi"},
  92. {name: "burstable-high", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "800Mi"},
  93. {name: "burstable-low", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "300Mi"},
  94. {name: "guaranteed-high", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "800Mi"},
  95. {name: "guaranteed-low", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "200Mi"},
  96. }
  97. pods := []*api.Pod{}
  98. podStats := map[*api.Pod]statsapi.PodStats{}
  99. for _, podToMake := range podsToMake {
  100. pod, podStat := podMaker(podToMake.name, podToMake.requests, podToMake.limits, podToMake.memoryWorkingSet)
  101. pods = append(pods, pod)
  102. podStats[pod] = podStat
  103. }
  104. activePodsFunc := func() []*api.Pod {
  105. return pods
  106. }
  107. fakeClock := clock.NewFakeClock(time.Now())
  108. podKiller := &mockPodKiller{}
  109. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  110. imageGC := &mockImageGC{freed: int64(0), err: nil}
  111. nodeRef := &api.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  112. config := Config{
  113. MaxPodGracePeriodSeconds: 5,
  114. PressureTransitionPeriod: time.Minute * 5,
  115. Thresholds: []Threshold{
  116. {
  117. Signal: SignalMemoryAvailable,
  118. Operator: OpLessThan,
  119. Value: ThresholdValue{
  120. Quantity: quantityMustParse("1Gi"),
  121. },
  122. },
  123. {
  124. Signal: SignalMemoryAvailable,
  125. Operator: OpLessThan,
  126. Value: ThresholdValue{
  127. Quantity: quantityMustParse("2Gi"),
  128. },
  129. GracePeriod: time.Minute * 2,
  130. },
  131. },
  132. }
  133. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("2Gi", podStats)}
  134. manager := &managerImpl{
  135. clock: fakeClock,
  136. killPodFunc: podKiller.killPodNow,
  137. imageGC: imageGC,
  138. config: config,
  139. recorder: &record.FakeRecorder{},
  140. summaryProvider: summaryProvider,
  141. nodeRef: nodeRef,
  142. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  143. thresholdsFirstObservedAt: thresholdsObservedAt{},
  144. }
  145. // create a best effort pod to test admission
  146. bestEffortPodToAdmit, _ := podMaker("best-admit", newResourceList("", ""), newResourceList("", ""), "0Gi")
  147. burstablePodToAdmit, _ := podMaker("burst-admit", newResourceList("100m", "100Mi"), newResourceList("200m", "200Mi"), "0Gi")
  148. // synchronize
  149. manager.synchronize(diskInfoProvider, activePodsFunc)
  150. // we should not have memory pressure
  151. if manager.IsUnderMemoryPressure() {
  152. t.Errorf("Manager should not report memory pressure")
  153. }
  154. // try to admit our pods (they should succeed)
  155. expected := []bool{true, true}
  156. for i, pod := range []*api.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  157. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  158. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  159. }
  160. }
  161. // induce soft threshold
  162. fakeClock.Step(1 * time.Minute)
  163. summaryProvider.result = summaryStatsMaker("1500Mi", podStats)
  164. manager.synchronize(diskInfoProvider, activePodsFunc)
  165. // we should have memory pressure
  166. if !manager.IsUnderMemoryPressure() {
  167. t.Errorf("Manager should report memory pressure since soft threshold was met")
  168. }
  169. // verify no pod was yet killed because there has not yet been enough time passed.
  170. if podKiller.pod != nil {
  171. t.Errorf("Manager should not have killed a pod yet, but killed: %v", podKiller.pod)
  172. }
  173. // step forward in time pass the grace period
  174. fakeClock.Step(3 * time.Minute)
  175. summaryProvider.result = summaryStatsMaker("1500Mi", podStats)
  176. manager.synchronize(diskInfoProvider, activePodsFunc)
  177. // we should have memory pressure
  178. if !manager.IsUnderMemoryPressure() {
  179. t.Errorf("Manager should report memory pressure since soft threshold was met")
  180. }
  181. // verify the right pod was killed with the right grace period.
  182. if podKiller.pod != pods[0] {
  183. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  184. }
  185. if podKiller.gracePeriodOverride == nil {
  186. t.Errorf("Manager chose to kill pod but should have had a grace period override.")
  187. }
  188. observedGracePeriod := *podKiller.gracePeriodOverride
  189. if observedGracePeriod != manager.config.MaxPodGracePeriodSeconds {
  190. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", manager.config.MaxPodGracePeriodSeconds, observedGracePeriod)
  191. }
  192. // reset state
  193. podKiller.pod = nil
  194. podKiller.gracePeriodOverride = nil
  195. // remove memory pressure
  196. fakeClock.Step(20 * time.Minute)
  197. summaryProvider.result = summaryStatsMaker("3Gi", podStats)
  198. manager.synchronize(diskInfoProvider, activePodsFunc)
  199. // we should not have memory pressure
  200. if manager.IsUnderMemoryPressure() {
  201. t.Errorf("Manager should not report memory pressure")
  202. }
  203. // induce memory pressure!
  204. fakeClock.Step(1 * time.Minute)
  205. summaryProvider.result = summaryStatsMaker("500Mi", podStats)
  206. manager.synchronize(diskInfoProvider, activePodsFunc)
  207. // we should have memory pressure
  208. if !manager.IsUnderMemoryPressure() {
  209. t.Errorf("Manager should report memory pressure")
  210. }
  211. // check the right pod was killed
  212. if podKiller.pod != pods[0] {
  213. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  214. }
  215. observedGracePeriod = *podKiller.gracePeriodOverride
  216. if observedGracePeriod != int64(0) {
  217. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  218. }
  219. // the best-effort pod should not admit, burstable should
  220. expected = []bool{false, true}
  221. for i, pod := range []*api.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  222. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  223. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  224. }
  225. }
  226. // reduce memory pressure
  227. fakeClock.Step(1 * time.Minute)
  228. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  229. podKiller.pod = nil // reset state
  230. manager.synchronize(diskInfoProvider, activePodsFunc)
  231. // we should have memory pressure (because transition period not yet met)
  232. if !manager.IsUnderMemoryPressure() {
  233. t.Errorf("Manager should report memory pressure")
  234. }
  235. // no pod should have been killed
  236. if podKiller.pod != nil {
  237. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  238. }
  239. // the best-effort pod should not admit, burstable should
  240. expected = []bool{false, true}
  241. for i, pod := range []*api.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  242. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  243. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  244. }
  245. }
  246. // move the clock past transition period to ensure that we stop reporting pressure
  247. fakeClock.Step(5 * time.Minute)
  248. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  249. podKiller.pod = nil // reset state
  250. manager.synchronize(diskInfoProvider, activePodsFunc)
  251. // we should not have memory pressure (because transition period met)
  252. if manager.IsUnderMemoryPressure() {
  253. t.Errorf("Manager should not report memory pressure")
  254. }
  255. // no pod should have been killed
  256. if podKiller.pod != nil {
  257. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  258. }
  259. // all pods should admit now
  260. expected = []bool{true, true}
  261. for i, pod := range []*api.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  262. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  263. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  264. }
  265. }
  266. }
  267. // parseQuantity parses the specified value (if provided) otherwise returns 0 value
  268. func parseQuantity(value string) resource.Quantity {
  269. if len(value) == 0 {
  270. return resource.MustParse("0")
  271. }
  272. return resource.MustParse(value)
  273. }
  274. func TestDiskPressureNodeFs(t *testing.T) {
  275. podMaker := func(name string, requests api.ResourceList, limits api.ResourceList, rootFsUsed, logsUsed, perLocalVolumeUsed string) (*api.Pod, statsapi.PodStats) {
  276. pod := newPod(name, []api.Container{
  277. newContainer(name, requests, limits),
  278. }, nil)
  279. podStats := newPodDiskStats(pod, parseQuantity(rootFsUsed), parseQuantity(logsUsed), parseQuantity(perLocalVolumeUsed))
  280. return pod, podStats
  281. }
  282. summaryStatsMaker := func(rootFsAvailableBytes, imageFsAvailableBytes string, podStats map[*api.Pod]statsapi.PodStats) *statsapi.Summary {
  283. rootFsVal := resource.MustParse(rootFsAvailableBytes)
  284. rootFsBytes := uint64(rootFsVal.Value())
  285. rootFsCapacityBytes := uint64(rootFsVal.Value() * 2)
  286. imageFsVal := resource.MustParse(imageFsAvailableBytes)
  287. imageFsBytes := uint64(imageFsVal.Value())
  288. imageFsCapacityBytes := uint64(imageFsVal.Value() * 2)
  289. result := &statsapi.Summary{
  290. Node: statsapi.NodeStats{
  291. Fs: &statsapi.FsStats{
  292. AvailableBytes: &rootFsBytes,
  293. CapacityBytes: &rootFsCapacityBytes,
  294. },
  295. Runtime: &statsapi.RuntimeStats{
  296. ImageFs: &statsapi.FsStats{
  297. AvailableBytes: &imageFsBytes,
  298. CapacityBytes: &imageFsCapacityBytes,
  299. },
  300. },
  301. },
  302. Pods: []statsapi.PodStats{},
  303. }
  304. for _, podStat := range podStats {
  305. result.Pods = append(result.Pods, podStat)
  306. }
  307. return result
  308. }
  309. podsToMake := []struct {
  310. name string
  311. requests api.ResourceList
  312. limits api.ResourceList
  313. rootFsUsed string
  314. logsFsUsed string
  315. perLocalVolumeUsed string
  316. }{
  317. {name: "best-effort-high", requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "500Mi"},
  318. {name: "best-effort-low", requests: newResourceList("", ""), limits: newResourceList("", ""), perLocalVolumeUsed: "300Mi"},
  319. {name: "burstable-high", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsUsed: "800Mi"},
  320. {name: "burstable-low", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), logsFsUsed: "300Mi"},
  321. {name: "guaranteed-high", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "800Mi"},
  322. {name: "guaranteed-low", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "200Mi"},
  323. }
  324. pods := []*api.Pod{}
  325. podStats := map[*api.Pod]statsapi.PodStats{}
  326. for _, podToMake := range podsToMake {
  327. pod, podStat := podMaker(podToMake.name, podToMake.requests, podToMake.limits, podToMake.rootFsUsed, podToMake.logsFsUsed, podToMake.perLocalVolumeUsed)
  328. pods = append(pods, pod)
  329. podStats[pod] = podStat
  330. }
  331. activePodsFunc := func() []*api.Pod {
  332. return pods
  333. }
  334. fakeClock := clock.NewFakeClock(time.Now())
  335. podKiller := &mockPodKiller{}
  336. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  337. imageGC := &mockImageGC{freed: int64(0), err: nil}
  338. nodeRef := &api.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  339. config := Config{
  340. MaxPodGracePeriodSeconds: 5,
  341. PressureTransitionPeriod: time.Minute * 5,
  342. Thresholds: []Threshold{
  343. {
  344. Signal: SignalNodeFsAvailable,
  345. Operator: OpLessThan,
  346. Value: ThresholdValue{
  347. Quantity: quantityMustParse("1Gi"),
  348. },
  349. },
  350. {
  351. Signal: SignalNodeFsAvailable,
  352. Operator: OpLessThan,
  353. Value: ThresholdValue{
  354. Quantity: quantityMustParse("2Gi"),
  355. },
  356. GracePeriod: time.Minute * 2,
  357. },
  358. },
  359. }
  360. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("16Gi", "200Gi", podStats)}
  361. manager := &managerImpl{
  362. clock: fakeClock,
  363. killPodFunc: podKiller.killPodNow,
  364. imageGC: imageGC,
  365. config: config,
  366. recorder: &record.FakeRecorder{},
  367. summaryProvider: summaryProvider,
  368. nodeRef: nodeRef,
  369. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  370. thresholdsFirstObservedAt: thresholdsObservedAt{},
  371. }
  372. // create a best effort pod to test admission
  373. podToAdmit, _ := podMaker("pod-to-admit", newResourceList("", ""), newResourceList("", ""), "0Gi", "0Gi", "0Gi")
  374. // synchronize
  375. manager.synchronize(diskInfoProvider, activePodsFunc)
  376. // we should not have disk pressure
  377. if manager.IsUnderDiskPressure() {
  378. t.Errorf("Manager should not report disk pressure")
  379. }
  380. // try to admit our pod (should succeed)
  381. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit {
  382. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit)
  383. }
  384. // induce soft threshold
  385. fakeClock.Step(1 * time.Minute)
  386. summaryProvider.result = summaryStatsMaker("1.5Gi", "200Gi", podStats)
  387. manager.synchronize(diskInfoProvider, activePodsFunc)
  388. // we should have disk pressure
  389. if !manager.IsUnderDiskPressure() {
  390. t.Errorf("Manager should report disk pressure since soft threshold was met")
  391. }
  392. // verify no pod was yet killed because there has not yet been enough time passed.
  393. if podKiller.pod != nil {
  394. t.Errorf("Manager should not have killed a pod yet, but killed: %v", podKiller.pod)
  395. }
  396. // step forward in time pass the grace period
  397. fakeClock.Step(3 * time.Minute)
  398. summaryProvider.result = summaryStatsMaker("1.5Gi", "200Gi", podStats)
  399. manager.synchronize(diskInfoProvider, activePodsFunc)
  400. // we should have disk pressure
  401. if !manager.IsUnderDiskPressure() {
  402. t.Errorf("Manager should report disk pressure since soft threshold was met")
  403. }
  404. // verify the right pod was killed with the right grace period.
  405. if podKiller.pod != pods[0] {
  406. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  407. }
  408. if podKiller.gracePeriodOverride == nil {
  409. t.Errorf("Manager chose to kill pod but should have had a grace period override.")
  410. }
  411. observedGracePeriod := *podKiller.gracePeriodOverride
  412. if observedGracePeriod != manager.config.MaxPodGracePeriodSeconds {
  413. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", manager.config.MaxPodGracePeriodSeconds, observedGracePeriod)
  414. }
  415. // reset state
  416. podKiller.pod = nil
  417. podKiller.gracePeriodOverride = nil
  418. // remove disk pressure
  419. fakeClock.Step(20 * time.Minute)
  420. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  421. manager.synchronize(diskInfoProvider, activePodsFunc)
  422. // we should not have disk pressure
  423. if manager.IsUnderDiskPressure() {
  424. t.Errorf("Manager should not report disk pressure")
  425. }
  426. // induce disk pressure!
  427. fakeClock.Step(1 * time.Minute)
  428. summaryProvider.result = summaryStatsMaker("500Mi", "200Gi", podStats)
  429. manager.synchronize(diskInfoProvider, activePodsFunc)
  430. // we should have disk pressure
  431. if !manager.IsUnderDiskPressure() {
  432. t.Errorf("Manager should report disk pressure")
  433. }
  434. // check the right pod was killed
  435. if podKiller.pod != pods[0] {
  436. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  437. }
  438. observedGracePeriod = *podKiller.gracePeriodOverride
  439. if observedGracePeriod != int64(0) {
  440. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  441. }
  442. // try to admit our pod (should fail)
  443. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit {
  444. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit)
  445. }
  446. // reduce disk pressure
  447. fakeClock.Step(1 * time.Minute)
  448. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  449. podKiller.pod = nil // reset state
  450. manager.synchronize(diskInfoProvider, activePodsFunc)
  451. // we should have disk pressure (because transition period not yet met)
  452. if !manager.IsUnderDiskPressure() {
  453. t.Errorf("Manager should report disk pressure")
  454. }
  455. // no pod should have been killed
  456. if podKiller.pod != nil {
  457. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  458. }
  459. // try to admit our pod (should fail)
  460. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit {
  461. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit)
  462. }
  463. // move the clock past transition period to ensure that we stop reporting pressure
  464. fakeClock.Step(5 * time.Minute)
  465. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  466. podKiller.pod = nil // reset state
  467. manager.synchronize(diskInfoProvider, activePodsFunc)
  468. // we should not have disk pressure (because transition period met)
  469. if manager.IsUnderDiskPressure() {
  470. t.Errorf("Manager should not report disk pressure")
  471. }
  472. // no pod should have been killed
  473. if podKiller.pod != nil {
  474. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  475. }
  476. // try to admit our pod (should succeed)
  477. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit {
  478. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit)
  479. }
  480. }
  481. // TestMinReclaim verifies that min-reclaim works as desired.
  482. func TestMinReclaim(t *testing.T) {
  483. podMaker := func(name string, requests api.ResourceList, limits api.ResourceList, memoryWorkingSet string) (*api.Pod, statsapi.PodStats) {
  484. pod := newPod(name, []api.Container{
  485. newContainer(name, requests, limits),
  486. }, nil)
  487. podStats := newPodMemoryStats(pod, resource.MustParse(memoryWorkingSet))
  488. return pod, podStats
  489. }
  490. summaryStatsMaker := func(nodeAvailableBytes string, podStats map[*api.Pod]statsapi.PodStats) *statsapi.Summary {
  491. val := resource.MustParse(nodeAvailableBytes)
  492. availableBytes := uint64(val.Value())
  493. WorkingSetBytes := uint64(val.Value())
  494. result := &statsapi.Summary{
  495. Node: statsapi.NodeStats{
  496. Memory: &statsapi.MemoryStats{
  497. AvailableBytes: &availableBytes,
  498. WorkingSetBytes: &WorkingSetBytes,
  499. },
  500. },
  501. Pods: []statsapi.PodStats{},
  502. }
  503. for _, podStat := range podStats {
  504. result.Pods = append(result.Pods, podStat)
  505. }
  506. return result
  507. }
  508. podsToMake := []struct {
  509. name string
  510. requests api.ResourceList
  511. limits api.ResourceList
  512. memoryWorkingSet string
  513. }{
  514. {name: "best-effort-high", requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "500Mi"},
  515. {name: "best-effort-low", requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "300Mi"},
  516. {name: "burstable-high", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "800Mi"},
  517. {name: "burstable-low", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "300Mi"},
  518. {name: "guaranteed-high", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "800Mi"},
  519. {name: "guaranteed-low", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "200Mi"},
  520. }
  521. pods := []*api.Pod{}
  522. podStats := map[*api.Pod]statsapi.PodStats{}
  523. for _, podToMake := range podsToMake {
  524. pod, podStat := podMaker(podToMake.name, podToMake.requests, podToMake.limits, podToMake.memoryWorkingSet)
  525. pods = append(pods, pod)
  526. podStats[pod] = podStat
  527. }
  528. activePodsFunc := func() []*api.Pod {
  529. return pods
  530. }
  531. fakeClock := clock.NewFakeClock(time.Now())
  532. podKiller := &mockPodKiller{}
  533. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  534. imageGC := &mockImageGC{freed: int64(0), err: nil}
  535. nodeRef := &api.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  536. config := Config{
  537. MaxPodGracePeriodSeconds: 5,
  538. PressureTransitionPeriod: time.Minute * 5,
  539. Thresholds: []Threshold{
  540. {
  541. Signal: SignalMemoryAvailable,
  542. Operator: OpLessThan,
  543. Value: ThresholdValue{
  544. Quantity: quantityMustParse("1Gi"),
  545. },
  546. MinReclaim: quantityMustParse("500Mi"),
  547. },
  548. },
  549. }
  550. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("2Gi", podStats)}
  551. manager := &managerImpl{
  552. clock: fakeClock,
  553. killPodFunc: podKiller.killPodNow,
  554. imageGC: imageGC,
  555. config: config,
  556. recorder: &record.FakeRecorder{},
  557. summaryProvider: summaryProvider,
  558. nodeRef: nodeRef,
  559. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  560. thresholdsFirstObservedAt: thresholdsObservedAt{},
  561. }
  562. // synchronize
  563. manager.synchronize(diskInfoProvider, activePodsFunc)
  564. // we should not have memory pressure
  565. if manager.IsUnderMemoryPressure() {
  566. t.Errorf("Manager should not report memory pressure")
  567. }
  568. // induce memory pressure!
  569. fakeClock.Step(1 * time.Minute)
  570. summaryProvider.result = summaryStatsMaker("500Mi", podStats)
  571. manager.synchronize(diskInfoProvider, activePodsFunc)
  572. // we should have memory pressure
  573. if !manager.IsUnderMemoryPressure() {
  574. t.Errorf("Manager should report memory pressure")
  575. }
  576. // check the right pod was killed
  577. if podKiller.pod != pods[0] {
  578. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  579. }
  580. observedGracePeriod := *podKiller.gracePeriodOverride
  581. if observedGracePeriod != int64(0) {
  582. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  583. }
  584. // reduce memory pressure, but not below the min-reclaim amount
  585. fakeClock.Step(1 * time.Minute)
  586. summaryProvider.result = summaryStatsMaker("1.2Gi", podStats)
  587. podKiller.pod = nil // reset state
  588. manager.synchronize(diskInfoProvider, activePodsFunc)
  589. // we should have memory pressure (because transition period not yet met)
  590. if !manager.IsUnderMemoryPressure() {
  591. t.Errorf("Manager should report memory pressure")
  592. }
  593. // check the right pod was killed
  594. if podKiller.pod != pods[0] {
  595. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  596. }
  597. observedGracePeriod = *podKiller.gracePeriodOverride
  598. if observedGracePeriod != int64(0) {
  599. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  600. }
  601. // reduce memory pressure and ensure the min-reclaim amount
  602. fakeClock.Step(1 * time.Minute)
  603. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  604. podKiller.pod = nil // reset state
  605. manager.synchronize(diskInfoProvider, activePodsFunc)
  606. // we should have memory pressure (because transition period not yet met)
  607. if !manager.IsUnderMemoryPressure() {
  608. t.Errorf("Manager should report memory pressure")
  609. }
  610. // no pod should have been killed
  611. if podKiller.pod != nil {
  612. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  613. }
  614. // move the clock past transition period to ensure that we stop reporting pressure
  615. fakeClock.Step(5 * time.Minute)
  616. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  617. podKiller.pod = nil // reset state
  618. manager.synchronize(diskInfoProvider, activePodsFunc)
  619. // we should not have memory pressure (because transition period met)
  620. if manager.IsUnderMemoryPressure() {
  621. t.Errorf("Manager should not report memory pressure")
  622. }
  623. // no pod should have been killed
  624. if podKiller.pod != nil {
  625. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  626. }
  627. }
  628. func TestNodeReclaimFuncs(t *testing.T) {
  629. podMaker := func(name string, requests api.ResourceList, limits api.ResourceList, rootFsUsed, logsUsed, perLocalVolumeUsed string) (*api.Pod, statsapi.PodStats) {
  630. pod := newPod(name, []api.Container{
  631. newContainer(name, requests, limits),
  632. }, nil)
  633. podStats := newPodDiskStats(pod, parseQuantity(rootFsUsed), parseQuantity(logsUsed), parseQuantity(perLocalVolumeUsed))
  634. return pod, podStats
  635. }
  636. summaryStatsMaker := func(rootFsAvailableBytes, imageFsAvailableBytes string, podStats map[*api.Pod]statsapi.PodStats) *statsapi.Summary {
  637. rootFsVal := resource.MustParse(rootFsAvailableBytes)
  638. rootFsBytes := uint64(rootFsVal.Value())
  639. rootFsCapacityBytes := uint64(rootFsVal.Value() * 2)
  640. imageFsVal := resource.MustParse(imageFsAvailableBytes)
  641. imageFsBytes := uint64(imageFsVal.Value())
  642. imageFsCapacityBytes := uint64(imageFsVal.Value() * 2)
  643. result := &statsapi.Summary{
  644. Node: statsapi.NodeStats{
  645. Fs: &statsapi.FsStats{
  646. AvailableBytes: &rootFsBytes,
  647. CapacityBytes: &rootFsCapacityBytes,
  648. },
  649. Runtime: &statsapi.RuntimeStats{
  650. ImageFs: &statsapi.FsStats{
  651. AvailableBytes: &imageFsBytes,
  652. CapacityBytes: &imageFsCapacityBytes,
  653. },
  654. },
  655. },
  656. Pods: []statsapi.PodStats{},
  657. }
  658. for _, podStat := range podStats {
  659. result.Pods = append(result.Pods, podStat)
  660. }
  661. return result
  662. }
  663. podsToMake := []struct {
  664. name string
  665. requests api.ResourceList
  666. limits api.ResourceList
  667. rootFsUsed string
  668. logsFsUsed string
  669. perLocalVolumeUsed string
  670. }{
  671. {name: "best-effort-high", requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "500Mi"},
  672. {name: "best-effort-low", requests: newResourceList("", ""), limits: newResourceList("", ""), perLocalVolumeUsed: "300Mi"},
  673. {name: "burstable-high", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsUsed: "800Mi"},
  674. {name: "burstable-low", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), logsFsUsed: "300Mi"},
  675. {name: "guaranteed-high", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "800Mi"},
  676. {name: "guaranteed-low", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "200Mi"},
  677. }
  678. pods := []*api.Pod{}
  679. podStats := map[*api.Pod]statsapi.PodStats{}
  680. for _, podToMake := range podsToMake {
  681. pod, podStat := podMaker(podToMake.name, podToMake.requests, podToMake.limits, podToMake.rootFsUsed, podToMake.logsFsUsed, podToMake.perLocalVolumeUsed)
  682. pods = append(pods, pod)
  683. podStats[pod] = podStat
  684. }
  685. activePodsFunc := func() []*api.Pod {
  686. return pods
  687. }
  688. fakeClock := clock.NewFakeClock(time.Now())
  689. podKiller := &mockPodKiller{}
  690. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  691. imageGcFree := resource.MustParse("700Mi")
  692. imageGC := &mockImageGC{freed: imageGcFree.Value(), err: nil}
  693. nodeRef := &api.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  694. config := Config{
  695. MaxPodGracePeriodSeconds: 5,
  696. PressureTransitionPeriod: time.Minute * 5,
  697. Thresholds: []Threshold{
  698. {
  699. Signal: SignalNodeFsAvailable,
  700. Operator: OpLessThan,
  701. Value: ThresholdValue{
  702. Quantity: quantityMustParse("1Gi"),
  703. },
  704. MinReclaim: quantityMustParse("500Mi"),
  705. },
  706. },
  707. }
  708. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("16Gi", "200Gi", podStats)}
  709. manager := &managerImpl{
  710. clock: fakeClock,
  711. killPodFunc: podKiller.killPodNow,
  712. imageGC: imageGC,
  713. config: config,
  714. recorder: &record.FakeRecorder{},
  715. summaryProvider: summaryProvider,
  716. nodeRef: nodeRef,
  717. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  718. thresholdsFirstObservedAt: thresholdsObservedAt{},
  719. }
  720. // synchronize
  721. manager.synchronize(diskInfoProvider, activePodsFunc)
  722. // we should not have disk pressure
  723. if manager.IsUnderDiskPressure() {
  724. t.Errorf("Manager should not report disk pressure")
  725. }
  726. // induce hard threshold
  727. fakeClock.Step(1 * time.Minute)
  728. summaryProvider.result = summaryStatsMaker(".9Gi", "200Gi", podStats)
  729. manager.synchronize(diskInfoProvider, activePodsFunc)
  730. // we should have disk pressure
  731. if !manager.IsUnderDiskPressure() {
  732. t.Errorf("Manager should report disk pressure since soft threshold was met")
  733. }
  734. // verify image gc was invoked
  735. if !imageGC.invoked {
  736. t.Errorf("Manager should have invoked image gc")
  737. }
  738. // verify no pod was killed because image gc was sufficient
  739. if podKiller.pod != nil {
  740. t.Errorf("Manager should not have killed a pod, but killed: %v", podKiller.pod)
  741. }
  742. // reset state
  743. imageGC.invoked = false
  744. // remove disk pressure
  745. fakeClock.Step(20 * time.Minute)
  746. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  747. manager.synchronize(diskInfoProvider, activePodsFunc)
  748. // we should not have disk pressure
  749. if manager.IsUnderDiskPressure() {
  750. t.Errorf("Manager should not report disk pressure")
  751. }
  752. // induce disk pressure!
  753. fakeClock.Step(1 * time.Minute)
  754. summaryProvider.result = summaryStatsMaker("400Mi", "200Gi", podStats)
  755. manager.synchronize(diskInfoProvider, activePodsFunc)
  756. // we should have disk pressure
  757. if !manager.IsUnderDiskPressure() {
  758. t.Errorf("Manager should report disk pressure")
  759. }
  760. // ensure image gc was invoked
  761. if !imageGC.invoked {
  762. t.Errorf("Manager should have invoked image gc")
  763. }
  764. // check the right pod was killed
  765. if podKiller.pod != pods[0] {
  766. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  767. }
  768. observedGracePeriod := *podKiller.gracePeriodOverride
  769. if observedGracePeriod != int64(0) {
  770. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  771. }
  772. // reduce disk pressure
  773. fakeClock.Step(1 * time.Minute)
  774. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  775. imageGC.invoked = false // reset state
  776. podKiller.pod = nil // reset state
  777. manager.synchronize(diskInfoProvider, activePodsFunc)
  778. // we should have disk pressure (because transition period not yet met)
  779. if !manager.IsUnderDiskPressure() {
  780. t.Errorf("Manager should report disk pressure")
  781. }
  782. // no image gc should have occurred
  783. if imageGC.invoked {
  784. t.Errorf("Manager chose to perform image gc when it was not neeed")
  785. }
  786. // no pod should have been killed
  787. if podKiller.pod != nil {
  788. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  789. }
  790. // move the clock past transition period to ensure that we stop reporting pressure
  791. fakeClock.Step(5 * time.Minute)
  792. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  793. imageGC.invoked = false // reset state
  794. podKiller.pod = nil // reset state
  795. manager.synchronize(diskInfoProvider, activePodsFunc)
  796. // we should not have disk pressure (because transition period met)
  797. if manager.IsUnderDiskPressure() {
  798. t.Errorf("Manager should not report disk pressure")
  799. }
  800. // no image gc should have occurred
  801. if imageGC.invoked {
  802. t.Errorf("Manager chose to perform image gc when it was not neeed")
  803. }
  804. // no pod should have been killed
  805. if podKiller.pod != nil {
  806. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  807. }
  808. }
  809. func TestDiskPressureNodeFsInodes(t *testing.T) {
  810. // TODO: we need to know inodes used when cadvisor supports per container stats
  811. podMaker := func(name string, requests api.ResourceList, limits api.ResourceList) (*api.Pod, statsapi.PodStats) {
  812. pod := newPod(name, []api.Container{
  813. newContainer(name, requests, limits),
  814. }, nil)
  815. podStats := newPodInodeStats(pod)
  816. return pod, podStats
  817. }
  818. summaryStatsMaker := func(rootFsInodesFree, rootFsInodes string, podStats map[*api.Pod]statsapi.PodStats) *statsapi.Summary {
  819. rootFsInodesFreeVal := resource.MustParse(rootFsInodesFree)
  820. internalRootFsInodesFree := uint64(rootFsInodesFreeVal.Value())
  821. rootFsInodesVal := resource.MustParse(rootFsInodes)
  822. internalRootFsInodes := uint64(rootFsInodesVal.Value())
  823. result := &statsapi.Summary{
  824. Node: statsapi.NodeStats{
  825. Fs: &statsapi.FsStats{
  826. InodesFree: &internalRootFsInodesFree,
  827. Inodes: &internalRootFsInodes,
  828. },
  829. },
  830. Pods: []statsapi.PodStats{},
  831. }
  832. for _, podStat := range podStats {
  833. result.Pods = append(result.Pods, podStat)
  834. }
  835. return result
  836. }
  837. // TODO: pass inodes used in future when supported by cadvisor.
  838. podsToMake := []struct {
  839. name string
  840. requests api.ResourceList
  841. limits api.ResourceList
  842. }{
  843. {name: "best-effort-high", requests: newResourceList("", ""), limits: newResourceList("", "")},
  844. {name: "best-effort-low", requests: newResourceList("", ""), limits: newResourceList("", "")},
  845. {name: "burstable-high", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi")},
  846. {name: "burstable-low", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi")},
  847. {name: "guaranteed-high", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi")},
  848. {name: "guaranteed-low", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi")},
  849. }
  850. pods := []*api.Pod{}
  851. podStats := map[*api.Pod]statsapi.PodStats{}
  852. for _, podToMake := range podsToMake {
  853. pod, podStat := podMaker(podToMake.name, podToMake.requests, podToMake.limits)
  854. pods = append(pods, pod)
  855. podStats[pod] = podStat
  856. }
  857. activePodsFunc := func() []*api.Pod {
  858. return pods
  859. }
  860. fakeClock := clock.NewFakeClock(time.Now())
  861. podKiller := &mockPodKiller{}
  862. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  863. imageGC := &mockImageGC{freed: int64(0), err: nil}
  864. nodeRef := &api.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  865. config := Config{
  866. MaxPodGracePeriodSeconds: 5,
  867. PressureTransitionPeriod: time.Minute * 5,
  868. Thresholds: []Threshold{
  869. {
  870. Signal: SignalNodeFsInodesFree,
  871. Operator: OpLessThan,
  872. Value: ThresholdValue{
  873. Quantity: quantityMustParse("1Mi"),
  874. },
  875. },
  876. {
  877. Signal: SignalNodeFsInodesFree,
  878. Operator: OpLessThan,
  879. Value: ThresholdValue{
  880. Quantity: quantityMustParse("2Mi"),
  881. },
  882. GracePeriod: time.Minute * 2,
  883. },
  884. },
  885. }
  886. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("3Mi", "4Mi", podStats)}
  887. manager := &managerImpl{
  888. clock: fakeClock,
  889. killPodFunc: podKiller.killPodNow,
  890. imageGC: imageGC,
  891. config: config,
  892. recorder: &record.FakeRecorder{},
  893. summaryProvider: summaryProvider,
  894. nodeRef: nodeRef,
  895. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  896. thresholdsFirstObservedAt: thresholdsObservedAt{},
  897. }
  898. // create a best effort pod to test admission
  899. podToAdmit, _ := podMaker("pod-to-admit", newResourceList("", ""), newResourceList("", ""))
  900. // synchronize
  901. manager.synchronize(diskInfoProvider, activePodsFunc)
  902. // we should not have disk pressure
  903. if manager.IsUnderDiskPressure() {
  904. t.Errorf("Manager should not report disk pressure")
  905. }
  906. // try to admit our pod (should succeed)
  907. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit {
  908. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit)
  909. }
  910. // induce soft threshold
  911. fakeClock.Step(1 * time.Minute)
  912. summaryProvider.result = summaryStatsMaker("1.5Mi", "4Mi", podStats)
  913. manager.synchronize(diskInfoProvider, activePodsFunc)
  914. // we should have disk pressure
  915. if !manager.IsUnderDiskPressure() {
  916. t.Errorf("Manager should report disk pressure since soft threshold was met")
  917. }
  918. // verify no pod was yet killed because there has not yet been enough time passed.
  919. if podKiller.pod != nil {
  920. t.Errorf("Manager should not have killed a pod yet, but killed: %v", podKiller.pod)
  921. }
  922. // step forward in time pass the grace period
  923. fakeClock.Step(3 * time.Minute)
  924. summaryProvider.result = summaryStatsMaker("1.5Mi", "4Mi", podStats)
  925. manager.synchronize(diskInfoProvider, activePodsFunc)
  926. // we should have disk pressure
  927. if !manager.IsUnderDiskPressure() {
  928. t.Errorf("Manager should report disk pressure since soft threshold was met")
  929. }
  930. // verify the right pod was killed with the right grace period.
  931. if podKiller.pod != pods[0] {
  932. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  933. }
  934. if podKiller.gracePeriodOverride == nil {
  935. t.Errorf("Manager chose to kill pod but should have had a grace period override.")
  936. }
  937. observedGracePeriod := *podKiller.gracePeriodOverride
  938. if observedGracePeriod != manager.config.MaxPodGracePeriodSeconds {
  939. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", manager.config.MaxPodGracePeriodSeconds, observedGracePeriod)
  940. }
  941. // reset state
  942. podKiller.pod = nil
  943. podKiller.gracePeriodOverride = nil
  944. // remove disk pressure
  945. fakeClock.Step(20 * time.Minute)
  946. summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats)
  947. manager.synchronize(diskInfoProvider, activePodsFunc)
  948. // we should not have disk pressure
  949. if manager.IsUnderDiskPressure() {
  950. t.Errorf("Manager should not report disk pressure")
  951. }
  952. // induce disk pressure!
  953. fakeClock.Step(1 * time.Minute)
  954. summaryProvider.result = summaryStatsMaker("0.5Mi", "4Mi", podStats)
  955. manager.synchronize(diskInfoProvider, activePodsFunc)
  956. // we should have disk pressure
  957. if !manager.IsUnderDiskPressure() {
  958. t.Errorf("Manager should report disk pressure")
  959. }
  960. // check the right pod was killed
  961. if podKiller.pod != pods[0] {
  962. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod, pods[0])
  963. }
  964. observedGracePeriod = *podKiller.gracePeriodOverride
  965. if observedGracePeriod != int64(0) {
  966. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  967. }
  968. // try to admit our pod (should fail)
  969. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit {
  970. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit)
  971. }
  972. // reduce disk pressure
  973. fakeClock.Step(1 * time.Minute)
  974. summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats)
  975. podKiller.pod = nil // reset state
  976. manager.synchronize(diskInfoProvider, activePodsFunc)
  977. // we should have disk pressure (because transition period not yet met)
  978. if !manager.IsUnderDiskPressure() {
  979. t.Errorf("Manager should report disk pressure")
  980. }
  981. // no pod should have been killed
  982. if podKiller.pod != nil {
  983. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  984. }
  985. // try to admit our pod (should fail)
  986. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit {
  987. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit)
  988. }
  989. // move the clock past transition period to ensure that we stop reporting pressure
  990. fakeClock.Step(5 * time.Minute)
  991. summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats)
  992. podKiller.pod = nil // reset state
  993. manager.synchronize(diskInfoProvider, activePodsFunc)
  994. // we should not have disk pressure (because transition period met)
  995. if manager.IsUnderDiskPressure() {
  996. t.Errorf("Manager should not report disk pressure")
  997. }
  998. // no pod should have been killed
  999. if podKiller.pod != nil {
  1000. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod)
  1001. }
  1002. // try to admit our pod (should succeed)
  1003. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit {
  1004. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit)
  1005. }
  1006. }