memory_threshold_notifier_test.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. Copyright 2018 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. "strings"
  17. "sync"
  18. "testing"
  19. "time"
  20. "k8s.io/apimachinery/pkg/api/resource"
  21. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  22. evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
  23. )
  24. const testCgroupPath = "/sys/fs/cgroups/memory"
  25. func nodeSummary(available, workingSet, usage resource.Quantity, allocatable bool) *statsapi.Summary {
  26. availableBytes := uint64(available.Value())
  27. workingSetBytes := uint64(workingSet.Value())
  28. usageBytes := uint64(usage.Value())
  29. memoryStats := statsapi.MemoryStats{
  30. AvailableBytes: &availableBytes,
  31. WorkingSetBytes: &workingSetBytes,
  32. UsageBytes: &usageBytes,
  33. }
  34. if allocatable {
  35. return &statsapi.Summary{
  36. Node: statsapi.NodeStats{
  37. SystemContainers: []statsapi.ContainerStats{
  38. {
  39. Name: statsapi.SystemContainerPods,
  40. Memory: &memoryStats,
  41. },
  42. },
  43. },
  44. }
  45. }
  46. return &statsapi.Summary{
  47. Node: statsapi.NodeStats{
  48. Memory: &memoryStats,
  49. },
  50. }
  51. }
  52. func newTestMemoryThresholdNotifier(threshold evictionapi.Threshold, factory NotifierFactory, handler func(string)) *memoryThresholdNotifier {
  53. return &memoryThresholdNotifier{
  54. threshold: threshold,
  55. cgroupPath: testCgroupPath,
  56. events: make(chan struct{}),
  57. factory: factory,
  58. handler: handler,
  59. }
  60. }
  61. func TestUpdateThreshold(t *testing.T) {
  62. testCases := []struct {
  63. description string
  64. available resource.Quantity
  65. workingSet resource.Quantity
  66. usage resource.Quantity
  67. evictionThreshold evictionapi.Threshold
  68. expectedThreshold resource.Quantity
  69. updateThresholdErr error
  70. expectErr bool
  71. }{
  72. {
  73. description: "node level threshold",
  74. available: resource.MustParse("3Gi"),
  75. usage: resource.MustParse("2Gi"),
  76. workingSet: resource.MustParse("1Gi"),
  77. evictionThreshold: evictionapi.Threshold{
  78. Signal: evictionapi.SignalMemoryAvailable,
  79. Operator: evictionapi.OpLessThan,
  80. Value: evictionapi.ThresholdValue{
  81. Quantity: quantityMustParse("1Gi"),
  82. },
  83. },
  84. expectedThreshold: resource.MustParse("4Gi"),
  85. updateThresholdErr: nil,
  86. expectErr: false,
  87. },
  88. {
  89. description: "allocatable threshold",
  90. available: resource.MustParse("4Gi"),
  91. usage: resource.MustParse("3Gi"),
  92. workingSet: resource.MustParse("1Gi"),
  93. evictionThreshold: evictionapi.Threshold{
  94. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  95. Operator: evictionapi.OpLessThan,
  96. Value: evictionapi.ThresholdValue{
  97. Quantity: quantityMustParse("1Gi"),
  98. },
  99. },
  100. expectedThreshold: resource.MustParse("6Gi"),
  101. updateThresholdErr: nil,
  102. expectErr: false,
  103. },
  104. {
  105. description: "error updating node level threshold",
  106. available: resource.MustParse("3Gi"),
  107. usage: resource.MustParse("2Gi"),
  108. workingSet: resource.MustParse("1Gi"),
  109. evictionThreshold: evictionapi.Threshold{
  110. Signal: evictionapi.SignalMemoryAvailable,
  111. Operator: evictionapi.OpLessThan,
  112. Value: evictionapi.ThresholdValue{
  113. Quantity: quantityMustParse("1Gi"),
  114. },
  115. },
  116. expectedThreshold: resource.MustParse("4Gi"),
  117. updateThresholdErr: fmt.Errorf("unexpected error"),
  118. expectErr: true,
  119. },
  120. }
  121. for _, tc := range testCases {
  122. t.Run(tc.description, func(t *testing.T) {
  123. notifierFactory := &MockNotifierFactory{}
  124. notifier := &MockCgroupNotifier{}
  125. m := newTestMemoryThresholdNotifier(tc.evictionThreshold, notifierFactory, nil)
  126. notifierFactory.On("NewCgroupNotifier", testCgroupPath, memoryUsageAttribute, tc.expectedThreshold.Value()).Return(notifier, tc.updateThresholdErr)
  127. var events chan<- struct{}
  128. events = m.events
  129. notifier.On("Start", events).Return()
  130. err := m.UpdateThreshold(nodeSummary(tc.available, tc.workingSet, tc.usage, isAllocatableEvictionThreshold(tc.evictionThreshold)))
  131. if err != nil && !tc.expectErr {
  132. t.Errorf("Unexpected error updating threshold: %v", err)
  133. } else if err == nil && tc.expectErr {
  134. t.Errorf("Expected error updating threshold, but got nil")
  135. }
  136. if !tc.expectErr {
  137. notifierFactory.AssertNumberOfCalls(t, "NewCgroupNotifier", 1)
  138. }
  139. })
  140. }
  141. }
  142. func TestStart(t *testing.T) {
  143. noResources := resource.MustParse("0")
  144. threshold := evictionapi.Threshold{
  145. Signal: evictionapi.SignalMemoryAvailable,
  146. Operator: evictionapi.OpLessThan,
  147. Value: evictionapi.ThresholdValue{
  148. Quantity: &noResources,
  149. },
  150. }
  151. notifier := &MockCgroupNotifier{}
  152. notifierFactory := &MockNotifierFactory{}
  153. var wg sync.WaitGroup
  154. wg.Add(4)
  155. m := newTestMemoryThresholdNotifier(threshold, notifierFactory, func(string) {
  156. wg.Done()
  157. })
  158. notifierFactory.On("NewCgroupNotifier", testCgroupPath, memoryUsageAttribute, int64(0)).Return(notifier, nil)
  159. var events chan<- struct{}
  160. events = m.events
  161. notifier.On("Start", events).Return()
  162. notifier.On("Stop").Return()
  163. err := m.UpdateThreshold(nodeSummary(noResources, noResources, noResources, isAllocatableEvictionThreshold(threshold)))
  164. if err != nil {
  165. t.Errorf("Unexpected error updating threshold: %v", err)
  166. }
  167. notifierFactory.AssertNumberOfCalls(t, "NewCgroupNotifier", 1)
  168. go m.Start()
  169. for i := 0; i < 4; i++ {
  170. m.events <- struct{}{}
  171. }
  172. wg.Wait()
  173. }
  174. func TestThresholdDescription(t *testing.T) {
  175. testCases := []struct {
  176. description string
  177. evictionThreshold evictionapi.Threshold
  178. expectedSubstrings []string
  179. omittedSubstrings []string
  180. }{
  181. {
  182. description: "hard node level threshold",
  183. evictionThreshold: evictionapi.Threshold{
  184. Signal: evictionapi.SignalMemoryAvailable,
  185. Operator: evictionapi.OpLessThan,
  186. Value: evictionapi.ThresholdValue{
  187. Quantity: quantityMustParse("2Gi"),
  188. },
  189. },
  190. expectedSubstrings: []string{"hard"},
  191. omittedSubstrings: []string{"allocatable", "soft"},
  192. },
  193. {
  194. description: "soft node level threshold",
  195. evictionThreshold: evictionapi.Threshold{
  196. Signal: evictionapi.SignalMemoryAvailable,
  197. Operator: evictionapi.OpLessThan,
  198. Value: evictionapi.ThresholdValue{
  199. Quantity: quantityMustParse("2Gi"),
  200. },
  201. GracePeriod: time.Minute * 2,
  202. },
  203. expectedSubstrings: []string{"soft"},
  204. omittedSubstrings: []string{"allocatable", "hard"},
  205. },
  206. {
  207. description: "hard allocatable threshold",
  208. evictionThreshold: evictionapi.Threshold{
  209. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  210. Operator: evictionapi.OpLessThan,
  211. Value: evictionapi.ThresholdValue{
  212. Quantity: quantityMustParse("2Gi"),
  213. },
  214. GracePeriod: time.Minute * 2,
  215. },
  216. expectedSubstrings: []string{"soft", "allocatable"},
  217. omittedSubstrings: []string{"hard"},
  218. },
  219. {
  220. description: "soft allocatable threshold",
  221. evictionThreshold: evictionapi.Threshold{
  222. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  223. Operator: evictionapi.OpLessThan,
  224. Value: evictionapi.ThresholdValue{
  225. Quantity: quantityMustParse("2Gi"),
  226. },
  227. },
  228. expectedSubstrings: []string{"hard", "allocatable"},
  229. omittedSubstrings: []string{"soft"},
  230. },
  231. }
  232. for _, tc := range testCases {
  233. t.Run(tc.description, func(t *testing.T) {
  234. m := &memoryThresholdNotifier{
  235. notifier: &MockCgroupNotifier{},
  236. threshold: tc.evictionThreshold,
  237. cgroupPath: testCgroupPath,
  238. }
  239. desc := m.Description()
  240. for _, expected := range tc.expectedSubstrings {
  241. if !strings.Contains(desc, expected) {
  242. t.Errorf("expected description for notifier with threshold %+v to contain %s, but it did not", tc.evictionThreshold, expected)
  243. }
  244. }
  245. for _, omitted := range tc.omittedSubstrings {
  246. if strings.Contains(desc, omitted) {
  247. t.Errorf("expected description for notifier with threshold %+v NOT to contain %s, but it did", tc.evictionThreshold, omitted)
  248. }
  249. }
  250. })
  251. }
  252. }