From 5bf87cb10e06e606bd31c6d0a60ca53ed2669d95 Mon Sep 17 00:00:00 2001 From: Abe Friesen <2319792+doyshinda@users.noreply.github.com> Date: Tue, 10 Mar 2020 03:45:28 -0600 Subject: [PATCH] Add support for passing URL query params to pod metric endpoints (#109) Adds a new metric-config option named `rawQuery`. The value of this option will be appended to the metric `path` as URL query parameters to be used by the pod's application. E.g.,: ``` metric-config.pods.requests-per-second.json-path/rawQuery: "foo=bar&baz=bop" ``` will apppend `?foo=bar&baz=bop` to the URL. Signed-off-by: Abe Friesen <2319792+doyshinda@users.noreply.github.com> --- README.md | 11 ++++ pkg/collector/json_path_collector.go | 35 +++++++++---- pkg/collector/json_path_collector_test.go | 61 +++++++++++++++++++++++ 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4b638541..e6c1cd14 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,17 @@ values of JSONPath expressions that evaluate to arrays/slices of numbers. It's optional but when the expression evaluates to an array/slice, it's absence will produce an error. The supported aggregation functions are `avg`, `max`, `min` and `sum`. +The `raw-query` configuration option specifies the query params to send along to the endpoint: +```yaml + metric-config.pods.requests-per-second.json-path/path: /metrics + metric-config.pods.requests-per-second.json-path/port: "9090" + metric-config.pods.requests-per-second.json-path/raw-query: "foo=bar&baz=bop" +``` +will create a URL like this: +``` +http://:9090/metrics?foo=bar&baz=bop +``` + ## Prometheus collector The Prometheus collector is a generic collector which can map Prometheus diff --git a/pkg/collector/json_path_collector.go b/pkg/collector/json_path_collector.go index 59e139a5..362389f6 100644 --- a/pkg/collector/json_path_collector.go +++ b/pkg/collector/json_path_collector.go @@ -25,6 +25,7 @@ type JSONPathMetricsGetter struct { port int aggregator string client *http.Client + rawQuery string } // NewJSONPathMetricsGetter initializes a new JSONPathMetricsGetter. @@ -49,6 +50,10 @@ func NewJSONPathMetricsGetter(config map[string]string) (*JSONPathMetricsGetter, getter.path = v } + if v, ok := config["raw-query"]; ok { + getter.rawQuery = v + } + if v, ok := config["port"]; ok { n, err := strconv.Atoi(v) if err != nil { @@ -83,7 +88,7 @@ func defaultHTTPClient() *http.Client { // endpoint and extracting the desired value using the specified json path // query. func (g *JSONPathMetricsGetter) GetMetric(pod *corev1.Pod) (float64, error) { - data, err := g.getPodMetrics(pod, g.scheme, g.path, g.port) + data, err := g.getPodMetrics(pod) if err != nil { return 0, err } @@ -140,20 +145,12 @@ func castSlice(in []interface{}) ([]float64, error) { } // getPodMetrics returns the content of the pods metrics endpoint. -func (g *JSONPathMetricsGetter) getPodMetrics(pod *corev1.Pod, scheme, path string, port int) ([]byte, error) { +func (g *JSONPathMetricsGetter) getPodMetrics(pod *corev1.Pod) ([]byte, error) { if pod.Status.PodIP == "" { return nil, fmt.Errorf("pod %s/%s does not have a pod IP", pod.Namespace, pod.Namespace) } - if scheme == "" { - scheme = "http" - } - - metricsURL := url.URL{ - Scheme: scheme, - Host: fmt.Sprintf("%s:%d", pod.Status.PodIP, port), - Path: path, - } + metricsURL := g.buildMetricsURL(pod.Status.PodIP) request, err := http.NewRequest(http.MethodGet, metricsURL.String(), nil) if err != nil { @@ -178,6 +175,22 @@ func (g *JSONPathMetricsGetter) getPodMetrics(pod *corev1.Pod, scheme, path stri return data, nil } +// buildMetricsURL will build the full URL needed to hit the pod metric endpoint. +func (g *JSONPathMetricsGetter) buildMetricsURL(podIP string) url.URL { + var scheme = g.scheme + + if scheme == "" { + scheme = "http" + } + + return url.URL{ + Scheme: scheme, + Host: fmt.Sprintf("%s:%d", podIP, g.port), + Path: g.path, + RawQuery: g.rawQuery, + } +} + // reduce will reduce a slice of numbers given a aggregator function's name. If it's empty or not recognized, an error is returned. func reduce(values []float64, aggregator string) (float64, error) { switch aggregator { diff --git a/pkg/collector/json_path_collector_test.go b/pkg/collector/json_path_collector_test.go index e1c68059..ffc803ab 100644 --- a/pkg/collector/json_path_collector_test.go +++ b/pkg/collector/json_path_collector_test.go @@ -1,6 +1,7 @@ package collector import ( + "fmt" "testing" "github.com/oliveagle/jsonpath" @@ -70,6 +71,25 @@ func TestNewJSONPathMetricsGetter(t *testing.T) { _, err4 := NewJSONPathMetricsGetter(configErrorPort) require.Error(t, err4) + + configWithRawQuery := map[string]string{ + "json-key": "$.values", + "scheme": "http", + "path": "/metrics", + "port": "9090", + "raw-query": "foo=bar&baz=bop", + } + jpath5, _ := jsonpath.Compile(configWithRawQuery["json-key"]) + getterWithRawQuery, err5 := NewJSONPathMetricsGetter(configWithRawQuery) + + require.NoError(t, err5) + compareMetricsGetter(t, &JSONPathMetricsGetter{ + jsonPath: jpath5, + scheme: "http", + path: "/metrics", + port: 9090, + rawQuery: "foo=bar&baz=bop", + }, getterWithRawQuery) } func TestCastSlice(t *testing.T) { @@ -110,3 +130,44 @@ func TestReduce(t *testing.T) { _, err5 := reduce([]float64{1, 2, 3}, "inexistent_function") require.Errorf(t, err5, "slice of numbers was returned by JSONPath, but no valid aggregator function was specified: %v", "inexistent_function") } + +func TestBuildMetricsURL(t *testing.T) { + scheme := "http" + ip := "1.2.3.4" + port := "9090" + path := "/v1/test/" + rawQuery := "foo=bar&baz=bop" + + // Test building URL with rawQuery + configWithRawQuery := map[string]string{ + "json-key": "$.value", + "scheme": scheme, + "path": path, + "port": port, + "raw-query": rawQuery, + } + _, err := jsonpath.Compile(configWithRawQuery["json-key"]) + require.NoError(t, err) + getterWithRawQuery, err1 := NewJSONPathMetricsGetter(configWithRawQuery) + require.NoError(t, err1) + + expectedURLWithQuery := fmt.Sprintf("%s://%s:%s%s?%s", scheme, ip, port, path, rawQuery) + receivedURLWithQuery := getterWithRawQuery.buildMetricsURL(ip) + require.Equal(t, receivedURLWithQuery.String(), expectedURLWithQuery) + + // Test building URL without rawQuery + configWithNoQuery := map[string]string{ + "json-key": "$.value", + "scheme": scheme, + "path": path, + "port": port, + } + _, err2 := jsonpath.Compile(configWithNoQuery["json-key"]) + require.NoError(t, err2) + getterWithNoQuery, err3 := NewJSONPathMetricsGetter(configWithNoQuery) + require.NoError(t, err3) + + expectedURLNoQuery := fmt.Sprintf("%s://%s:%s%s", scheme, ip, port, path) + receivedURLNoQuery := getterWithNoQuery.buildMetricsURL(ip) + require.Equal(t, receivedURLNoQuery.String(), expectedURLNoQuery) +}