Skip to content

Commit 328bc3b

Browse files
authored
Add support for /api/v1/format_query API (#6893)
* Add support for /api/v1/format_query API Signed-off-by: Siddarth Gundu <[email protected]> * update changelog Signed-off-by: Siddarth Gundu <[email protected]> * Integration: Add test for format query API Signed-off-by: Siddarth Gundu <[email protected]> * fix lint error Signed-off-by: Siddarth Gundu <[email protected]> --------- Signed-off-by: Siddarth Gundu <[email protected]>
1 parent 52a833c commit 328bc3b

File tree

5 files changed

+148
-0
lines changed

5 files changed

+148
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
## master / unreleased
4+
* [FEATURE] Query Frontend: Add support /api/v1/format_query API for formatting queries. #6893
45
* [CHANGE] StoreGateway/Alertmanager: Add default 5s connection timeout on client. #6603
56
* [CHANGE] Ingester: Remove EnableNativeHistograms config flag and instead gate keep through new per-tenant limit at ingestion. #6718
67
* [CHANGE] Validate a tenantID when to use a single tenant resolver. #6727

docs/api/_index.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ For the sake of clarity, in this document we have grouped API endpoints by servi
3737
| [Instant query](#instant-query) | Querier, Query-frontend || `GET,POST <prometheus-http-prefix>/api/v1/query` |
3838
| [Range query](#range-query) | Querier, Query-frontend || `GET,POST <prometheus-http-prefix>/api/v1/query_range` |
3939
| [Exemplar query](#exemplar-query) | Querier, Query-frontend || `GET,POST <prometheus-http-prefix>/api/v1/query_exemplars` |
40+
| [Format query](#format-query) | Querier, Query-frontend || `GET,POST <prometheus-http-prefix>/api/v1/format-query` |
4041
| [Get series by label matchers](#get-series-by-label-matchers) | Querier, Query-frontend || `GET,POST <prometheus-http-prefix>/api/v1/series` |
4142
| [Get label names](#get-label-names) | Querier, Query-frontend || `GET,POST <prometheus-http-prefix>/api/v1/labels` |
4243
| [Get label values](#get-label-values) | Querier, Query-frontend || `GET <prometheus-http-prefix>/api/v1/label/{name}/values` |
@@ -368,6 +369,21 @@ _For more information, please check out the Prometheus [exemplar query](https://
368369

369370
_Requires [authentication](#authentication)._
370371

372+
### Format query
373+
374+
```
375+
GET,POST <prometheus-http-prefix>/api/v1/format_query
376+
377+
# Legacy
378+
GET,POST <legacy-http-prefix>/api/v1/format_query
379+
```
380+
381+
Prometheus-compatible format query endpoint. The endpoint formats a PromQL expression in a prettified way.
382+
383+
_For more information, please check out the Prometheus [fomatting query expressions](https://prometheus.io/docs/prometheus/latest/querying/api/#formatting-query-expressions) documentation._
384+
385+
_Requires [authentication](#authentication)._
386+
371387
### Get series by label matchers
372388

373389
```

integration/format_query_api_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//go:build requires_docker
2+
// +build requires_docker
3+
4+
package integration
5+
6+
import (
7+
"encoding/json"
8+
"fmt"
9+
"io"
10+
"net/http"
11+
"testing"
12+
13+
"github.com/stretchr/testify/require"
14+
15+
"github.com/cortexproject/cortex/integration/e2e"
16+
e2edb "github.com/cortexproject/cortex/integration/e2e/db"
17+
"github.com/cortexproject/cortex/integration/e2ecortex"
18+
)
19+
20+
func TestFormatQueryAPI(t *testing.T) {
21+
s, err := e2e.NewScenario(networkName)
22+
require.NoError(t, err)
23+
defer s.Close()
24+
25+
// Start dependencies.
26+
consul := e2edb.NewConsul()
27+
minio := e2edb.NewMinio(9000, bucketName)
28+
require.NoError(t, s.StartAndWaitReady(consul, minio))
29+
30+
flags := mergeFlags(BlocksStorageFlags(), map[string]string{
31+
"-auth.enabled": "true",
32+
})
33+
34+
// Start the query-frontend.
35+
queryFrontend := e2ecortex.NewQueryFrontend("query-frontend", flags, "")
36+
require.NoError(t, s.Start(queryFrontend))
37+
38+
querier := e2ecortex.NewQuerier("querier", e2ecortex.RingStoreConsul, consul.NetworkHTTPEndpoint(), mergeFlags(flags, map[string]string{
39+
"-querier.frontend-address": queryFrontend.NetworkGRPCEndpoint(),
40+
}), "")
41+
require.NoError(t, s.StartAndWaitReady(querier))
42+
43+
// Start querier without frontend.
44+
querierDirect := e2ecortex.NewQuerier("querier-direct", e2ecortex.RingStoreConsul, consul.NetworkHTTPEndpoint(), flags, "")
45+
require.NoError(t, s.StartAndWaitReady(querierDirect))
46+
47+
require.NoError(t, s.WaitReady(queryFrontend))
48+
49+
testCases := []struct {
50+
name string
51+
query string
52+
expectedResp string
53+
expectError bool
54+
method string
55+
useOnlyQuerier bool
56+
}{
57+
{
58+
name: "Valid query for GET method",
59+
query: "foo/bar",
60+
expectedResp: "foo / bar",
61+
expectError: false,
62+
method: "GET",
63+
},
64+
{
65+
name: "Valid query for POST method",
66+
query: "foo/bar",
67+
expectedResp: "foo / bar",
68+
expectError: false,
69+
method: "POST",
70+
},
71+
{
72+
name: "Invalid query for GET method",
73+
query: "invalid_expression/",
74+
expectError: true,
75+
method: "GET",
76+
},
77+
{
78+
name: "Invalid query for POST method",
79+
query: "invalid_expression/",
80+
expectError: true,
81+
method: "POST",
82+
},
83+
{
84+
name: "Valid query using only querier (GET)",
85+
query: "foo/bar",
86+
expectedResp: "foo / bar",
87+
expectError: false,
88+
method: "GET",
89+
useOnlyQuerier: true,
90+
},
91+
}
92+
var parsed struct {
93+
Data string `json:"data"`
94+
}
95+
96+
for _, tc := range testCases {
97+
t.Run(tc.name, func(t *testing.T) {
98+
var endpoint string
99+
if tc.useOnlyQuerier {
100+
endpoint = fmt.Sprintf("http://%s/api/prom/api/v1/format_query?query=%s", querierDirect.HTTPEndpoint(), tc.query)
101+
} else {
102+
endpoint = fmt.Sprintf("http://%s/api/prom/api/v1/format_query?query=%s", queryFrontend.HTTPEndpoint(), tc.query)
103+
}
104+
105+
req, err := http.NewRequest(tc.method, endpoint, nil)
106+
req.Header.Set("X-Scope-OrgID", "user-1")
107+
require.NoError(t, err)
108+
109+
resp, err := http.DefaultClient.Do(req)
110+
require.NoError(t, err)
111+
defer resp.Body.Close()
112+
113+
if tc.expectError {
114+
require.NotEqual(t, 200, resp.StatusCode)
115+
return
116+
} else {
117+
require.Equal(t, 200, resp.StatusCode)
118+
}
119+
120+
body, err := io.ReadAll(resp.Body)
121+
require.NoError(t, err)
122+
123+
require.NoError(t, json.Unmarshal(body, &parsed))
124+
require.Equal(t, tc.expectedResp, parsed.Data)
125+
})
126+
}
127+
}

pkg/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ func (a *API) RegisterQueryAPI(handler http.Handler) {
430430
a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/query"), hf, true, "GET", "POST")
431431
a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/query_range"), hf, true, "GET", "POST")
432432
a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/query_exemplars"), hf, true, "GET", "POST")
433+
a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/format_query"), hf, true, "GET", "POST")
433434
a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/labels"), hf, true, "GET", "POST")
434435
a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/label/{name}/values"), hf, true, "GET")
435436
a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/series"), hf, true, "GET", "POST", "DELETE")
@@ -440,6 +441,7 @@ func (a *API) RegisterQueryAPI(handler http.Handler) {
440441
a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/query"), hf, true, "GET", "POST")
441442
a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/query_range"), hf, true, "GET", "POST")
442443
a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/query_exemplars"), hf, true, "GET", "POST")
444+
a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/format_query"), hf, true, "GET", "POST")
443445
a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/labels"), hf, true, "GET", "POST")
444446
a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/label/{name}/values"), hf, true, "GET")
445447
a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/series"), hf, true, "GET", "POST", "DELETE")

pkg/api/handlers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ func NewQuerierHandler(
290290
router.Path(path.Join(prefix, "/api/v1/query")).Methods("GET", "POST").Handler(queryAPI.Wrap(queryAPI.InstantQueryHandler))
291291
router.Path(path.Join(prefix, "/api/v1/query_range")).Methods("GET", "POST").Handler(queryAPI.Wrap(queryAPI.RangeQueryHandler))
292292
router.Path(path.Join(prefix, "/api/v1/query_exemplars")).Methods("GET", "POST").Handler(promRouter)
293+
router.Path(path.Join(prefix, "/api/v1/format_query")).Methods("GET", "POST").Handler(promRouter)
293294
router.Path(path.Join(prefix, "/api/v1/labels")).Methods("GET", "POST").Handler(promRouter)
294295
router.Path(path.Join(prefix, "/api/v1/label/{name}/values")).Methods("GET").Handler(promRouter)
295296
router.Path(path.Join(prefix, "/api/v1/series")).Methods("GET", "POST", "DELETE").Handler(promRouter)
@@ -303,6 +304,7 @@ func NewQuerierHandler(
303304
router.Path(path.Join(legacyPrefix, "/api/v1/query")).Methods("GET", "POST").Handler(queryAPI.Wrap(queryAPI.InstantQueryHandler))
304305
router.Path(path.Join(legacyPrefix, "/api/v1/query_range")).Methods("GET", "POST").Handler(queryAPI.Wrap(queryAPI.RangeQueryHandler))
305306
router.Path(path.Join(legacyPrefix, "/api/v1/query_exemplars")).Methods("GET", "POST").Handler(legacyPromRouter)
307+
router.Path(path.Join(legacyPrefix, "/api/v1/format_query")).Methods("GET", "POST").Handler(legacyPromRouter)
306308
router.Path(path.Join(legacyPrefix, "/api/v1/labels")).Methods("GET", "POST").Handler(legacyPromRouter)
307309
router.Path(path.Join(legacyPrefix, "/api/v1/label/{name}/values")).Methods("GET").Handler(legacyPromRouter)
308310
router.Path(path.Join(legacyPrefix, "/api/v1/series")).Methods("GET", "POST", "DELETE").Handler(legacyPromRouter)

0 commit comments

Comments
 (0)