2023-04-25 20:05:47 +02:00
|
|
|
package collector
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2023-05-08 16:16:23 +02:00
|
|
|
"regexp"
|
2023-04-25 20:05:47 +02:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
2023-05-05 12:05:09 +02:00
|
|
|
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
2023-04-25 20:05:47 +02:00
|
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/metrics/pkg/apis/external_metrics"
|
|
|
|
)
|
|
|
|
|
2023-05-23 15:40:56 +02:00
|
|
|
func TestExternalRPSCollectorPluginConstructor(tt *testing.T) {
|
2023-04-25 20:05:47 +02:00
|
|
|
for _, testcase := range []struct {
|
|
|
|
msg string
|
|
|
|
name string
|
|
|
|
isValid bool
|
|
|
|
}{
|
|
|
|
{"No metric name", "", false},
|
|
|
|
{"Valid metric name", "a_valid_metric_name", true},
|
|
|
|
} {
|
|
|
|
tt.Run(testcase.msg, func(t *testing.T) {
|
|
|
|
|
|
|
|
fakePlugin := &FakeCollectorPlugin{}
|
2023-05-23 15:40:56 +02:00
|
|
|
plugin, err := NewExternalRPSCollectorPlugin(fakePlugin, testcase.name)
|
2023-04-25 20:05:47 +02:00
|
|
|
|
|
|
|
if testcase.isValid {
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, plugin)
|
|
|
|
require.Equal(t, testcase.name, plugin.metricName)
|
|
|
|
require.Equal(t, fakePlugin, plugin.promPlugin)
|
|
|
|
} else {
|
|
|
|
require.NotNil(t, err)
|
|
|
|
require.Nil(t, plugin)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-23 15:40:56 +02:00
|
|
|
func TestExternalRPSPluginNewCollector(tt *testing.T) {
|
2023-04-25 20:05:47 +02:00
|
|
|
fakePlugin := &FakeCollectorPlugin{}
|
|
|
|
|
2023-05-08 16:16:23 +02:00
|
|
|
pattern, err := regexp.Compile("^[a-zA-Z0-9.-]+$")
|
|
|
|
require.Nil(tt, err, "Something is up, regex compiling failed.")
|
|
|
|
|
2023-05-23 15:40:56 +02:00
|
|
|
plugin := &ExternalRPSCollectorPlugin{
|
2023-04-25 20:05:47 +02:00
|
|
|
metricName: "a_valid_one",
|
|
|
|
promPlugin: fakePlugin,
|
2023-05-08 16:16:23 +02:00
|
|
|
pattern: pattern,
|
2023-04-25 20:05:47 +02:00
|
|
|
}
|
|
|
|
interval := time.Duration(42)
|
|
|
|
|
|
|
|
for _, testcase := range []struct {
|
2023-05-03 18:13:02 +02:00
|
|
|
msg string
|
|
|
|
config *MetricConfig
|
|
|
|
expectedQuery string
|
|
|
|
shouldWork bool
|
2023-04-25 20:05:47 +02:00
|
|
|
}{
|
2023-05-03 18:13:02 +02:00
|
|
|
{
|
|
|
|
"No hostname config",
|
|
|
|
&MetricConfig{Config: make(map[string]string)},
|
|
|
|
"",
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Nil metric config",
|
|
|
|
nil,
|
|
|
|
"",
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Valid hostname no prom query config",
|
2023-05-03 20:49:14 +02:00
|
|
|
&MetricConfig{Config: map[string]string{"hostnames": "foo.bar.baz"}},
|
|
|
|
`scalar(sum(rate(a_valid_one{host=~"foo_bar_baz"}[1m])) * 1.0000)`,
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Valid hostname no prom query config",
|
|
|
|
&MetricConfig{Config: map[string]string{"hostnames": "foo.bar.baz", "weight": "42"}},
|
|
|
|
`scalar(sum(rate(a_valid_one{host=~"foo_bar_baz"}[1m])) * 0.4200)`,
|
2023-05-03 18:13:02 +02:00
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Multiple valid hostnames no prom query config",
|
2023-05-03 20:49:14 +02:00
|
|
|
&MetricConfig{Config: map[string]string{"hostnames": "foo.bar.baz,foz.bax.bas"}},
|
|
|
|
`scalar(sum(rate(a_valid_one{host=~"foo_bar_baz|foz_bax_bas"}[1m])) * 1.0000)`,
|
2023-05-03 18:13:02 +02:00
|
|
|
true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Valid hostname with prom query config",
|
|
|
|
&MetricConfig{
|
2023-05-03 20:49:14 +02:00
|
|
|
Config: map[string]string{"hostnames": "foo.bar.baz", "query": "some_other_query"},
|
2023-05-03 18:13:02 +02:00
|
|
|
},
|
2023-05-03 20:49:14 +02:00
|
|
|
`scalar(sum(rate(a_valid_one{host=~"foo_bar_baz"}[1m])) * 1.0000)`,
|
2023-05-03 18:13:02 +02:00
|
|
|
true,
|
|
|
|
},
|
2023-04-25 20:05:47 +02:00
|
|
|
} {
|
|
|
|
tt.Run(testcase.msg, func(t *testing.T) {
|
|
|
|
c, err := plugin.NewCollector(
|
|
|
|
&autoscalingv2.HorizontalPodAutoscaler{},
|
|
|
|
testcase.config,
|
|
|
|
interval,
|
|
|
|
)
|
|
|
|
|
|
|
|
if testcase.shouldWork {
|
|
|
|
require.NotNil(t, c)
|
|
|
|
require.Nil(t, err)
|
2023-05-03 18:13:02 +02:00
|
|
|
require.Equal(t, testcase.expectedQuery, fakePlugin.config["query"])
|
2023-04-25 20:05:47 +02:00
|
|
|
} else {
|
|
|
|
require.Nil(t, c)
|
|
|
|
require.NotNil(t, err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-23 15:40:56 +02:00
|
|
|
func TestExternalRPSCollectorGetMetrics(tt *testing.T) {
|
2023-04-25 20:05:47 +02:00
|
|
|
genericErr := fmt.Errorf("This is an error")
|
|
|
|
expectedMetric := *resource.NewQuantity(int64(42), resource.DecimalSI)
|
|
|
|
|
|
|
|
for _, testcase := range []struct {
|
|
|
|
msg string
|
|
|
|
stub func() ([]CollectedMetric, error)
|
|
|
|
shouldWork bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"Internal collector error",
|
|
|
|
func() ([]CollectedMetric, error) {
|
|
|
|
return nil, genericErr
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Invalid metric collection from internal collector",
|
|
|
|
func() ([]CollectedMetric, error) {
|
|
|
|
return []CollectedMetric{
|
|
|
|
{External: external_metrics.ExternalMetricValue{Value: *resource.NewQuantity(int64(24), resource.DecimalSI)}},
|
|
|
|
{External: external_metrics.ExternalMetricValue{Value: *resource.NewQuantity(int64(42), resource.DecimalSI)}},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Internal collector return single metric",
|
|
|
|
func() ([]CollectedMetric, error) {
|
|
|
|
return []CollectedMetric{
|
|
|
|
{External: external_metrics.ExternalMetricValue{Value: *resource.NewQuantity(int64(42), resource.DecimalSI)}},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
tt.Run(testcase.msg, func(t *testing.T) {
|
|
|
|
fake := makeCollectorWithStub(testcase.stub)
|
2023-05-23 15:40:56 +02:00
|
|
|
c := &ExternalRPSCollector{promCollector: fake}
|
2023-04-25 20:05:47 +02:00
|
|
|
m, err := c.GetMetrics()
|
|
|
|
|
|
|
|
if testcase.shouldWork {
|
|
|
|
require.Nil(t, err)
|
|
|
|
require.NotNil(t, m)
|
|
|
|
require.Len(t, m, 1)
|
2023-05-03 18:13:02 +02:00
|
|
|
require.Equal(t, expectedMetric, m[0].External.Value)
|
2023-04-25 20:05:47 +02:00
|
|
|
} else {
|
|
|
|
require.NotNil(t, err)
|
|
|
|
require.Nil(t, m)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-23 15:40:56 +02:00
|
|
|
func TestExternalRPSCollectorInterval(t *testing.T) {
|
2023-04-25 20:05:47 +02:00
|
|
|
interval := time.Duration(42)
|
|
|
|
fakePlugin := &FakeCollectorPlugin{}
|
2023-05-08 16:16:23 +02:00
|
|
|
pattern, err := regexp.Compile("^[a-zA-Z0-9.-]+$")
|
|
|
|
require.Nil(t, err, "Something is up, regex compiling failed.")
|
2023-05-23 15:40:56 +02:00
|
|
|
plugin := &ExternalRPSCollectorPlugin{
|
2023-04-25 20:05:47 +02:00
|
|
|
metricName: "a_valid_one",
|
|
|
|
promPlugin: fakePlugin,
|
2023-05-08 16:16:23 +02:00
|
|
|
pattern: pattern,
|
2023-04-25 20:05:47 +02:00
|
|
|
}
|
|
|
|
c, err := plugin.NewCollector(
|
|
|
|
&autoscalingv2.HorizontalPodAutoscaler{},
|
2023-05-03 20:49:14 +02:00
|
|
|
&MetricConfig{Config: map[string]string{"hostnames": "foo.bar.baz"}},
|
2023-04-25 20:05:47 +02:00
|
|
|
interval,
|
|
|
|
)
|
|
|
|
|
|
|
|
require.Nil(t, err)
|
2023-05-08 16:16:23 +02:00
|
|
|
require.NotNil(t, c)
|
2023-04-25 20:05:47 +02:00
|
|
|
require.Equal(t, interval, c.Interval())
|
|
|
|
}
|
|
|
|
|
2023-05-23 15:40:56 +02:00
|
|
|
func TestExternalRPSCollectorAndCollectorFabricInteraction(t *testing.T) {
|
2023-05-03 20:49:14 +02:00
|
|
|
expectedQuery := `scalar(sum(rate(a_metric{host=~"just_testing_com"}[1m])) * 0.4200)`
|
2023-04-25 20:05:47 +02:00
|
|
|
hpa := &autoscalingv2.HorizontalPodAutoscaler{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Annotations: map[string]string{
|
2023-05-03 20:54:08 +02:00
|
|
|
"metric-config.external.foo.requests-per-second/hostnames": "just.testing.com",
|
|
|
|
"metric-config.external.foo.requests-per-second/weight": "42",
|
2023-04-25 20:05:47 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
|
|
|
Metrics: []autoscalingv2.MetricSpec{
|
|
|
|
{
|
|
|
|
Type: autoscalingv2.ExternalMetricSourceType,
|
|
|
|
External: &autoscalingv2.ExternalMetricSource{
|
|
|
|
Metric: autoscalingv2.MetricIdentifier{
|
|
|
|
Name: "foo",
|
|
|
|
Selector: &metav1.LabelSelector{
|
2023-05-03 20:54:08 +02:00
|
|
|
MatchLabels: map[string]string{"type": "requests-per-second"},
|
2023-04-25 20:05:47 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
factory := NewCollectorFactory()
|
|
|
|
fakePlugin := makePlugin(42)
|
2023-05-23 15:40:56 +02:00
|
|
|
hostnamePlugin, err := NewExternalRPSCollectorPlugin(fakePlugin, "a_metric")
|
2023-04-25 20:05:47 +02:00
|
|
|
require.NoError(t, err)
|
2023-05-23 15:40:56 +02:00
|
|
|
factory.RegisterExternalCollector([]string{ExternalRPSMetricType}, hostnamePlugin)
|
2023-04-25 20:05:47 +02:00
|
|
|
conf, err := ParseHPAMetrics(hpa)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, conf, 1)
|
|
|
|
|
|
|
|
c, err := factory.NewCollector(hpa, conf[0], 0)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
2023-05-23 15:40:56 +02:00
|
|
|
_, ok := c.(*ExternalRPSCollector)
|
2023-04-25 20:05:47 +02:00
|
|
|
require.True(t, ok)
|
2023-05-03 18:13:02 +02:00
|
|
|
require.Equal(t, expectedQuery, fakePlugin.config["query"])
|
2023-04-25 20:05:47 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-05-23 15:40:56 +02:00
|
|
|
func TestExternalRPSPrometheusCollectorInteraction(t *testing.T) {
|
|
|
|
externalRPSQuery := `scalar(sum(rate(a_metric{host=~"just_testing_com"}[1m])) * 0.4200)`
|
2023-04-25 20:05:47 +02:00
|
|
|
promQuery := "sum(rate(rps[1m]))"
|
|
|
|
hpa := &autoscalingv2.HorizontalPodAutoscaler{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Annotations: map[string]string{
|
2023-05-03 20:54:08 +02:00
|
|
|
"metric-config.external.foo.requests-per-second/hostnames": "just.testing.com",
|
|
|
|
"metric-config.external.foo.requests-per-second/weight": "42",
|
|
|
|
"metric-config.external.bar.prometheus/query": promQuery,
|
2023-04-25 20:05:47 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
|
|
|
Metrics: []autoscalingv2.MetricSpec{
|
|
|
|
{
|
|
|
|
Type: autoscalingv2.ExternalMetricSourceType,
|
|
|
|
External: &autoscalingv2.ExternalMetricSource{
|
|
|
|
Metric: autoscalingv2.MetricIdentifier{
|
|
|
|
Name: "foo",
|
|
|
|
Selector: &metav1.LabelSelector{
|
2023-05-03 20:54:08 +02:00
|
|
|
MatchLabels: map[string]string{"type": "requests-per-second"},
|
2023-04-25 20:05:47 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: autoscalingv2.ExternalMetricSourceType,
|
|
|
|
External: &autoscalingv2.ExternalMetricSource{
|
|
|
|
Metric: autoscalingv2.MetricIdentifier{
|
|
|
|
Name: "bar",
|
|
|
|
Selector: &metav1.LabelSelector{
|
|
|
|
MatchLabels: map[string]string{"type": "prometheus"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
factory := NewCollectorFactory()
|
|
|
|
promPlugin, err := NewPrometheusCollectorPlugin(nil, "http://prometheus")
|
|
|
|
require.NoError(t, err)
|
|
|
|
factory.RegisterExternalCollector([]string{PrometheusMetricType, PrometheusMetricNameLegacy}, promPlugin)
|
2023-05-23 15:40:56 +02:00
|
|
|
hostnamePlugin, err := NewExternalRPSCollectorPlugin(promPlugin, "a_metric")
|
2023-04-25 20:05:47 +02:00
|
|
|
require.NoError(t, err)
|
2023-05-23 15:40:56 +02:00
|
|
|
factory.RegisterExternalCollector([]string{ExternalRPSMetricType}, hostnamePlugin)
|
2023-04-25 20:05:47 +02:00
|
|
|
|
|
|
|
conf, err := ParseHPAMetrics(hpa)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, conf, 2)
|
|
|
|
|
|
|
|
collectors := make(map[string]Collector)
|
|
|
|
collectors["hostname"], err = factory.NewCollector(hpa, conf[0], 0)
|
|
|
|
require.NoError(t, err)
|
|
|
|
collectors["prom"], err = factory.NewCollector(hpa, conf[1], 0)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
prom, ok := collectors["prom"].(*PrometheusCollector)
|
|
|
|
require.True(t, ok)
|
2023-05-23 15:40:56 +02:00
|
|
|
hostname, ok := collectors["hostname"].(*ExternalRPSCollector)
|
2023-04-25 20:05:47 +02:00
|
|
|
require.True(t, ok)
|
|
|
|
hostnameProm, ok := hostname.promCollector.(*PrometheusCollector)
|
|
|
|
require.True(t, ok)
|
|
|
|
|
2023-05-03 18:13:02 +02:00
|
|
|
require.Equal(t, promQuery, prom.query)
|
2023-05-23 15:40:56 +02:00
|
|
|
require.Equal(t, externalRPSQuery, hostnameProm.query)
|
2023-04-25 20:05:47 +02:00
|
|
|
}
|