eviction_manager_test.go 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514
  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. "testing"
  17. "time"
  18. "k8s.io/api/core/v1"
  19. "k8s.io/apimachinery/pkg/api/resource"
  20. "k8s.io/apimachinery/pkg/types"
  21. "k8s.io/apimachinery/pkg/util/clock"
  22. utilfeature "k8s.io/apiserver/pkg/util/feature"
  23. "k8s.io/client-go/tools/record"
  24. featuregatetesting "k8s.io/component-base/featuregate/testing"
  25. kubeapi "k8s.io/kubernetes/pkg/apis/core"
  26. "k8s.io/kubernetes/pkg/features"
  27. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  28. evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
  29. "k8s.io/kubernetes/pkg/kubelet/lifecycle"
  30. kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
  31. )
  32. const (
  33. lowPriority = -1
  34. defaultPriority = 0
  35. highPriority = 1
  36. )
  37. // mockPodKiller is used to testing which pod is killed
  38. type mockPodKiller struct {
  39. pod *v1.Pod
  40. status v1.PodStatus
  41. gracePeriodOverride *int64
  42. }
  43. // killPodNow records the pod that was killed
  44. func (m *mockPodKiller) killPodNow(pod *v1.Pod, status v1.PodStatus, gracePeriodOverride *int64) error {
  45. m.pod = pod
  46. m.status = status
  47. m.gracePeriodOverride = gracePeriodOverride
  48. return nil
  49. }
  50. // mockDiskInfoProvider is used to simulate testing.
  51. type mockDiskInfoProvider struct {
  52. dedicatedImageFs bool
  53. }
  54. // HasDedicatedImageFs returns the mocked value
  55. func (m *mockDiskInfoProvider) HasDedicatedImageFs() (bool, error) {
  56. return m.dedicatedImageFs, nil
  57. }
  58. // mockDiskGC is used to simulate invoking image and container garbage collection.
  59. type mockDiskGC struct {
  60. err error
  61. imageGCInvoked bool
  62. containerGCInvoked bool
  63. fakeSummaryProvider *fakeSummaryProvider
  64. summaryAfterGC *statsapi.Summary
  65. }
  66. // DeleteUnusedImages returns the mocked values.
  67. func (m *mockDiskGC) DeleteUnusedImages() error {
  68. m.imageGCInvoked = true
  69. if m.summaryAfterGC != nil && m.fakeSummaryProvider != nil {
  70. m.fakeSummaryProvider.result = m.summaryAfterGC
  71. }
  72. return m.err
  73. }
  74. // DeleteAllUnusedContainers returns the mocked value
  75. func (m *mockDiskGC) DeleteAllUnusedContainers() error {
  76. m.containerGCInvoked = true
  77. if m.summaryAfterGC != nil && m.fakeSummaryProvider != nil {
  78. m.fakeSummaryProvider.result = m.summaryAfterGC
  79. }
  80. return m.err
  81. }
  82. func makePodWithMemoryStats(name string, priority int32, requests v1.ResourceList, limits v1.ResourceList, memoryWorkingSet string) (*v1.Pod, statsapi.PodStats) {
  83. pod := newPod(name, priority, []v1.Container{
  84. newContainer(name, requests, limits),
  85. }, nil)
  86. podStats := newPodMemoryStats(pod, resource.MustParse(memoryWorkingSet))
  87. return pod, podStats
  88. }
  89. func makePodWithDiskStats(name string, priority int32, requests v1.ResourceList, limits v1.ResourceList, rootFsUsed, logsUsed, perLocalVolumeUsed string) (*v1.Pod, statsapi.PodStats) {
  90. pod := newPod(name, priority, []v1.Container{
  91. newContainer(name, requests, limits),
  92. }, nil)
  93. podStats := newPodDiskStats(pod, parseQuantity(rootFsUsed), parseQuantity(logsUsed), parseQuantity(perLocalVolumeUsed))
  94. return pod, podStats
  95. }
  96. func makeMemoryStats(nodeAvailableBytes string, podStats map[*v1.Pod]statsapi.PodStats) *statsapi.Summary {
  97. val := resource.MustParse(nodeAvailableBytes)
  98. availableBytes := uint64(val.Value())
  99. WorkingSetBytes := uint64(val.Value())
  100. result := &statsapi.Summary{
  101. Node: statsapi.NodeStats{
  102. Memory: &statsapi.MemoryStats{
  103. AvailableBytes: &availableBytes,
  104. WorkingSetBytes: &WorkingSetBytes,
  105. },
  106. SystemContainers: []statsapi.ContainerStats{
  107. {
  108. Name: statsapi.SystemContainerPods,
  109. Memory: &statsapi.MemoryStats{
  110. AvailableBytes: &availableBytes,
  111. WorkingSetBytes: &WorkingSetBytes,
  112. },
  113. },
  114. },
  115. },
  116. Pods: []statsapi.PodStats{},
  117. }
  118. for _, podStat := range podStats {
  119. result.Pods = append(result.Pods, podStat)
  120. }
  121. return result
  122. }
  123. func makeDiskStats(rootFsAvailableBytes, imageFsAvailableBytes string, podStats map[*v1.Pod]statsapi.PodStats) *statsapi.Summary {
  124. rootFsVal := resource.MustParse(rootFsAvailableBytes)
  125. rootFsBytes := uint64(rootFsVal.Value())
  126. rootFsCapacityBytes := uint64(rootFsVal.Value() * 2)
  127. imageFsVal := resource.MustParse(imageFsAvailableBytes)
  128. imageFsBytes := uint64(imageFsVal.Value())
  129. imageFsCapacityBytes := uint64(imageFsVal.Value() * 2)
  130. result := &statsapi.Summary{
  131. Node: statsapi.NodeStats{
  132. Fs: &statsapi.FsStats{
  133. AvailableBytes: &rootFsBytes,
  134. CapacityBytes: &rootFsCapacityBytes,
  135. },
  136. Runtime: &statsapi.RuntimeStats{
  137. ImageFs: &statsapi.FsStats{
  138. AvailableBytes: &imageFsBytes,
  139. CapacityBytes: &imageFsCapacityBytes,
  140. },
  141. },
  142. },
  143. Pods: []statsapi.PodStats{},
  144. }
  145. for _, podStat := range podStats {
  146. result.Pods = append(result.Pods, podStat)
  147. }
  148. return result
  149. }
  150. type podToMake struct {
  151. name string
  152. priority int32
  153. requests v1.ResourceList
  154. limits v1.ResourceList
  155. memoryWorkingSet string
  156. rootFsUsed string
  157. logsFsUsed string
  158. logsFsInodesUsed string
  159. rootFsInodesUsed string
  160. perLocalVolumeUsed string
  161. perLocalVolumeInodesUsed string
  162. }
  163. // TestMemoryPressure
  164. func TestMemoryPressure(t *testing.T) {
  165. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
  166. podMaker := makePodWithMemoryStats
  167. summaryStatsMaker := makeMemoryStats
  168. podsToMake := []podToMake{
  169. {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), memoryWorkingSet: "900Mi"},
  170. {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "50Mi"},
  171. {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "400Mi"},
  172. {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "400Mi"},
  173. {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "100Mi"},
  174. }
  175. pods := []*v1.Pod{}
  176. podStats := map[*v1.Pod]statsapi.PodStats{}
  177. for _, podToMake := range podsToMake {
  178. pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.memoryWorkingSet)
  179. pods = append(pods, pod)
  180. podStats[pod] = podStat
  181. }
  182. podToEvict := pods[4]
  183. activePodsFunc := func() []*v1.Pod {
  184. return pods
  185. }
  186. fakeClock := clock.NewFakeClock(time.Now())
  187. podKiller := &mockPodKiller{}
  188. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  189. diskGC := &mockDiskGC{err: nil}
  190. nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  191. config := Config{
  192. MaxPodGracePeriodSeconds: 5,
  193. PressureTransitionPeriod: time.Minute * 5,
  194. Thresholds: []evictionapi.Threshold{
  195. {
  196. Signal: evictionapi.SignalMemoryAvailable,
  197. Operator: evictionapi.OpLessThan,
  198. Value: evictionapi.ThresholdValue{
  199. Quantity: quantityMustParse("1Gi"),
  200. },
  201. },
  202. {
  203. Signal: evictionapi.SignalMemoryAvailable,
  204. Operator: evictionapi.OpLessThan,
  205. Value: evictionapi.ThresholdValue{
  206. Quantity: quantityMustParse("2Gi"),
  207. },
  208. GracePeriod: time.Minute * 2,
  209. },
  210. },
  211. }
  212. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("2Gi", podStats)}
  213. manager := &managerImpl{
  214. clock: fakeClock,
  215. killPodFunc: podKiller.killPodNow,
  216. imageGC: diskGC,
  217. containerGC: diskGC,
  218. config: config,
  219. recorder: &record.FakeRecorder{},
  220. summaryProvider: summaryProvider,
  221. nodeRef: nodeRef,
  222. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  223. thresholdsFirstObservedAt: thresholdsObservedAt{},
  224. }
  225. // create a best effort pod to test admission
  226. bestEffortPodToAdmit, _ := podMaker("best-admit", defaultPriority, newResourceList("", "", ""), newResourceList("", "", ""), "0Gi")
  227. burstablePodToAdmit, _ := podMaker("burst-admit", defaultPriority, newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", ""), "0Gi")
  228. // synchronize
  229. manager.synchronize(diskInfoProvider, activePodsFunc)
  230. // we should not have memory pressure
  231. if manager.IsUnderMemoryPressure() {
  232. t.Errorf("Manager should not report memory pressure")
  233. }
  234. // try to admit our pods (they should succeed)
  235. expected := []bool{true, true}
  236. for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  237. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  238. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  239. }
  240. }
  241. // induce soft threshold
  242. fakeClock.Step(1 * time.Minute)
  243. summaryProvider.result = summaryStatsMaker("1500Mi", podStats)
  244. manager.synchronize(diskInfoProvider, activePodsFunc)
  245. // we should have memory pressure
  246. if !manager.IsUnderMemoryPressure() {
  247. t.Errorf("Manager should report memory pressure since soft threshold was met")
  248. }
  249. // verify no pod was yet killed because there has not yet been enough time passed.
  250. if podKiller.pod != nil {
  251. t.Errorf("Manager should not have killed a pod yet, but killed: %v", podKiller.pod.Name)
  252. }
  253. // step forward in time pass the grace period
  254. fakeClock.Step(3 * time.Minute)
  255. summaryProvider.result = summaryStatsMaker("1500Mi", podStats)
  256. manager.synchronize(diskInfoProvider, activePodsFunc)
  257. // we should have memory pressure
  258. if !manager.IsUnderMemoryPressure() {
  259. t.Errorf("Manager should report memory pressure since soft threshold was met")
  260. }
  261. // verify the right pod was killed with the right grace period.
  262. if podKiller.pod != podToEvict {
  263. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  264. }
  265. if podKiller.gracePeriodOverride == nil {
  266. t.Errorf("Manager chose to kill pod but should have had a grace period override.")
  267. }
  268. observedGracePeriod := *podKiller.gracePeriodOverride
  269. if observedGracePeriod != manager.config.MaxPodGracePeriodSeconds {
  270. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", manager.config.MaxPodGracePeriodSeconds, observedGracePeriod)
  271. }
  272. // reset state
  273. podKiller.pod = nil
  274. podKiller.gracePeriodOverride = nil
  275. // remove memory pressure
  276. fakeClock.Step(20 * time.Minute)
  277. summaryProvider.result = summaryStatsMaker("3Gi", podStats)
  278. manager.synchronize(diskInfoProvider, activePodsFunc)
  279. // we should not have memory pressure
  280. if manager.IsUnderMemoryPressure() {
  281. t.Errorf("Manager should not report memory pressure")
  282. }
  283. // induce memory pressure!
  284. fakeClock.Step(1 * time.Minute)
  285. summaryProvider.result = summaryStatsMaker("500Mi", podStats)
  286. manager.synchronize(diskInfoProvider, activePodsFunc)
  287. // we should have memory pressure
  288. if !manager.IsUnderMemoryPressure() {
  289. t.Errorf("Manager should report memory pressure")
  290. }
  291. // check the right pod was killed
  292. if podKiller.pod != podToEvict {
  293. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  294. }
  295. observedGracePeriod = *podKiller.gracePeriodOverride
  296. if observedGracePeriod != int64(0) {
  297. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  298. }
  299. // the best-effort pod should not admit, burstable should
  300. expected = []bool{false, true}
  301. for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  302. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  303. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  304. }
  305. }
  306. // reduce memory pressure
  307. fakeClock.Step(1 * time.Minute)
  308. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  309. podKiller.pod = nil // reset state
  310. manager.synchronize(diskInfoProvider, activePodsFunc)
  311. // we should have memory pressure (because transition period not yet met)
  312. if !manager.IsUnderMemoryPressure() {
  313. t.Errorf("Manager should report memory pressure")
  314. }
  315. // no pod should have been killed
  316. if podKiller.pod != nil {
  317. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  318. }
  319. // the best-effort pod should not admit, burstable should
  320. expected = []bool{false, true}
  321. for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  322. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  323. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  324. }
  325. }
  326. // move the clock past transition period to ensure that we stop reporting pressure
  327. fakeClock.Step(5 * time.Minute)
  328. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  329. podKiller.pod = nil // reset state
  330. manager.synchronize(diskInfoProvider, activePodsFunc)
  331. // we should not have memory pressure (because transition period met)
  332. if manager.IsUnderMemoryPressure() {
  333. t.Errorf("Manager should not report memory pressure")
  334. }
  335. // no pod should have been killed
  336. if podKiller.pod != nil {
  337. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  338. }
  339. // all pods should admit now
  340. expected = []bool{true, true}
  341. for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  342. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  343. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  344. }
  345. }
  346. }
  347. // parseQuantity parses the specified value (if provided) otherwise returns 0 value
  348. func parseQuantity(value string) resource.Quantity {
  349. if len(value) == 0 {
  350. return resource.MustParse("0")
  351. }
  352. return resource.MustParse(value)
  353. }
  354. func TestDiskPressureNodeFs(t *testing.T) {
  355. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LocalStorageCapacityIsolation, true)()
  356. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
  357. podMaker := makePodWithDiskStats
  358. summaryStatsMaker := makeDiskStats
  359. podsToMake := []podToMake{
  360. {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), rootFsUsed: "900Mi"},
  361. {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), logsFsUsed: "50Mi"},
  362. {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), rootFsUsed: "400Mi"},
  363. {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), perLocalVolumeUsed: "400Mi"},
  364. {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), rootFsUsed: "100Mi"},
  365. }
  366. pods := []*v1.Pod{}
  367. podStats := map[*v1.Pod]statsapi.PodStats{}
  368. for _, podToMake := range podsToMake {
  369. pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.rootFsUsed, podToMake.logsFsUsed, podToMake.perLocalVolumeUsed)
  370. pods = append(pods, pod)
  371. podStats[pod] = podStat
  372. }
  373. podToEvict := pods[0]
  374. activePodsFunc := func() []*v1.Pod {
  375. return pods
  376. }
  377. fakeClock := clock.NewFakeClock(time.Now())
  378. podKiller := &mockPodKiller{}
  379. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  380. diskGC := &mockDiskGC{err: nil}
  381. nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  382. config := Config{
  383. MaxPodGracePeriodSeconds: 5,
  384. PressureTransitionPeriod: time.Minute * 5,
  385. Thresholds: []evictionapi.Threshold{
  386. {
  387. Signal: evictionapi.SignalNodeFsAvailable,
  388. Operator: evictionapi.OpLessThan,
  389. Value: evictionapi.ThresholdValue{
  390. Quantity: quantityMustParse("1Gi"),
  391. },
  392. },
  393. {
  394. Signal: evictionapi.SignalNodeFsAvailable,
  395. Operator: evictionapi.OpLessThan,
  396. Value: evictionapi.ThresholdValue{
  397. Quantity: quantityMustParse("2Gi"),
  398. },
  399. GracePeriod: time.Minute * 2,
  400. },
  401. },
  402. }
  403. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("16Gi", "200Gi", podStats)}
  404. manager := &managerImpl{
  405. clock: fakeClock,
  406. killPodFunc: podKiller.killPodNow,
  407. imageGC: diskGC,
  408. containerGC: diskGC,
  409. config: config,
  410. recorder: &record.FakeRecorder{},
  411. summaryProvider: summaryProvider,
  412. nodeRef: nodeRef,
  413. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  414. thresholdsFirstObservedAt: thresholdsObservedAt{},
  415. }
  416. // create a best effort pod to test admission
  417. podToAdmit, _ := podMaker("pod-to-admit", defaultPriority, newResourceList("", "", ""), newResourceList("", "", ""), "0Gi", "0Gi", "0Gi")
  418. // synchronize
  419. manager.synchronize(diskInfoProvider, activePodsFunc)
  420. // we should not have disk pressure
  421. if manager.IsUnderDiskPressure() {
  422. t.Errorf("Manager should not report disk pressure")
  423. }
  424. // try to admit our pod (should succeed)
  425. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit {
  426. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit)
  427. }
  428. // induce soft threshold
  429. fakeClock.Step(1 * time.Minute)
  430. summaryProvider.result = summaryStatsMaker("1.5Gi", "200Gi", podStats)
  431. manager.synchronize(diskInfoProvider, activePodsFunc)
  432. // we should have disk pressure
  433. if !manager.IsUnderDiskPressure() {
  434. t.Errorf("Manager should report disk pressure since soft threshold was met")
  435. }
  436. // verify no pod was yet killed because there has not yet been enough time passed.
  437. if podKiller.pod != nil {
  438. t.Errorf("Manager should not have killed a pod yet, but killed: %v", podKiller.pod.Name)
  439. }
  440. // step forward in time pass the grace period
  441. fakeClock.Step(3 * time.Minute)
  442. summaryProvider.result = summaryStatsMaker("1.5Gi", "200Gi", podStats)
  443. manager.synchronize(diskInfoProvider, activePodsFunc)
  444. // we should have disk pressure
  445. if !manager.IsUnderDiskPressure() {
  446. t.Errorf("Manager should report disk pressure since soft threshold was met")
  447. }
  448. // verify the right pod was killed with the right grace period.
  449. if podKiller.pod != podToEvict {
  450. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  451. }
  452. if podKiller.gracePeriodOverride == nil {
  453. t.Errorf("Manager chose to kill pod but should have had a grace period override.")
  454. }
  455. observedGracePeriod := *podKiller.gracePeriodOverride
  456. if observedGracePeriod != manager.config.MaxPodGracePeriodSeconds {
  457. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", manager.config.MaxPodGracePeriodSeconds, observedGracePeriod)
  458. }
  459. // reset state
  460. podKiller.pod = nil
  461. podKiller.gracePeriodOverride = nil
  462. // remove disk pressure
  463. fakeClock.Step(20 * time.Minute)
  464. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  465. manager.synchronize(diskInfoProvider, activePodsFunc)
  466. // we should not have disk pressure
  467. if manager.IsUnderDiskPressure() {
  468. t.Errorf("Manager should not report disk pressure")
  469. }
  470. // induce disk pressure!
  471. fakeClock.Step(1 * time.Minute)
  472. summaryProvider.result = summaryStatsMaker("500Mi", "200Gi", podStats)
  473. manager.synchronize(diskInfoProvider, activePodsFunc)
  474. // we should have disk pressure
  475. if !manager.IsUnderDiskPressure() {
  476. t.Errorf("Manager should report disk pressure")
  477. }
  478. // check the right pod was killed
  479. if podKiller.pod != podToEvict {
  480. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  481. }
  482. observedGracePeriod = *podKiller.gracePeriodOverride
  483. if observedGracePeriod != int64(0) {
  484. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  485. }
  486. // try to admit our pod (should fail)
  487. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit {
  488. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit)
  489. }
  490. // reduce disk pressure
  491. fakeClock.Step(1 * time.Minute)
  492. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  493. podKiller.pod = nil // reset state
  494. manager.synchronize(diskInfoProvider, activePodsFunc)
  495. // we should have disk pressure (because transition period not yet met)
  496. if !manager.IsUnderDiskPressure() {
  497. t.Errorf("Manager should report disk pressure")
  498. }
  499. // no pod should have been killed
  500. if podKiller.pod != nil {
  501. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  502. }
  503. // try to admit our pod (should fail)
  504. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit {
  505. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit)
  506. }
  507. // move the clock past transition period to ensure that we stop reporting pressure
  508. fakeClock.Step(5 * time.Minute)
  509. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  510. podKiller.pod = nil // reset state
  511. manager.synchronize(diskInfoProvider, activePodsFunc)
  512. // we should not have disk pressure (because transition period met)
  513. if manager.IsUnderDiskPressure() {
  514. t.Errorf("Manager should not report disk pressure")
  515. }
  516. // no pod should have been killed
  517. if podKiller.pod != nil {
  518. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  519. }
  520. // try to admit our pod (should succeed)
  521. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit {
  522. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit)
  523. }
  524. }
  525. // TestMinReclaim verifies that min-reclaim works as desired.
  526. func TestMinReclaim(t *testing.T) {
  527. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
  528. podMaker := makePodWithMemoryStats
  529. summaryStatsMaker := makeMemoryStats
  530. podsToMake := []podToMake{
  531. {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), memoryWorkingSet: "900Mi"},
  532. {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "50Mi"},
  533. {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "400Mi"},
  534. {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "400Mi"},
  535. {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "100Mi"},
  536. }
  537. pods := []*v1.Pod{}
  538. podStats := map[*v1.Pod]statsapi.PodStats{}
  539. for _, podToMake := range podsToMake {
  540. pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.memoryWorkingSet)
  541. pods = append(pods, pod)
  542. podStats[pod] = podStat
  543. }
  544. podToEvict := pods[4]
  545. activePodsFunc := func() []*v1.Pod {
  546. return pods
  547. }
  548. fakeClock := clock.NewFakeClock(time.Now())
  549. podKiller := &mockPodKiller{}
  550. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  551. diskGC := &mockDiskGC{err: nil}
  552. nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  553. config := Config{
  554. MaxPodGracePeriodSeconds: 5,
  555. PressureTransitionPeriod: time.Minute * 5,
  556. Thresholds: []evictionapi.Threshold{
  557. {
  558. Signal: evictionapi.SignalMemoryAvailable,
  559. Operator: evictionapi.OpLessThan,
  560. Value: evictionapi.ThresholdValue{
  561. Quantity: quantityMustParse("1Gi"),
  562. },
  563. MinReclaim: &evictionapi.ThresholdValue{
  564. Quantity: quantityMustParse("500Mi"),
  565. },
  566. },
  567. },
  568. }
  569. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("2Gi", podStats)}
  570. manager := &managerImpl{
  571. clock: fakeClock,
  572. killPodFunc: podKiller.killPodNow,
  573. imageGC: diskGC,
  574. containerGC: diskGC,
  575. config: config,
  576. recorder: &record.FakeRecorder{},
  577. summaryProvider: summaryProvider,
  578. nodeRef: nodeRef,
  579. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  580. thresholdsFirstObservedAt: thresholdsObservedAt{},
  581. }
  582. // synchronize
  583. manager.synchronize(diskInfoProvider, activePodsFunc)
  584. // we should not have memory pressure
  585. if manager.IsUnderMemoryPressure() {
  586. t.Errorf("Manager should not report memory pressure")
  587. }
  588. // induce memory pressure!
  589. fakeClock.Step(1 * time.Minute)
  590. summaryProvider.result = summaryStatsMaker("500Mi", podStats)
  591. manager.synchronize(diskInfoProvider, activePodsFunc)
  592. // we should have memory pressure
  593. if !manager.IsUnderMemoryPressure() {
  594. t.Errorf("Manager should report memory pressure")
  595. }
  596. // check the right pod was killed
  597. if podKiller.pod != podToEvict {
  598. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  599. }
  600. observedGracePeriod := *podKiller.gracePeriodOverride
  601. if observedGracePeriod != int64(0) {
  602. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  603. }
  604. // reduce memory pressure, but not below the min-reclaim amount
  605. fakeClock.Step(1 * time.Minute)
  606. summaryProvider.result = summaryStatsMaker("1.2Gi", podStats)
  607. podKiller.pod = nil // reset state
  608. manager.synchronize(diskInfoProvider, activePodsFunc)
  609. // we should have memory pressure (because transition period not yet met)
  610. if !manager.IsUnderMemoryPressure() {
  611. t.Errorf("Manager should report memory pressure")
  612. }
  613. // check the right pod was killed
  614. if podKiller.pod != podToEvict {
  615. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  616. }
  617. observedGracePeriod = *podKiller.gracePeriodOverride
  618. if observedGracePeriod != int64(0) {
  619. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  620. }
  621. // reduce memory pressure and ensure the min-reclaim amount
  622. fakeClock.Step(1 * time.Minute)
  623. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  624. podKiller.pod = nil // reset state
  625. manager.synchronize(diskInfoProvider, activePodsFunc)
  626. // we should have memory pressure (because transition period not yet met)
  627. if !manager.IsUnderMemoryPressure() {
  628. t.Errorf("Manager should report memory pressure")
  629. }
  630. // no pod should have been killed
  631. if podKiller.pod != nil {
  632. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  633. }
  634. // move the clock past transition period to ensure that we stop reporting pressure
  635. fakeClock.Step(5 * time.Minute)
  636. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  637. podKiller.pod = nil // reset state
  638. manager.synchronize(diskInfoProvider, activePodsFunc)
  639. // we should not have memory pressure (because transition period met)
  640. if manager.IsUnderMemoryPressure() {
  641. t.Errorf("Manager should not report memory pressure")
  642. }
  643. // no pod should have been killed
  644. if podKiller.pod != nil {
  645. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  646. }
  647. }
  648. func TestNodeReclaimFuncs(t *testing.T) {
  649. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LocalStorageCapacityIsolation, true)()
  650. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
  651. podMaker := makePodWithDiskStats
  652. summaryStatsMaker := makeDiskStats
  653. podsToMake := []podToMake{
  654. {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), rootFsUsed: "900Mi"},
  655. {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), logsFsUsed: "50Mi"},
  656. {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), rootFsUsed: "400Mi"},
  657. {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), perLocalVolumeUsed: "400Mi"},
  658. {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), rootFsUsed: "100Mi"},
  659. }
  660. pods := []*v1.Pod{}
  661. podStats := map[*v1.Pod]statsapi.PodStats{}
  662. for _, podToMake := range podsToMake {
  663. pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.rootFsUsed, podToMake.logsFsUsed, podToMake.perLocalVolumeUsed)
  664. pods = append(pods, pod)
  665. podStats[pod] = podStat
  666. }
  667. podToEvict := pods[0]
  668. activePodsFunc := func() []*v1.Pod {
  669. return pods
  670. }
  671. fakeClock := clock.NewFakeClock(time.Now())
  672. podKiller := &mockPodKiller{}
  673. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  674. nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  675. config := Config{
  676. MaxPodGracePeriodSeconds: 5,
  677. PressureTransitionPeriod: time.Minute * 5,
  678. Thresholds: []evictionapi.Threshold{
  679. {
  680. Signal: evictionapi.SignalNodeFsAvailable,
  681. Operator: evictionapi.OpLessThan,
  682. Value: evictionapi.ThresholdValue{
  683. Quantity: quantityMustParse("1Gi"),
  684. },
  685. MinReclaim: &evictionapi.ThresholdValue{
  686. Quantity: quantityMustParse("500Mi"),
  687. },
  688. },
  689. },
  690. }
  691. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("16Gi", "200Gi", podStats)}
  692. diskGC := &mockDiskGC{fakeSummaryProvider: summaryProvider, err: nil}
  693. manager := &managerImpl{
  694. clock: fakeClock,
  695. killPodFunc: podKiller.killPodNow,
  696. imageGC: diskGC,
  697. containerGC: diskGC,
  698. config: config,
  699. recorder: &record.FakeRecorder{},
  700. summaryProvider: summaryProvider,
  701. nodeRef: nodeRef,
  702. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  703. thresholdsFirstObservedAt: thresholdsObservedAt{},
  704. }
  705. // synchronize
  706. manager.synchronize(diskInfoProvider, activePodsFunc)
  707. // we should not have disk pressure
  708. if manager.IsUnderDiskPressure() {
  709. t.Errorf("Manager should not report disk pressure")
  710. }
  711. // induce hard threshold
  712. fakeClock.Step(1 * time.Minute)
  713. summaryProvider.result = summaryStatsMaker(".9Gi", "200Gi", podStats)
  714. // make GC successfully return disk usage to previous levels
  715. diskGC.summaryAfterGC = summaryStatsMaker("16Gi", "200Gi", podStats)
  716. manager.synchronize(diskInfoProvider, activePodsFunc)
  717. // we should have disk pressure
  718. if !manager.IsUnderDiskPressure() {
  719. t.Errorf("Manager should report disk pressure since soft threshold was met")
  720. }
  721. // verify image gc was invoked
  722. if !diskGC.imageGCInvoked || !diskGC.containerGCInvoked {
  723. t.Errorf("Manager should have invoked image gc")
  724. }
  725. // verify no pod was killed because image gc was sufficient
  726. if podKiller.pod != nil {
  727. t.Errorf("Manager should not have killed a pod, but killed: %v", podKiller.pod.Name)
  728. }
  729. // reset state
  730. diskGC.imageGCInvoked = false
  731. diskGC.containerGCInvoked = false
  732. // remove disk pressure
  733. fakeClock.Step(20 * time.Minute)
  734. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  735. manager.synchronize(diskInfoProvider, activePodsFunc)
  736. // we should not have disk pressure
  737. if manager.IsUnderDiskPressure() {
  738. t.Errorf("Manager should not report disk pressure")
  739. }
  740. // induce disk pressure!
  741. fakeClock.Step(1 * time.Minute)
  742. summaryProvider.result = summaryStatsMaker("400Mi", "200Gi", podStats)
  743. // Dont reclaim any disk
  744. diskGC.summaryAfterGC = summaryStatsMaker("400Mi", "200Gi", podStats)
  745. manager.synchronize(diskInfoProvider, activePodsFunc)
  746. // we should have disk pressure
  747. if !manager.IsUnderDiskPressure() {
  748. t.Errorf("Manager should report disk pressure")
  749. }
  750. // ensure disk gc was invoked
  751. if !diskGC.imageGCInvoked || !diskGC.containerGCInvoked {
  752. t.Errorf("Manager should have invoked image gc")
  753. }
  754. // check the right pod was killed
  755. if podKiller.pod != podToEvict {
  756. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  757. }
  758. observedGracePeriod := *podKiller.gracePeriodOverride
  759. if observedGracePeriod != int64(0) {
  760. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  761. }
  762. // reduce disk pressure
  763. fakeClock.Step(1 * time.Minute)
  764. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  765. diskGC.imageGCInvoked = false // reset state
  766. diskGC.containerGCInvoked = false // reset state
  767. podKiller.pod = nil // reset state
  768. manager.synchronize(diskInfoProvider, activePodsFunc)
  769. // we should have disk pressure (because transition period not yet met)
  770. if !manager.IsUnderDiskPressure() {
  771. t.Errorf("Manager should report disk pressure")
  772. }
  773. // no image gc should have occurred
  774. if diskGC.imageGCInvoked || diskGC.containerGCInvoked {
  775. t.Errorf("Manager chose to perform image gc when it was not neeed")
  776. }
  777. // no pod should have been killed
  778. if podKiller.pod != nil {
  779. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  780. }
  781. // move the clock past transition period to ensure that we stop reporting pressure
  782. fakeClock.Step(5 * time.Minute)
  783. summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
  784. diskGC.imageGCInvoked = false // reset state
  785. diskGC.containerGCInvoked = false // reset state
  786. podKiller.pod = nil // reset state
  787. manager.synchronize(diskInfoProvider, activePodsFunc)
  788. // we should not have disk pressure (because transition period met)
  789. if manager.IsUnderDiskPressure() {
  790. t.Errorf("Manager should not report disk pressure")
  791. }
  792. // no image gc should have occurred
  793. if diskGC.imageGCInvoked || diskGC.containerGCInvoked {
  794. t.Errorf("Manager chose to perform image gc when it was not neeed")
  795. }
  796. // no pod should have been killed
  797. if podKiller.pod != nil {
  798. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  799. }
  800. }
  801. func TestInodePressureNodeFsInodes(t *testing.T) {
  802. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
  803. podMaker := func(name string, priority int32, requests v1.ResourceList, limits v1.ResourceList, rootInodes, logInodes, volumeInodes string) (*v1.Pod, statsapi.PodStats) {
  804. pod := newPod(name, priority, []v1.Container{
  805. newContainer(name, requests, limits),
  806. }, nil)
  807. podStats := newPodInodeStats(pod, parseQuantity(rootInodes), parseQuantity(logInodes), parseQuantity(volumeInodes))
  808. return pod, podStats
  809. }
  810. summaryStatsMaker := func(rootFsInodesFree, rootFsInodes string, podStats map[*v1.Pod]statsapi.PodStats) *statsapi.Summary {
  811. rootFsInodesFreeVal := resource.MustParse(rootFsInodesFree)
  812. internalRootFsInodesFree := uint64(rootFsInodesFreeVal.Value())
  813. rootFsInodesVal := resource.MustParse(rootFsInodes)
  814. internalRootFsInodes := uint64(rootFsInodesVal.Value())
  815. result := &statsapi.Summary{
  816. Node: statsapi.NodeStats{
  817. Fs: &statsapi.FsStats{
  818. InodesFree: &internalRootFsInodesFree,
  819. Inodes: &internalRootFsInodes,
  820. },
  821. },
  822. Pods: []statsapi.PodStats{},
  823. }
  824. for _, podStat := range podStats {
  825. result.Pods = append(result.Pods, podStat)
  826. }
  827. return result
  828. }
  829. podsToMake := []podToMake{
  830. {name: "low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), rootFsInodesUsed: "900Mi"},
  831. {name: "below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), rootFsInodesUsed: "50Mi"},
  832. {name: "above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), rootFsInodesUsed: "400Mi"},
  833. {name: "high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), rootFsInodesUsed: "400Mi"},
  834. {name: "low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), rootFsInodesUsed: "100Mi"},
  835. }
  836. pods := []*v1.Pod{}
  837. podStats := map[*v1.Pod]statsapi.PodStats{}
  838. for _, podToMake := range podsToMake {
  839. pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.rootFsInodesUsed, podToMake.logsFsInodesUsed, podToMake.perLocalVolumeInodesUsed)
  840. pods = append(pods, pod)
  841. podStats[pod] = podStat
  842. }
  843. podToEvict := pods[0]
  844. activePodsFunc := func() []*v1.Pod {
  845. return pods
  846. }
  847. fakeClock := clock.NewFakeClock(time.Now())
  848. podKiller := &mockPodKiller{}
  849. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  850. diskGC := &mockDiskGC{err: nil}
  851. nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  852. config := Config{
  853. MaxPodGracePeriodSeconds: 5,
  854. PressureTransitionPeriod: time.Minute * 5,
  855. Thresholds: []evictionapi.Threshold{
  856. {
  857. Signal: evictionapi.SignalNodeFsInodesFree,
  858. Operator: evictionapi.OpLessThan,
  859. Value: evictionapi.ThresholdValue{
  860. Quantity: quantityMustParse("1Mi"),
  861. },
  862. },
  863. {
  864. Signal: evictionapi.SignalNodeFsInodesFree,
  865. Operator: evictionapi.OpLessThan,
  866. Value: evictionapi.ThresholdValue{
  867. Quantity: quantityMustParse("2Mi"),
  868. },
  869. GracePeriod: time.Minute * 2,
  870. },
  871. },
  872. }
  873. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("3Mi", "4Mi", podStats)}
  874. manager := &managerImpl{
  875. clock: fakeClock,
  876. killPodFunc: podKiller.killPodNow,
  877. imageGC: diskGC,
  878. containerGC: diskGC,
  879. config: config,
  880. recorder: &record.FakeRecorder{},
  881. summaryProvider: summaryProvider,
  882. nodeRef: nodeRef,
  883. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  884. thresholdsFirstObservedAt: thresholdsObservedAt{},
  885. }
  886. // create a best effort pod to test admission
  887. podToAdmit, _ := podMaker("pod-to-admit", defaultPriority, newResourceList("", "", ""), newResourceList("", "", ""), "0", "0", "0")
  888. // synchronize
  889. manager.synchronize(diskInfoProvider, activePodsFunc)
  890. // we should not have disk pressure
  891. if manager.IsUnderDiskPressure() {
  892. t.Errorf("Manager should not report inode pressure")
  893. }
  894. // try to admit our pod (should succeed)
  895. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit {
  896. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit)
  897. }
  898. // induce soft threshold
  899. fakeClock.Step(1 * time.Minute)
  900. summaryProvider.result = summaryStatsMaker("1.5Mi", "4Mi", podStats)
  901. manager.synchronize(diskInfoProvider, activePodsFunc)
  902. // we should have disk pressure
  903. if !manager.IsUnderDiskPressure() {
  904. t.Errorf("Manager should report inode pressure since soft threshold was met")
  905. }
  906. // verify no pod was yet killed because there has not yet been enough time passed.
  907. if podKiller.pod != nil {
  908. t.Errorf("Manager should not have killed a pod yet, but killed: %v", podKiller.pod.Name)
  909. }
  910. // step forward in time pass the grace period
  911. fakeClock.Step(3 * 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 inode pressure since soft threshold was met")
  917. }
  918. // verify the right pod was killed with the right grace period.
  919. if podKiller.pod != podToEvict {
  920. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  921. }
  922. if podKiller.gracePeriodOverride == nil {
  923. t.Errorf("Manager chose to kill pod but should have had a grace period override.")
  924. }
  925. observedGracePeriod := *podKiller.gracePeriodOverride
  926. if observedGracePeriod != manager.config.MaxPodGracePeriodSeconds {
  927. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", manager.config.MaxPodGracePeriodSeconds, observedGracePeriod)
  928. }
  929. // reset state
  930. podKiller.pod = nil
  931. podKiller.gracePeriodOverride = nil
  932. // remove inode pressure
  933. fakeClock.Step(20 * time.Minute)
  934. summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats)
  935. manager.synchronize(diskInfoProvider, activePodsFunc)
  936. // we should not have disk pressure
  937. if manager.IsUnderDiskPressure() {
  938. t.Errorf("Manager should not report inode pressure")
  939. }
  940. // induce inode pressure!
  941. fakeClock.Step(1 * time.Minute)
  942. summaryProvider.result = summaryStatsMaker("0.5Mi", "4Mi", podStats)
  943. manager.synchronize(diskInfoProvider, activePodsFunc)
  944. // we should have disk pressure
  945. if !manager.IsUnderDiskPressure() {
  946. t.Errorf("Manager should report inode pressure")
  947. }
  948. // check the right pod was killed
  949. if podKiller.pod != podToEvict {
  950. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  951. }
  952. observedGracePeriod = *podKiller.gracePeriodOverride
  953. if observedGracePeriod != int64(0) {
  954. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  955. }
  956. // try to admit our pod (should fail)
  957. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit {
  958. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit)
  959. }
  960. // reduce inode pressure
  961. fakeClock.Step(1 * time.Minute)
  962. summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats)
  963. podKiller.pod = nil // reset state
  964. manager.synchronize(diskInfoProvider, activePodsFunc)
  965. // we should have disk pressure (because transition period not yet met)
  966. if !manager.IsUnderDiskPressure() {
  967. t.Errorf("Manager should report inode pressure")
  968. }
  969. // no pod should have been killed
  970. if podKiller.pod != nil {
  971. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  972. }
  973. // try to admit our pod (should fail)
  974. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit {
  975. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit)
  976. }
  977. // move the clock past transition period to ensure that we stop reporting pressure
  978. fakeClock.Step(5 * time.Minute)
  979. summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats)
  980. podKiller.pod = nil // reset state
  981. manager.synchronize(diskInfoProvider, activePodsFunc)
  982. // we should not have disk pressure (because transition period met)
  983. if manager.IsUnderDiskPressure() {
  984. t.Errorf("Manager should not report inode pressure")
  985. }
  986. // no pod should have been killed
  987. if podKiller.pod != nil {
  988. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  989. }
  990. // try to admit our pod (should succeed)
  991. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit {
  992. t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit)
  993. }
  994. }
  995. // TestCriticalPodsAreNotEvicted
  996. func TestCriticalPodsAreNotEvicted(t *testing.T) {
  997. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
  998. podMaker := makePodWithMemoryStats
  999. summaryStatsMaker := makeMemoryStats
  1000. podsToMake := []podToMake{
  1001. {name: "critical", priority: defaultPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), memoryWorkingSet: "800Mi"},
  1002. }
  1003. pods := []*v1.Pod{}
  1004. podStats := map[*v1.Pod]statsapi.PodStats{}
  1005. for _, podToMake := range podsToMake {
  1006. pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.memoryWorkingSet)
  1007. pods = append(pods, pod)
  1008. podStats[pod] = podStat
  1009. }
  1010. // Mark the pod as critical
  1011. pods[0].Annotations = map[string]string{
  1012. kubelettypes.CriticalPodAnnotationKey: "",
  1013. kubelettypes.ConfigSourceAnnotationKey: kubelettypes.FileSource,
  1014. }
  1015. pods[0].Namespace = kubeapi.NamespaceSystem
  1016. podToEvict := pods[0]
  1017. activePodsFunc := func() []*v1.Pod {
  1018. return pods
  1019. }
  1020. mirrorPodFunc := func(staticPod *v1.Pod) (*v1.Pod, bool) {
  1021. mirrorPod := staticPod.DeepCopy()
  1022. mirrorPod.Annotations[kubelettypes.ConfigSourceAnnotationKey] = kubelettypes.ApiserverSource
  1023. return mirrorPod, true
  1024. }
  1025. fakeClock := clock.NewFakeClock(time.Now())
  1026. podKiller := &mockPodKiller{}
  1027. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  1028. diskGC := &mockDiskGC{err: nil}
  1029. nodeRef := &v1.ObjectReference{
  1030. Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: "",
  1031. }
  1032. config := Config{
  1033. MaxPodGracePeriodSeconds: 5,
  1034. PressureTransitionPeriod: time.Minute * 5,
  1035. Thresholds: []evictionapi.Threshold{
  1036. {
  1037. Signal: evictionapi.SignalMemoryAvailable,
  1038. Operator: evictionapi.OpLessThan,
  1039. Value: evictionapi.ThresholdValue{
  1040. Quantity: quantityMustParse("1Gi"),
  1041. },
  1042. },
  1043. {
  1044. Signal: evictionapi.SignalMemoryAvailable,
  1045. Operator: evictionapi.OpLessThan,
  1046. Value: evictionapi.ThresholdValue{
  1047. Quantity: quantityMustParse("2Gi"),
  1048. },
  1049. GracePeriod: time.Minute * 2,
  1050. },
  1051. },
  1052. }
  1053. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("2Gi", podStats)}
  1054. manager := &managerImpl{
  1055. clock: fakeClock,
  1056. killPodFunc: podKiller.killPodNow,
  1057. mirrorPodFunc: mirrorPodFunc,
  1058. imageGC: diskGC,
  1059. containerGC: diskGC,
  1060. config: config,
  1061. recorder: &record.FakeRecorder{},
  1062. summaryProvider: summaryProvider,
  1063. nodeRef: nodeRef,
  1064. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  1065. thresholdsFirstObservedAt: thresholdsObservedAt{},
  1066. }
  1067. // Enable critical pod annotation feature gate
  1068. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExperimentalCriticalPodAnnotation, true)()
  1069. // induce soft threshold
  1070. fakeClock.Step(1 * time.Minute)
  1071. summaryProvider.result = summaryStatsMaker("1500Mi", podStats)
  1072. manager.synchronize(diskInfoProvider, activePodsFunc)
  1073. // we should have memory pressure
  1074. if !manager.IsUnderMemoryPressure() {
  1075. t.Errorf("Manager should report memory pressure since soft threshold was met")
  1076. }
  1077. // verify no pod was yet killed because there has not yet been enough time passed.
  1078. if podKiller.pod != nil {
  1079. t.Errorf("Manager should not have killed a pod yet, but killed: %v", podKiller.pod.Name)
  1080. }
  1081. // step forward in time pass the grace period
  1082. fakeClock.Step(3 * time.Minute)
  1083. summaryProvider.result = summaryStatsMaker("1500Mi", podStats)
  1084. manager.synchronize(diskInfoProvider, activePodsFunc)
  1085. // we should have memory pressure
  1086. if !manager.IsUnderMemoryPressure() {
  1087. t.Errorf("Manager should report memory pressure since soft threshold was met")
  1088. }
  1089. // verify the right pod was killed with the right grace period.
  1090. if podKiller.pod == podToEvict {
  1091. t.Errorf("Manager chose to kill critical pod: %v, but should have ignored it", podKiller.pod.Name)
  1092. }
  1093. // reset state
  1094. podKiller.pod = nil
  1095. podKiller.gracePeriodOverride = nil
  1096. // remove memory pressure
  1097. fakeClock.Step(20 * time.Minute)
  1098. summaryProvider.result = summaryStatsMaker("3Gi", podStats)
  1099. manager.synchronize(diskInfoProvider, activePodsFunc)
  1100. // we should not have memory pressure
  1101. if manager.IsUnderMemoryPressure() {
  1102. t.Errorf("Manager should not report memory pressure")
  1103. }
  1104. // Disable critical pod annotation feature gate
  1105. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExperimentalCriticalPodAnnotation, false)()
  1106. // induce memory pressure!
  1107. fakeClock.Step(1 * time.Minute)
  1108. summaryProvider.result = summaryStatsMaker("500Mi", podStats)
  1109. manager.synchronize(diskInfoProvider, activePodsFunc)
  1110. // we should have memory pressure
  1111. if !manager.IsUnderMemoryPressure() {
  1112. t.Errorf("Manager should report memory pressure")
  1113. }
  1114. // check the right pod was killed
  1115. if podKiller.pod != podToEvict {
  1116. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  1117. }
  1118. }
  1119. // TestAllocatableMemoryPressure
  1120. func TestAllocatableMemoryPressure(t *testing.T) {
  1121. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
  1122. podMaker := makePodWithMemoryStats
  1123. summaryStatsMaker := makeMemoryStats
  1124. podsToMake := []podToMake{
  1125. {name: "guaranteed-low-priority-high-usage", priority: lowPriority, requests: newResourceList("100m", "1Gi", ""), limits: newResourceList("100m", "1Gi", ""), memoryWorkingSet: "900Mi"},
  1126. {name: "burstable-below-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "50Mi"},
  1127. {name: "burstable-above-requests", priority: defaultPriority, requests: newResourceList("100m", "100Mi", ""), limits: newResourceList("200m", "1Gi", ""), memoryWorkingSet: "400Mi"},
  1128. {name: "best-effort-high-priority-high-usage", priority: highPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "400Mi"},
  1129. {name: "best-effort-low-priority-low-usage", priority: lowPriority, requests: newResourceList("", "", ""), limits: newResourceList("", "", ""), memoryWorkingSet: "100Mi"},
  1130. }
  1131. pods := []*v1.Pod{}
  1132. podStats := map[*v1.Pod]statsapi.PodStats{}
  1133. for _, podToMake := range podsToMake {
  1134. pod, podStat := podMaker(podToMake.name, podToMake.priority, podToMake.requests, podToMake.limits, podToMake.memoryWorkingSet)
  1135. pods = append(pods, pod)
  1136. podStats[pod] = podStat
  1137. }
  1138. podToEvict := pods[4]
  1139. activePodsFunc := func() []*v1.Pod {
  1140. return pods
  1141. }
  1142. fakeClock := clock.NewFakeClock(time.Now())
  1143. podKiller := &mockPodKiller{}
  1144. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  1145. diskGC := &mockDiskGC{err: nil}
  1146. nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  1147. config := Config{
  1148. MaxPodGracePeriodSeconds: 5,
  1149. PressureTransitionPeriod: time.Minute * 5,
  1150. Thresholds: []evictionapi.Threshold{
  1151. {
  1152. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  1153. Operator: evictionapi.OpLessThan,
  1154. Value: evictionapi.ThresholdValue{
  1155. Quantity: quantityMustParse("1Gi"),
  1156. },
  1157. },
  1158. },
  1159. }
  1160. summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("4Gi", podStats)}
  1161. manager := &managerImpl{
  1162. clock: fakeClock,
  1163. killPodFunc: podKiller.killPodNow,
  1164. imageGC: diskGC,
  1165. containerGC: diskGC,
  1166. config: config,
  1167. recorder: &record.FakeRecorder{},
  1168. summaryProvider: summaryProvider,
  1169. nodeRef: nodeRef,
  1170. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  1171. thresholdsFirstObservedAt: thresholdsObservedAt{},
  1172. }
  1173. // create a best effort pod to test admission
  1174. bestEffortPodToAdmit, _ := podMaker("best-admit", defaultPriority, newResourceList("", "", ""), newResourceList("", "", ""), "0Gi")
  1175. burstablePodToAdmit, _ := podMaker("burst-admit", defaultPriority, newResourceList("100m", "100Mi", ""), newResourceList("200m", "200Mi", ""), "0Gi")
  1176. // synchronize
  1177. manager.synchronize(diskInfoProvider, activePodsFunc)
  1178. // we should not have memory pressure
  1179. if manager.IsUnderMemoryPressure() {
  1180. t.Errorf("Manager should not report memory pressure")
  1181. }
  1182. // try to admit our pods (they should succeed)
  1183. expected := []bool{true, true}
  1184. for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  1185. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  1186. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  1187. }
  1188. }
  1189. // induce memory pressure!
  1190. fakeClock.Step(1 * time.Minute)
  1191. pod, podStat := podMaker("guaranteed-high-2", defaultPriority, newResourceList("100m", "1Gi", ""), newResourceList("100m", "1Gi", ""), "1Gi")
  1192. podStats[pod] = podStat
  1193. summaryProvider.result = summaryStatsMaker("500Mi", podStats)
  1194. manager.synchronize(diskInfoProvider, activePodsFunc)
  1195. // we should have memory pressure
  1196. if !manager.IsUnderMemoryPressure() {
  1197. t.Errorf("Manager should report memory pressure")
  1198. }
  1199. // check the right pod was killed
  1200. if podKiller.pod != podToEvict {
  1201. t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name)
  1202. }
  1203. observedGracePeriod := *podKiller.gracePeriodOverride
  1204. if observedGracePeriod != int64(0) {
  1205. t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod)
  1206. }
  1207. // reset state
  1208. podKiller.pod = nil
  1209. podKiller.gracePeriodOverride = nil
  1210. // the best-effort pod should not admit, burstable should
  1211. expected = []bool{false, true}
  1212. for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  1213. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  1214. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  1215. }
  1216. }
  1217. // reduce memory pressure
  1218. fakeClock.Step(1 * time.Minute)
  1219. for pod := range podStats {
  1220. if pod.Name == "guaranteed-high-2" {
  1221. delete(podStats, pod)
  1222. }
  1223. }
  1224. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  1225. podKiller.pod = nil // reset state
  1226. manager.synchronize(diskInfoProvider, activePodsFunc)
  1227. // we should have memory pressure (because transition period not yet met)
  1228. if !manager.IsUnderMemoryPressure() {
  1229. t.Errorf("Manager should report memory pressure")
  1230. }
  1231. // no pod should have been killed
  1232. if podKiller.pod != nil {
  1233. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  1234. }
  1235. // the best-effort pod should not admit, burstable should
  1236. expected = []bool{false, true}
  1237. for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  1238. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  1239. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  1240. }
  1241. }
  1242. // move the clock past transition period to ensure that we stop reporting pressure
  1243. fakeClock.Step(5 * time.Minute)
  1244. summaryProvider.result = summaryStatsMaker("2Gi", podStats)
  1245. podKiller.pod = nil // reset state
  1246. manager.synchronize(diskInfoProvider, activePodsFunc)
  1247. // we should not have memory pressure (because transition period met)
  1248. if manager.IsUnderMemoryPressure() {
  1249. t.Errorf("Manager should not report memory pressure")
  1250. }
  1251. // no pod should have been killed
  1252. if podKiller.pod != nil {
  1253. t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name)
  1254. }
  1255. // all pods should admit now
  1256. expected = []bool{true, true}
  1257. for i, pod := range []*v1.Pod{bestEffortPodToAdmit, burstablePodToAdmit} {
  1258. if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: pod}); expected[i] != result.Admit {
  1259. t.Errorf("Admit pod: %v, expected: %v, actual: %v", pod, expected[i], result.Admit)
  1260. }
  1261. }
  1262. }
  1263. func TestUpdateMemcgThreshold(t *testing.T) {
  1264. activePodsFunc := func() []*v1.Pod {
  1265. return []*v1.Pod{}
  1266. }
  1267. fakeClock := clock.NewFakeClock(time.Now())
  1268. podKiller := &mockPodKiller{}
  1269. diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
  1270. diskGC := &mockDiskGC{err: nil}
  1271. nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
  1272. config := Config{
  1273. MaxPodGracePeriodSeconds: 5,
  1274. PressureTransitionPeriod: time.Minute * 5,
  1275. Thresholds: []evictionapi.Threshold{
  1276. {
  1277. Signal: evictionapi.SignalMemoryAvailable,
  1278. Operator: evictionapi.OpLessThan,
  1279. Value: evictionapi.ThresholdValue{
  1280. Quantity: quantityMustParse("1Gi"),
  1281. },
  1282. },
  1283. },
  1284. PodCgroupRoot: "kubepods",
  1285. }
  1286. summaryProvider := &fakeSummaryProvider{result: makeMemoryStats("2Gi", map[*v1.Pod]statsapi.PodStats{})}
  1287. thresholdNotifier := &MockThresholdNotifier{}
  1288. thresholdNotifier.On("UpdateThreshold", summaryProvider.result).Return(nil).Twice()
  1289. manager := &managerImpl{
  1290. clock: fakeClock,
  1291. killPodFunc: podKiller.killPodNow,
  1292. imageGC: diskGC,
  1293. containerGC: diskGC,
  1294. config: config,
  1295. recorder: &record.FakeRecorder{},
  1296. summaryProvider: summaryProvider,
  1297. nodeRef: nodeRef,
  1298. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  1299. thresholdsFirstObservedAt: thresholdsObservedAt{},
  1300. thresholdNotifiers: []ThresholdNotifier{thresholdNotifier},
  1301. }
  1302. manager.synchronize(diskInfoProvider, activePodsFunc)
  1303. // The UpdateThreshold method should have been called once, since this is the first run.
  1304. thresholdNotifier.AssertNumberOfCalls(t, "UpdateThreshold", 1)
  1305. manager.synchronize(diskInfoProvider, activePodsFunc)
  1306. // The UpdateThreshold method should not have been called again, since not enough time has passed
  1307. thresholdNotifier.AssertNumberOfCalls(t, "UpdateThreshold", 1)
  1308. fakeClock.Step(2 * notifierRefreshInterval)
  1309. manager.synchronize(diskInfoProvider, activePodsFunc)
  1310. // The UpdateThreshold method should be called again since enough time has passed
  1311. thresholdNotifier.AssertNumberOfCalls(t, "UpdateThreshold", 2)
  1312. // new memory threshold notifier that returns an error
  1313. thresholdNotifier = &MockThresholdNotifier{}
  1314. thresholdNotifier.On("UpdateThreshold", summaryProvider.result).Return(fmt.Errorf("error updating threshold"))
  1315. thresholdNotifier.On("Description").Return("mock thresholdNotifier").Once()
  1316. manager.thresholdNotifiers = []ThresholdNotifier{thresholdNotifier}
  1317. fakeClock.Step(2 * notifierRefreshInterval)
  1318. manager.synchronize(diskInfoProvider, activePodsFunc)
  1319. // The UpdateThreshold method should be called because at least notifierRefreshInterval time has passed.
  1320. // The Description method should be called because UpdateThreshold returned an error
  1321. thresholdNotifier.AssertNumberOfCalls(t, "UpdateThreshold", 1)
  1322. thresholdNotifier.AssertNumberOfCalls(t, "Description", 1)
  1323. }