Files
node-problem-detector/pkg/systemstatsmonitor/net_collector_test.go
2025-09-11 18:47:20 +00:00

187 lines
7.0 KiB
Go

package systemstatsmonitor
import (
"os"
"path"
"regexp"
"testing"
ssmtypes "k8s.io/node-problem-detector/pkg/systemstatsmonitor/types"
"k8s.io/node-problem-detector/pkg/util/metrics"
)
var defaultMetricsConfig = map[string]ssmtypes.MetricConfig{
string(metrics.NetDevRxBytes): {DisplayName: string(metrics.NetDevRxBytes)},
string(metrics.NetDevRxPackets): {DisplayName: string(metrics.NetDevRxPackets)},
string(metrics.NetDevRxErrors): {DisplayName: string(metrics.NetDevRxErrors)},
string(metrics.NetDevRxDropped): {DisplayName: string(metrics.NetDevRxDropped)},
string(metrics.NetDevRxFifo): {DisplayName: string(metrics.NetDevRxFifo)},
string(metrics.NetDevRxFrame): {DisplayName: string(metrics.NetDevRxFrame)},
string(metrics.NetDevRxCompressed): {DisplayName: string(metrics.NetDevRxCompressed)},
string(metrics.NetDevRxMulticast): {DisplayName: string(metrics.NetDevRxMulticast)},
string(metrics.NetDevTxBytes): {DisplayName: string(metrics.NetDevTxBytes)},
string(metrics.NetDevTxPackets): {DisplayName: string(metrics.NetDevTxPackets)},
string(metrics.NetDevTxErrors): {DisplayName: string(metrics.NetDevTxErrors)},
string(metrics.NetDevTxDropped): {DisplayName: string(metrics.NetDevTxDropped)},
string(metrics.NetDevTxFifo): {DisplayName: string(metrics.NetDevTxFifo)},
string(metrics.NetDevTxCollisions): {DisplayName: string(metrics.NetDevTxCollisions)},
string(metrics.NetDevTxCarrier): {DisplayName: string(metrics.NetDevTxCarrier)},
string(metrics.NetDevTxCompressed): {DisplayName: string(metrics.NetDevTxCompressed)},
}
// To get a similar output, run `cat /proc/net/dev` on a Linux machine
// docker: 1500 100 8 7 0 0 0 0 9000 450 565 200 20 30 0 0
const fakeNetProcContent = `Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
eth0: 5000 100 0 0 0 0 0 0 2500 30 0 0 0 0 0 0
docker0: 1000 90 8 7 0 0 0 0 0 0 0 0 0 0 0 0
docker1: 500 10 0 0 0 0 0 0 3000 150 15 0 20 30 0 0
docker2: 0 0 0 0 0 0 0 0 6000 300 550 200 0 0 0 0
`
// newFakeInt64Metric is a wrapper around metrics.NewFakeInt64Metric
func newFakeInt64Metric(metricID metrics.MetricID, viewName string, description string, unit string, aggregation metrics.Aggregation, tagNames []string) (metrics.Int64MetricInterface, error) {
return metrics.NewFakeInt64Metric(viewName, aggregation, tagNames), nil
}
// testCollectAux is a test auxiliary function used for testing netCollector.Collect
func testCollectAux(t *testing.T, name string, excludeInterfaceRegexp ssmtypes.NetStatsInterfaceRegexp, validate func(*testing.T, *netCollector)) {
// mkdir /tmp/proc-X
procDir := t.TempDir()
// mkdir -C /tmp/proc-X/net
procNetDir := path.Join(procDir, "net")
if err := os.Mkdir(procNetDir, 0o777); err != nil {
t.Fatalf("Failed to create directory %q: %v", procNetDir, err)
}
// touch /tmp/proc-X/net/dev
filename := path.Join(procNetDir, "dev")
f, err := os.Create(filename)
if err != nil {
t.Fatalf("Failed to create file %q: %v", filename, err)
}
// echo $FILE_CONTENT > /tmp/proc-X/net/dev
if _, err = f.WriteString(fakeNetProcContent); err != nil {
t.Fatalf("Failed to write to file %q: %v", filename, err)
}
if err = f.Close(); err != nil {
t.Fatalf("Failed to close file %q: %v", filename, err)
}
// Build the netCollector
config := &ssmtypes.NetStatsConfig{
ExcludeInterfaceRegexp: excludeInterfaceRegexp,
MetricsConfigs: defaultMetricsConfig,
}
netCollector := &netCollector{
config: config,
procPath: procDir,
recorder: newIfaceStatRecorder(newFakeInt64Metric),
}
netCollector.initOrDie()
netCollector.collect()
validate(t, netCollector)
}
func TestCollect(t *testing.T) {
t.Parallel()
tcs := []struct {
Name string
ExcludeInterfaceRegexp ssmtypes.NetStatsInterfaceRegexp
Validate func(t *testing.T, nc *netCollector)
}{
{
Name: "NoFilterMatch",
ExcludeInterfaceRegexp: ssmtypes.NetStatsInterfaceRegexp{R: regexp.MustCompile(`^fake$`)},
Validate: func(t *testing.T, nc *netCollector) {
// We just validate two metrics, no need to check all of them
expectedValues := map[metrics.MetricID]map[string]int64{
metrics.NetDevRxBytes: {
"eth0": 5000,
"docker0": 1000,
"docker1": 500,
"docker2": 0,
},
metrics.NetDevTxBytes: {
"eth0": 2500,
"docker0": 0,
"docker1": 3000,
"docker2": 6000,
},
}
for metricID, interfaceValues := range expectedValues {
collector, ok := nc.recorder.collectors[metricID]
if !ok {
t.Errorf("Failed to get collector of metric %s", metricID)
continue
}
fakeInt64Metric, ok := collector.metric.(*metrics.FakeInt64Metric)
if !ok {
t.Fatalf("Failed to convert metric %s to fakeMetric", string(metricID))
}
for _, repr := range fakeInt64Metric.ListMetrics() {
interfaceName, ok := repr.Labels[interfaceNameLabel]
if !ok {
t.Fatalf("Failed to get label %q for ", interfaceNameLabel)
}
expectedValue, ok := interfaceValues[interfaceName]
if !ok {
t.Fatalf("Failed to get metric value for interface %q", interfaceName)
}
if repr.Value != expectedValue {
t.Errorf("Mismatch in metric %q for interface %q: expected %d, got %d", metricID, interfaceName, expectedValue, repr.Value)
}
}
}
},
},
{
Name: "FilterMatch",
ExcludeInterfaceRegexp: ssmtypes.NetStatsInterfaceRegexp{R: regexp.MustCompile(`docker\d+`)},
Validate: func(t *testing.T, nc *netCollector) {
// We just validate two metrics, no need to check all of them
expectedValues := map[metrics.MetricID]map[string]int64{
metrics.NetDevRxBytes: {
"eth0": 5000,
},
metrics.NetDevTxBytes: {
"eth0": 2500,
},
}
for metricID, interfaceValues := range expectedValues {
collector, ok := nc.recorder.collectors[metricID]
if !ok {
t.Errorf("Failed to get collector of metric %s", metricID)
continue
}
fakeInt64Metric, ok := collector.metric.(*metrics.FakeInt64Metric)
if !ok {
t.Fatalf("Failed to convert metric %s to fakeMetric", string(metricID))
}
for _, repr := range fakeInt64Metric.ListMetrics() {
interfaceName, ok := repr.Labels[interfaceNameLabel]
if !ok {
t.Fatalf("Failed to get label %q for ", interfaceNameLabel)
}
expectedValue, ok := interfaceValues[interfaceName]
if !ok {
t.Fatalf("Failed to get metric value for interface %q", interfaceName)
}
if repr.Value != expectedValue {
t.Errorf("Mismatch in metric %q for interface %q: expected %d, got %d", metricID, interfaceName, expectedValue, repr.Value)
}
}
}
},
},
}
for _, tc := range tcs {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
testCollectAux(t, tc.Name, tc.ExcludeInterfaceRegexp, tc.Validate)
})
}
}