k8s.io/kubernetes@v1.29.3/pkg/kubelet/eviction/memory_threshold_notifier_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package eviction 18 19 import ( 20 "fmt" 21 "strings" 22 "sync" 23 "testing" 24 "time" 25 26 gomock "github.com/golang/mock/gomock" 27 "k8s.io/apimachinery/pkg/api/resource" 28 statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 29 evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api" 30 ) 31 32 const testCgroupPath = "/sys/fs/cgroups/memory" 33 34 func nodeSummary(available, workingSet, usage resource.Quantity, allocatable bool) *statsapi.Summary { 35 availableBytes := uint64(available.Value()) 36 workingSetBytes := uint64(workingSet.Value()) 37 usageBytes := uint64(usage.Value()) 38 memoryStats := statsapi.MemoryStats{ 39 AvailableBytes: &availableBytes, 40 WorkingSetBytes: &workingSetBytes, 41 UsageBytes: &usageBytes, 42 } 43 if allocatable { 44 return &statsapi.Summary{ 45 Node: statsapi.NodeStats{ 46 SystemContainers: []statsapi.ContainerStats{ 47 { 48 Name: statsapi.SystemContainerPods, 49 Memory: &memoryStats, 50 }, 51 }, 52 }, 53 } 54 } 55 return &statsapi.Summary{ 56 Node: statsapi.NodeStats{ 57 Memory: &memoryStats, 58 }, 59 } 60 } 61 62 func newTestMemoryThresholdNotifier(threshold evictionapi.Threshold, factory NotifierFactory, handler func(string)) *memoryThresholdNotifier { 63 return &memoryThresholdNotifier{ 64 threshold: threshold, 65 cgroupPath: testCgroupPath, 66 events: make(chan struct{}), 67 factory: factory, 68 handler: handler, 69 } 70 } 71 72 func TestUpdateThreshold(t *testing.T) { 73 testCases := []struct { 74 description string 75 available resource.Quantity 76 workingSet resource.Quantity 77 usage resource.Quantity 78 evictionThreshold evictionapi.Threshold 79 expectedThreshold resource.Quantity 80 updateThresholdErr error 81 expectErr bool 82 }{ 83 { 84 description: "node level threshold", 85 available: resource.MustParse("3Gi"), 86 usage: resource.MustParse("2Gi"), 87 workingSet: resource.MustParse("1Gi"), 88 evictionThreshold: evictionapi.Threshold{ 89 Signal: evictionapi.SignalMemoryAvailable, 90 Operator: evictionapi.OpLessThan, 91 Value: evictionapi.ThresholdValue{ 92 Quantity: quantityMustParse("1Gi"), 93 }, 94 }, 95 expectedThreshold: resource.MustParse("4Gi"), 96 updateThresholdErr: nil, 97 expectErr: false, 98 }, 99 { 100 description: "allocatable threshold", 101 available: resource.MustParse("4Gi"), 102 usage: resource.MustParse("3Gi"), 103 workingSet: resource.MustParse("1Gi"), 104 evictionThreshold: evictionapi.Threshold{ 105 Signal: evictionapi.SignalAllocatableMemoryAvailable, 106 Operator: evictionapi.OpLessThan, 107 Value: evictionapi.ThresholdValue{ 108 Quantity: quantityMustParse("1Gi"), 109 }, 110 }, 111 expectedThreshold: resource.MustParse("6Gi"), 112 updateThresholdErr: nil, 113 expectErr: false, 114 }, 115 { 116 description: "error updating node level threshold", 117 available: resource.MustParse("3Gi"), 118 usage: resource.MustParse("2Gi"), 119 workingSet: resource.MustParse("1Gi"), 120 evictionThreshold: evictionapi.Threshold{ 121 Signal: evictionapi.SignalMemoryAvailable, 122 Operator: evictionapi.OpLessThan, 123 Value: evictionapi.ThresholdValue{ 124 Quantity: quantityMustParse("1Gi"), 125 }, 126 }, 127 expectedThreshold: resource.MustParse("4Gi"), 128 updateThresholdErr: fmt.Errorf("unexpected error"), 129 expectErr: true, 130 }, 131 } 132 133 mockCtrl := gomock.NewController(t) 134 defer mockCtrl.Finish() 135 136 for _, tc := range testCases { 137 t.Run(tc.description, func(t *testing.T) { 138 notifierFactory := NewMockNotifierFactory(mockCtrl) 139 notifier := NewMockCgroupNotifier(mockCtrl) 140 141 m := newTestMemoryThresholdNotifier(tc.evictionThreshold, notifierFactory, nil) 142 notifierFactory.EXPECT().NewCgroupNotifier(testCgroupPath, memoryUsageAttribute, tc.expectedThreshold.Value()).Return(notifier, tc.updateThresholdErr).Times(1) 143 var events chan<- struct{} = m.events 144 notifier.EXPECT().Start(events).Return().AnyTimes() 145 err := m.UpdateThreshold(nodeSummary(tc.available, tc.workingSet, tc.usage, isAllocatableEvictionThreshold(tc.evictionThreshold))) 146 if err != nil && !tc.expectErr { 147 t.Errorf("Unexpected error updating threshold: %v", err) 148 } else if err == nil && tc.expectErr { 149 t.Errorf("Expected error updating threshold, but got nil") 150 } 151 }) 152 } 153 } 154 155 func TestStart(t *testing.T) { 156 noResources := resource.MustParse("0") 157 threshold := evictionapi.Threshold{ 158 Signal: evictionapi.SignalMemoryAvailable, 159 Operator: evictionapi.OpLessThan, 160 Value: evictionapi.ThresholdValue{ 161 Quantity: &noResources, 162 }, 163 } 164 mockCtrl := gomock.NewController(t) 165 defer mockCtrl.Finish() 166 notifierFactory := NewMockNotifierFactory(mockCtrl) 167 notifier := NewMockCgroupNotifier(mockCtrl) 168 169 var wg sync.WaitGroup 170 wg.Add(4) 171 m := newTestMemoryThresholdNotifier(threshold, notifierFactory, func(string) { 172 wg.Done() 173 }) 174 notifierFactory.EXPECT().NewCgroupNotifier(testCgroupPath, memoryUsageAttribute, int64(0)).Return(notifier, nil).Times(1) 175 176 var events chan<- struct{} = m.events 177 notifier.EXPECT().Start(events).DoAndReturn(func(events chan<- struct{}) { 178 for i := 0; i < 4; i++ { 179 events <- struct{}{} 180 } 181 }) 182 183 err := m.UpdateThreshold(nodeSummary(noResources, noResources, noResources, isAllocatableEvictionThreshold(threshold))) 184 if err != nil { 185 t.Errorf("Unexpected error updating threshold: %v", err) 186 } 187 188 go m.Start() 189 190 wg.Wait() 191 } 192 193 func TestThresholdDescription(t *testing.T) { 194 testCases := []struct { 195 description string 196 evictionThreshold evictionapi.Threshold 197 expectedSubstrings []string 198 omittedSubstrings []string 199 }{ 200 { 201 description: "hard node level threshold", 202 evictionThreshold: evictionapi.Threshold{ 203 Signal: evictionapi.SignalMemoryAvailable, 204 Operator: evictionapi.OpLessThan, 205 Value: evictionapi.ThresholdValue{ 206 Quantity: quantityMustParse("2Gi"), 207 }, 208 }, 209 expectedSubstrings: []string{"hard"}, 210 omittedSubstrings: []string{"allocatable", "soft"}, 211 }, 212 { 213 description: "soft node level threshold", 214 evictionThreshold: evictionapi.Threshold{ 215 Signal: evictionapi.SignalMemoryAvailable, 216 Operator: evictionapi.OpLessThan, 217 Value: evictionapi.ThresholdValue{ 218 Quantity: quantityMustParse("2Gi"), 219 }, 220 GracePeriod: time.Minute * 2, 221 }, 222 expectedSubstrings: []string{"soft"}, 223 omittedSubstrings: []string{"allocatable", "hard"}, 224 }, 225 { 226 description: "hard allocatable threshold", 227 evictionThreshold: evictionapi.Threshold{ 228 Signal: evictionapi.SignalAllocatableMemoryAvailable, 229 Operator: evictionapi.OpLessThan, 230 Value: evictionapi.ThresholdValue{ 231 Quantity: quantityMustParse("2Gi"), 232 }, 233 GracePeriod: time.Minute * 2, 234 }, 235 expectedSubstrings: []string{"soft", "allocatable"}, 236 omittedSubstrings: []string{"hard"}, 237 }, 238 { 239 description: "soft allocatable threshold", 240 evictionThreshold: evictionapi.Threshold{ 241 Signal: evictionapi.SignalAllocatableMemoryAvailable, 242 Operator: evictionapi.OpLessThan, 243 Value: evictionapi.ThresholdValue{ 244 Quantity: quantityMustParse("2Gi"), 245 }, 246 }, 247 expectedSubstrings: []string{"hard", "allocatable"}, 248 omittedSubstrings: []string{"soft"}, 249 }, 250 } 251 252 for _, tc := range testCases { 253 t.Run(tc.description, func(t *testing.T) { 254 m := &memoryThresholdNotifier{ 255 notifier: &MockCgroupNotifier{}, 256 threshold: tc.evictionThreshold, 257 cgroupPath: testCgroupPath, 258 } 259 desc := m.Description() 260 for _, expected := range tc.expectedSubstrings { 261 if !strings.Contains(desc, expected) { 262 t.Errorf("expected description for notifier with threshold %+v to contain %s, but it did not", tc.evictionThreshold, expected) 263 } 264 } 265 for _, omitted := range tc.omittedSubstrings { 266 if strings.Contains(desc, omitted) { 267 t.Errorf("expected description for notifier with threshold %+v NOT to contain %s, but it did", tc.evictionThreshold, omitted) 268 } 269 } 270 }) 271 } 272 }