autoscaler/vertical-pod-autoscaler/pkg/utils/metrics/updater/updater_test.go

298 lines
7.7 KiB
Go

/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package updater
import (
"testing"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
)
func TestAddEvictedPod(t *testing.T) {
testCases := []struct {
desc string
vpaSize int
mode vpa_types.UpdateMode
log2 string
}{
{
desc: "VPA size 5, mode Auto",
vpaSize: 5,
mode: vpa_types.UpdateModeAuto,
log2: "2",
},
{
desc: "VPA size 10, mode Off",
vpaSize: 10,
mode: vpa_types.UpdateModeOff,
log2: "3",
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
t.Cleanup(evictedCount.Reset)
AddEvictedPod(tc.vpaSize, tc.mode)
val := testutil.ToFloat64(evictedCount.WithLabelValues(tc.log2, string(tc.mode)))
if val != 1 {
t.Errorf("Unexpected value for evictedCount metric with labels (%s, %s): got %v, want 1", tc.log2, string(tc.mode), val)
}
})
}
}
func TestAddInPlaceUpdatedPod(t *testing.T) {
testCases := []struct {
desc string
vpaSize int
log2 string
}{
{
desc: "VPA size 10",
vpaSize: 10,
log2: "3",
},
{
desc: "VPA size 1",
vpaSize: 1,
log2: "0",
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
t.Cleanup(inPlaceUpdatedCount.Reset)
AddInPlaceUpdatedPod(tc.vpaSize)
val := testutil.ToFloat64(inPlaceUpdatedCount.WithLabelValues(tc.log2))
if val != 1 {
t.Errorf("Unexpected value for InPlaceUpdatedPod metric with labels (%s): got %v, want 1", tc.log2, val)
}
})
}
}
func TestRecordFailedInPlaceUpdate(t *testing.T) {
testCases := []struct {
desc string
vpaSize int
reason string
log2 string
}{
{
desc: "VPA size 2, some reason",
vpaSize: 2,
reason: "some_reason",
log2: "1",
},
{
desc: "VPA size 20, another reason",
vpaSize: 20,
reason: "another_reason",
log2: "4",
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
t.Cleanup(failedInPlaceUpdateAttempts.Reset)
RecordFailedInPlaceUpdate(tc.vpaSize, tc.reason)
val := testutil.ToFloat64(failedInPlaceUpdateAttempts.WithLabelValues(tc.log2, tc.reason))
if val != 1 {
t.Errorf("Unexpected value for FailedInPlaceUpdate metric with labels (%s, %s): got %v, want 1", tc.log2, tc.reason, val)
}
})
}
}
func TestUpdateModeAndSizeBasedGauge(t *testing.T) {
type addition struct {
vpaSize int
mode vpa_types.UpdateMode
value int
}
type expectation struct {
labels []string
value float64
}
testCases := []struct {
desc string
newCounter func() *UpdateModeAndSizeBasedGauge
metric *prometheus.GaugeVec
metricName string
additions []addition
expectedMetrics []expectation
}{
{
desc: "ControlledPodsCounter",
newCounter: NewControlledPodsCounter,
metric: controlledCount,
metricName: "vpa_updater_controlled_pods_total",
additions: []addition{
{1, vpa_types.UpdateModeAuto, 5},
{2, vpa_types.UpdateModeOff, 10},
{2, vpa_types.UpdateModeAuto, 2},
{2, vpa_types.UpdateModeAuto, 7},
},
expectedMetrics: []expectation{
{[]string{"0" /* log2(1) */, "Auto"}, 5},
{[]string{"1" /* log2(2) */, "Auto"}, 9},
{[]string{"1" /* log2(2) */, "Off"}, 10},
},
},
{
desc: "EvictablePodsCounter",
newCounter: NewEvictablePodsCounter,
metric: evictableCount,
metricName: "vpa_updater_evictable_pods_total",
additions: []addition{
{4, vpa_types.UpdateModeAuto, 3},
{1, vpa_types.UpdateModeRecreate, 8},
},
expectedMetrics: []expectation{
{[]string{"2" /* log2(4) */, "Auto"}, 3},
{[]string{"0" /* log2(1) */, "Recreate"}, 8},
},
},
{
desc: "VpasWithEvictablePodsCounter",
newCounter: NewVpasWithEvictablePodsCounter,
metric: vpasWithEvictablePodsCount,
metricName: "vpa_updater_vpas_with_evictable_pods_total",
additions: []addition{
{1, vpa_types.UpdateModeOff, 1},
{2, vpa_types.UpdateModeAuto, 1},
},
expectedMetrics: []expectation{
{[]string{"0" /* log2(1) */, "Off"}, 1},
{[]string{"1" /* log2(2) */, "Auto"}, 1},
},
},
{
desc: "VpasWithEvictedPodsCounter",
newCounter: NewVpasWithEvictedPodsCounter,
metric: vpasWithEvictedPodsCount,
metricName: "vpa_updater_vpas_with_evicted_pods_total",
additions: []addition{
{1, vpa_types.UpdateModeAuto, 2},
{1, vpa_types.UpdateModeAuto, 3},
},
expectedMetrics: []expectation{
{[]string{"0" /* log2(1) */, "Auto"}, 5},
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
t.Cleanup(tc.metric.Reset)
counter := tc.newCounter()
for _, add := range tc.additions {
counter.Add(add.vpaSize, add.mode, add.value)
}
counter.Observe()
for _, expected := range tc.expectedMetrics {
val := testutil.ToFloat64(tc.metric.WithLabelValues(expected.labels...))
if val != expected.value {
t.Errorf("Unexpected value for metric %s with labels %v: got %v, want %v", tc.metricName, expected.labels, val, expected.value)
}
}
})
}
}
func TestSizeBasedGauge(t *testing.T) {
type addition struct {
vpaSize int
value int
}
type expectation struct {
labels []string
value float64
}
testCases := []struct {
desc string
newCounter func() *SizeBasedGauge
metric *prometheus.GaugeVec
metricName string
additions []addition
expectedMetrics []expectation
}{
{
desc: "InPlaceUpdatablePodsCounter",
newCounter: NewInPlaceUpdatablePodsCounter,
metric: inPlaceUpdatableCount,
metricName: "vpa_updater_in_place_updatable_pods_total",
additions: []addition{
{1, 5},
{2, 10},
},
expectedMetrics: []expectation{
{[]string{"0" /* log2(1) */}, 5},
{[]string{"1" /* log2(2) */}, 10},
},
},
{
desc: "VpasWithInPlaceUpdatablePodsCounter",
newCounter: NewVpasWithInPlaceUpdatablePodsCounter,
metric: vpasWithInPlaceUpdatablePodsCount,
metricName: "vpa_updater_vpas_with_in_place_updatable_pods_total",
additions: []addition{
{10, 1},
{20, 1},
},
expectedMetrics: []expectation{
{[]string{"3" /* log2(10) */}, 1},
{[]string{"4" /* log2(20) */}, 1},
},
},
{
desc: "VpasWithInPlaceUpdatedPodsCounter",
newCounter: NewVpasWithInPlaceUpdatedPodsCounter,
metric: vpasWithInPlaceUpdatedPodsCount,
metricName: "vpa_updater_vpas_with_in_place_updated_pods_total",
additions: []addition{
{2, 4},
{4, 5},
},
expectedMetrics: []expectation{
{[]string{"1" /* log2(2) */}, 4},
{[]string{"2" /* log2(4) */}, 5},
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
t.Cleanup(tc.metric.Reset)
counter := tc.newCounter()
for _, add := range tc.additions {
counter.Add(add.vpaSize, add.value)
}
counter.Observe()
for _, expected := range tc.expectedMetrics {
val := testutil.ToFloat64(tc.metric.WithLabelValues(expected.labels...))
if val != expected.value {
t.Errorf("Unexpected value for metric %s with labels %v: got %v, want %v", tc.metricName, expected.labels, val, expected.value)
}
}
})
}
}