Skip to content

Commit

Permalink
cmd cni-metrics-helper: add tests for prometheus and cw paths
Browse files Browse the repository at this point in the history
  • Loading branch information
dshehbaj committed Dec 19, 2024
1 parent 8183388 commit 5394d02
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 7 deletions.
260 changes: 259 additions & 1 deletion cmd/cni-metrics-helper/metrics/cni_metrics_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package metrics

import (
"fmt"
"testing"

"github.com/golang/mock/gomock"
Expand All @@ -16,6 +17,12 @@ import (
eniconfigscheme "github.com/aws/amazon-vpc-cni-k8s/pkg/apis/crd/v1alpha1"
"github.com/aws/amazon-vpc-cni-k8s/pkg/publisher/mock_publisher"
"github.com/aws/amazon-vpc-cni-k8s/pkg/utils/logger"
"github.com/aws/amazon-vpc-cni-k8s/utils/prometheusmetrics"
"github.com/aws/aws-sdk-go-v2/aws"
cloudwatchtypes "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types"

"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
)

var logConfig = logger.Configuration{
Expand Down Expand Up @@ -49,11 +56,262 @@ func TestCNIMetricsNew(t *testing.T) {
m := setup(t)
ctx := context.Background()
_, _ = m.clientset.CoreV1().Pods("kube-system").Create(ctx, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "aws-node-1"}}, metav1.CreateOptions{})
//cniMetric := CNIMetricsNew(m.clientset, m.mockPublisher, m.discoverController, false, log)
// cniMetric := CNIMetricsNew(m.clientset, m.mockPublisher, m.discoverController, false, log)
cniMetric := CNIMetricsNew(m.clientset, m.mockPublisher, false, false, testLog, m.podWatcher)
assert.NotNil(t, cniMetric)
assert.NotNil(t, cniMetric.getCWMetricsPublisher())
assert.NotEmpty(t, cniMetric.getInterestingMetrics())
assert.Equal(t, testLog, cniMetric.getLogger())
assert.False(t, cniMetric.submitCloudWatch())
}

func TestProduceCloudWatchMetrics(t *testing.T) {
m := setup(t)

// Create a test metrics target
cniMetric := CNIMetricsNew(m.clientset, m.mockPublisher, true, false, testLog, m.podWatcher)

// Setup test metric families with multiple metrics
families := map[string]*dto.MetricFamily{
"awscni_eni_max": {
Name: aws.String("awscni_eni_max"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(10.0)},
}},
},
"awscni_ip_max": {
Name: aws.String("awscni_ip_max"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(20.0)},
}},
},
"awscni_eni_allocated": {
Name: aws.String("awscni_eni_allocated"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(3.0)},
}},
},
"awscni_total_ip_addresses": {
Name: aws.String("awscni_total_ip_addresses"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(30.0)},
}},
},
"awscni_assigned_ip_addresses": {
Name: aws.String("awscni_assigned_ip_addresses"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(15.0)},
}},
},
}

// Create test conversion definitions
testConvertDef := map[string]metricsConvert{
"awscni_eni_max": {
actions: []metricsAction{{
cwMetricName: "eni_max",
data: &dataPoints{curSingleDataPoint: 10.0},
}},
},
"awscni_ip_max": {
actions: []metricsAction{{
cwMetricName: "ip_max",
data: &dataPoints{curSingleDataPoint: 20.0},
}},
},
"awscni_eni_allocated": {
actions: []metricsAction{{
cwMetricName: "eni_allocated",
data: &dataPoints{curSingleDataPoint: 3.0},
}},
},
"awscni_total_ip_addresses": {
actions: []metricsAction{{
cwMetricName: "total_ip_addresses",
data: &dataPoints{curSingleDataPoint: 30.0},
}},
},
"awscni_assigned_ip_addresses": {
actions: []metricsAction{{
cwMetricName: "assigned_ip_addresses",
data: &dataPoints{curSingleDataPoint: 15.0},
}},
},
}

// Set up expected CloudWatch metric datums
expectedMetrics := []cloudwatchtypes.MetricDatum{
{
MetricName: aws.String("eni_max"),
Unit: cloudwatchtypes.StandardUnitCount,
Value: aws.Float64(10.0),
},
{
MetricName: aws.String("ip_max"),
Unit: cloudwatchtypes.StandardUnitCount,
Value: aws.Float64(20.0),
},
{
MetricName: aws.String("eni_allocated"),
Unit: cloudwatchtypes.StandardUnitCount,
Value: aws.Float64(3.0),
},
{
MetricName: aws.String("total_ip_addresses"),
Unit: cloudwatchtypes.StandardUnitCount,
Value: aws.Float64(30.0),
},
{
MetricName: aws.String("assigned_ip_addresses"),
Unit: cloudwatchtypes.StandardUnitCount,
Value: aws.Float64(15.0),
},
}

// Expect CloudWatch publish to be called for each metric
for _, expectedMetric := range expectedMetrics {
m.mockPublisher.EXPECT().Publish(expectedMetric).Times(1)
}

// Test CloudWatch metrics production
err := produceCloudWatchMetrics(cniMetric, families, testConvertDef, m.mockPublisher)
assert.NoError(t, err)
}

