kubelet/eviction: improve unit test coverage

Signed-off-by: TommyStarK <thomasmilox@gmail.com>
This commit is contained in:
TommyStarK 2024-08-29 00:13:42 +02:00
parent 4aca09bc0c
commit 2414cfdff5
No known key found for this signature in database
GPG Key ID: 9D2DFCECABB40F9E
2 changed files with 236 additions and 6 deletions

View File

@ -26,6 +26,7 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -46,7 +47,10 @@ func quantityMustParse(value string) *resource.Quantity {
func TestGetReclaimableThreshold(t *testing.T) {
testCases := map[string]struct {
thresholds []evictionapi.Threshold
thresholds []evictionapi.Threshold
expectedThreshold evictionapi.Threshold
expectedResourceName v1.ResourceName
expectedReclaimableToBeFound bool
}{
"": {
thresholds: []evictionapi.Threshold{
@ -101,14 +105,40 @@ func TestGetReclaimableThreshold(t *testing.T) {
},
},
},
expectedThreshold: evictionapi.Threshold{
Signal: evictionapi.Signal("memory.available"),
Operator: evictionapi.OpLessThan,
Value: evictionapi.ThresholdValue{
Quantity: quantityMustParse("150Mi"),
},
},
expectedResourceName: v1.ResourceName("memory"),
expectedReclaimableToBeFound: true,
},
"no thresholds": {
thresholds: []evictionapi.Threshold{},
expectedThreshold: evictionapi.Threshold{},
expectedResourceName: v1.ResourceName(""),
},
"threshold was crossed but reclaim not implemented (invalid signal)": {
thresholds: []evictionapi.Threshold{
{
Signal: "mem.available",
},
},
expectedThreshold: evictionapi.Threshold{},
expectedResourceName: v1.ResourceName(""),
},
}
for testName, testCase := range testCases {
for _, testCase := range testCases {
sort.Sort(byEvictionPriority(testCase.thresholds))
_, _, ok := getReclaimableThreshold(testCase.thresholds)
if !ok {
t.Errorf("Didn't find reclaimable threshold, test: %v", testName)
}
threshold, ressourceName, found := getReclaimableThreshold(testCase.thresholds)
assert.Equal(t, testCase.expectedReclaimableToBeFound, found)
assert.Equal(t, testCase.expectedResourceName, ressourceName)
assert.Equal(t, testCase.expectedThreshold.Signal, threshold.Signal)
assert.Equal(t, testCase.expectedThreshold.Operator, threshold.Operator)
assert.Equal(t, testCase.expectedThreshold.Value.Quantity.String(), threshold.Value.Quantity.String())
assert.Equal(t, testCase.expectedThreshold.GracePeriod, threshold.GracePeriod)
}
}
@ -455,6 +485,78 @@ func TestParseThresholdConfig(t *testing.T) {
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
"min-reclaim-invalid-signal": {
allocatableConfig: []string{},
evictionHard: map[string]string{},
evictionSoft: map[string]string{},
evictionSoftGracePeriod: map[string]string{},
evictionMinReclaim: map[string]string{"mem.available": "300Mi"},
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
"min-reclaim-empty-value": {
allocatableConfig: []string{},
evictionHard: map[string]string{},
evictionSoft: map[string]string{},
evictionSoftGracePeriod: map[string]string{},
evictionMinReclaim: map[string]string{"memory.available": ""},
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
"min-reclaim-negative-percentage": {
allocatableConfig: []string{},
evictionHard: map[string]string{},
evictionSoft: map[string]string{},
evictionSoftGracePeriod: map[string]string{},
evictionMinReclaim: map[string]string{"memory.available": "-15%"},
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
"min-reclaim-invalid-percentage": {
allocatableConfig: []string{},
evictionHard: map[string]string{},
evictionSoft: map[string]string{},
evictionSoftGracePeriod: map[string]string{},
evictionMinReclaim: map[string]string{"memory.available": "10..5%"},
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
"hard-signal-empty-eviction-value": {
allocatableConfig: []string{},
evictionHard: map[string]string{"memory.available": ""},
evictionSoft: map[string]string{},
evictionSoftGracePeriod: map[string]string{},
evictionMinReclaim: map[string]string{},
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
"hard-signal-invalid-float-percentage": {
allocatableConfig: []string{},
evictionHard: map[string]string{"memory.available": "10..5%"},
evictionSoft: map[string]string{},
evictionSoftGracePeriod: map[string]string{},
evictionMinReclaim: map[string]string{},
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
"soft-grace-period-invalid-signal": {
allocatableConfig: []string{},
evictionHard: map[string]string{},
evictionSoft: map[string]string{"memory.available": "150Mi"},
evictionSoftGracePeriod: map[string]string{"mem.available": "30s"},
evictionMinReclaim: map[string]string{},
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
"soft-invalid-grace-period": {
allocatableConfig: []string{},
evictionHard: map[string]string{},
evictionSoft: map[string]string{"memory.available": "150Mi"},
evictionSoftGracePeriod: map[string]string{"memory.available": "30mins"},
evictionMinReclaim: map[string]string{},
expectErr: true,
expectThresholds: []evictionapi.Threshold{},
},
}
for testName, testCase := range testCases {
thresholds, err := ParseThresholdConfig(testCase.allocatableConfig, testCase.evictionHard, testCase.evictionSoft, testCase.evictionSoftGracePeriod, testCase.evictionMinReclaim)
@ -648,6 +750,47 @@ func TestAddAllocatableThresholds(t *testing.T) {
}
}
func TestFallbackResourcesUsage(t *testing.T) {
for _, test := range []struct {
description string
usageFuncName string
usageFunc func() int64
}{
{
description: "disk usage, fallback value",
usageFuncName: "diskUsage",
usageFunc: func() int64 {
return diskUsage(&statsapi.FsStats{}).Value()
},
},
{
description: "inode usage, fallback value",
usageFuncName: "inodeUsage",
usageFunc: func() int64 {
return inodeUsage(&statsapi.FsStats{}).Value()
},
},
{
description: "memory usage, fallback value",
usageFuncName: "memoryUsage",
usageFunc: func() int64 {
return memoryUsage(&statsapi.MemoryStats{}).Value()
},
},
{
description: "process usage, fallback value",
usageFuncName: "processUsage",
usageFunc: func() int64 {
return int64(processUsage(&statsapi.ProcessStats{}))
},
},
} {
t.Run(test.description, func(t *testing.T) {
assert.NotEqual(t, 0, test.usageFunc(), fmt.Sprintf("%s: unexpected fallback value", test.usageFuncName))
})
}
}
func thresholdsEqual(expected []evictionapi.Threshold, actual []evictionapi.Threshold) bool {
if len(expected) != len(actual) {
return false
@ -3259,3 +3402,49 @@ func TestEvictonMessageWithResourceResize(t *testing.T) {
})
}
}
func TestStatsNotFoundForPod(t *testing.T) {
pod1 := newPod("fake-pod1", defaultPriority, []v1.Container{
newContainer("fake-container1", newResourceList("", "", ""), newResourceList("", "", "")),
}, nil)
pod2 := newPod("fake-pod2", defaultPriority, []v1.Container{
newContainer("fake-container2", newResourceList("", "", ""), newResourceList("", "", "")),
}, nil)
statsFn := func(pod *v1.Pod) (statsapi.PodStats, bool) {
return statsapi.PodStats{}, false
}
for _, test := range []struct {
description string
compFunc func(stats statsFunc) cmpFunc
}{
{
description: "process",
compFunc: process,
},
{
description: "memory",
compFunc: memory,
},
{
description: "exceedMemoryRequests",
compFunc: exceedMemoryRequests,
},
{
description: "exceedDiskRequests",
compFunc: func(stats statsFunc) cmpFunc {
return exceedDiskRequests(stats, nil, "")
},
},
{
description: "disk",
compFunc: func(stats statsFunc) cmpFunc {
return disk(stats, nil, "")
},
},
} {
t.Run(test.description, func(t *testing.T) {
assert.Equal(t, 0, test.compFunc(statsFn)(pod1, pod2), "unexpected default result")
})
}
}

View File

@ -26,6 +26,7 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/resource"
statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
@ -151,6 +152,46 @@ func TestUpdateThreshold(t *testing.T) {
}
}
func TestUpdateThresholdWithInvalidSummary(t *testing.T) {
testCases := []struct {
description string
summary *statsapi.Summary
allocatableEvictionThreshold bool
}{
{
description: "incomplete summary",
summary: &statsapi.Summary{
Node: statsapi.NodeStats{
Memory: &statsapi.MemoryStats{},
},
},
},
{
description: "system container not found in metrics",
allocatableEvictionThreshold: true,
summary: &statsapi.Summary{
Node: statsapi.NodeStats{
SystemContainers: []statsapi.ContainerStats{
{
Name: "invalid",
},
},
Memory: &statsapi.MemoryStats{},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
m := newTestMemoryThresholdNotifier(evictionapi.Threshold{}, nil, nil)
if tc.allocatableEvictionThreshold {
m.threshold.Signal = evictionapi.SignalAllocatableMemoryAvailable
}
assert.Error(t, m.UpdateThreshold(tc.summary))
})
}
}
func TestStart(t *testing.T) {
noResources := resource.MustParse("0")
threshold := evictionapi.Threshold{