func TestProducePrometheusMetrics(t *testing.T) {
// Reset the registry before test
prometheus.DefaultRegisterer = prometheus.NewRegistry()

m := setup(t)

// Create a test metrics target with Prometheus enabled
cniMetric := CNIMetricsNew(m.clientset, m.mockPublisher, false, true, testLog, m.podWatcher)

// Setup test metric families with only supported metrics
families := map[string]*dto.MetricFamily{
"awscni_eni_max": {
Name: aws.String("awscni_eni_max"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(10.0)},
}},
},
"awscni_ip_max": {
Name: aws.String("awscni_ip_max"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(20.0)},
}},
},
"awscni_eni_allocated": {
Name: aws.String("awscni_eni_allocated"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(3.0)},
}},
},
"awscni_total_ip_addresses": {
Name: aws.String("awscni_total_ip_addresses"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(30.0)},
}},
},
"awscni_assigned_ip_addresses": {
Name: aws.String("awscni_assigned_ip_addresses"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{{
Gauge: &dto.Gauge{Value: aws.Float64(15.0)},
}},
},
}

// Create test conversion definitions
testConvertDef := map[string]metricsConvert{
"awscni_eni_max": {
actions: []metricsAction{{
data: &dataPoints{curSingleDataPoint: 10.0},
}},
},
"awscni_ip_max": {
actions: []metricsAction{{
data: &dataPoints{curSingleDataPoint: 20.0},
}},
},
"awscni_eni_allocated": {
actions: []metricsAction{{
data: &dataPoints{curSingleDataPoint: 3.0},
}},
},
"awscni_total_ip_addresses": {
actions: []metricsAction{{
data: &dataPoints{curSingleDataPoint: 30.0},
}},
},
"awscni_assigned_ip_addresses": {
actions: []metricsAction{{
data: &dataPoints{curSingleDataPoint: 15.0},
}},
},
}

// Register Prometheus metrics
prometheusmetrics.PrometheusRegister()

// Initialize test metrics with initial values and verify registration
metrics := prometheusmetrics.GetSupportedPrometheusCNIMetricsMapping()
t.Logf("Number of registered metrics: %d", len(metrics))
for name, metric := range metrics {
t.Logf("Found metric: %s", name)
if gauge, ok := metric.(prometheus.Gauge); ok {
gauge.Set(0) // Set initial value to 0
var m dto.Metric
err := gauge.Write(&m)
assert.NoError(t, err)
t.Logf("Initial value for %s: %f", name, *m.Gauge.Value)
}
}

// Test Prometheus metrics production
err := producePrometheusMetrics(cniMetric, families, testConvertDef)
assert.NoError(t, err)

// Log the families and their names for debugging
for key, family := range families {
t.Logf("Family key: %s, Name: %s", key, family.GetName())
}

// Verify all metrics were set correctly in Prometheus registry
metrics = prometheusmetrics.GetSupportedPrometheusCNIMetricsMapping()
t.Logf("Number of metrics after production: %d", len(metrics))

// Test cases for supported metrics
testCases := []struct {
metricName string
expected float64
}{
{"awscni_eni_max", 10.0},
{"awscni_ip_max", 20.0},
{"awscni_eni_allocated", 3.0},
{"awscni_total_ip_addresses", 30.0},
{"awscni_assigned_ip_addresses", 15.0},
}

// Verify each gauge metric
for _, tc := range testCases {
gauge, ok := metrics[tc.metricName].(prometheus.Gauge)
assert.True(t, ok, fmt.Sprintf("Metric %s should be registered as a Gauge", tc.metricName))

var metric dto.Metric
err = gauge.Write(&metric)
assert.NoError(t, err)
t.Logf("Final value for %s: %f (expected %f)", tc.metricName, *metric.Gauge.Value, tc.expected)
assert.Equal(t, tc.expected, *metric.Gauge.Value,
fmt.Sprintf("Metric %s value should be set to %f", tc.metricName, tc.expected))
}
}
17 changes: 11 additions & 6 deletions cmd/cni-metrics-helper/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,19 +303,19 @@ func produceHistogram(act metricsAction, cw publisher.Publisher) {
}

func filterMetrics(originalMetrics map[string]*dto.MetricFamily,
interestingMetrics map[string]metricsConvert) (map[string]*dto.MetricFamily, error) {
interestingMetrics map[string]metricsConvert,
) (map[string]*dto.MetricFamily, error) {
result := map[string]*dto.MetricFamily{}

for metric := range interestingMetrics {
if family, found := originalMetrics[metric]; found {
result[metric] = family

}
}
return result, nil
}

func produceCloudWatchMetrics(t metricsTarget, families map[string]*dto.MetricFamily, convertDef map[string]metricsConvert, cw publisher.Publisher) {
func produceCloudWatchMetrics(t metricsTarget, families map[string]*dto.MetricFamily, convertDef map[string]metricsConvert, cw publisher.Publisher) error {
for key, family := range families {
convertMetrics := convertDef[key]
metricType := family.GetType()
Expand Down Expand Up @@ -347,15 +347,18 @@ func produceCloudWatchMetrics(t metricsTarget, families map[string]*dto.MetricFa
}
}
}

return nil
}

// Prometheus export supports only gauge metrics for now.

func producePrometheusMetrics(t metricsTarget, families map[string]*dto.MetricFamily, convertDef map[string]metricsConvert) {
func producePrometheusMetrics(t metricsTarget, families map[string]*dto.MetricFamily, convertDef map[string]metricsConvert) error {
prometheusCNIMetrics := prometheusmetrics.GetSupportedPrometheusCNIMetricsMapping()
if len(prometheusCNIMetrics) == 0 {
t.getLogger().Infof("Skipping since prometheus mapping is missing")
return
error_msg := "Skipping since prometheus mapping is missing"
t.getLogger().Infof(error_msg)
return fmt.Errorf(error_msg)
}
for key, family := range families {
convertMetrics := convertDef[key]
Expand All @@ -374,6 +377,8 @@ func producePrometheusMetrics(t metricsTarget, families map[string]*dto.MetricFa
}
}
}

return nil
}

func resetMetrics(interestingMetrics map[string]metricsConvert) {
Expand Down

0 comments on commit 5394d02

Please sign in to comment.