Skip to content

Commit 1404aa1

Browse files
CharlieTLeclaude
andcommitted
Add integration tests for /api/v1/status/tsdb endpoint
End-to-end test that starts a single-binary Cortex cluster, pushes series with varying cardinality, and validates the TSDB status API returns correct series counts, metric name breakdowns, and limit truncation. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> Signed-off-by: Charlie Le <[email protected]>
1 parent ff65033 commit 1404aa1

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

integration/api_endpoints_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/prometheus/prometheus/model/labels"
14+
"github.com/prometheus/prometheus/prompb"
1415
"github.com/stretchr/testify/assert"
1516
"github.com/stretchr/testify/require"
1617
"github.com/thanos-io/thanos/pkg/runutil"
@@ -134,3 +135,67 @@ func Test_AllUserStats_WhenIngesterRollingUpdate(t *testing.T) {
134135
require.Len(t, userStats, 1)
135136
require.Equal(t, uint64(2), userStats[0].QueriedIngesters)
136137
}
138+
139+
func TestTSDBStatus(t *testing.T) {
140+
s, err := e2e.NewScenario(networkName)
141+
require.NoError(t, err)
142+
defer s.Close()
143+
144+
flags := BlocksStorageFlags()
145+
flags["-distributor.replication-factor"] = "1"
146+
147+
// Start dependencies.
148+
consul := e2edb.NewConsul()
149+
minio := e2edb.NewMinio(9000, flags["-blocks-storage.s3.bucket-name"])
150+
require.NoError(t, s.StartAndWaitReady(consul, minio))
151+
152+
// Start Cortex in single binary mode.
153+
cortex := e2ecortex.NewSingleBinary("cortex-1", flags, "")
154+
require.NoError(t, s.StartAndWaitReady(cortex))
155+
156+
// Wait until the ingester ring is active.
157+
require.NoError(t, cortex.WaitSumMetricsWithOptions(e2e.Equals(1), []string{"cortex_ring_members"}, e2e.WithLabelMatchers(
158+
labels.MustNewMatcher(labels.MatchEqual, "name", "ingester"),
159+
labels.MustNewMatcher(labels.MatchEqual, "state", "ACTIVE"))))
160+
161+
client, err := e2ecortex.NewClient(cortex.HTTPEndpoint(), cortex.HTTPEndpoint(), "", "", "test-tenant")
162+
require.NoError(t, err)
163+
164+
now := time.Now()
165+
166+
// Push multiple series to create interesting cardinality:
167+
// - http_requests_total with 3 label combinations
168+
// - process_cpu_seconds_total with 1 label combination
169+
series1, _ := generateSeries("http_requests_total", now, prompb.Label{Name: "method", Value: "GET"}, prompb.Label{Name: "status", Value: "200"})
170+
series2, _ := generateSeries("http_requests_total", now, prompb.Label{Name: "method", Value: "POST"}, prompb.Label{Name: "status", Value: "200"})
171+
series3, _ := generateSeries("http_requests_total", now, prompb.Label{Name: "method", Value: "GET"}, prompb.Label{Name: "status", Value: "500"})
172+
series4, _ := generateSeries("process_cpu_seconds_total", now, prompb.Label{Name: "instance", Value: "a"})
173+
174+
allSeries := append(series1, series2...)
175+
allSeries = append(allSeries, series3...)
176+
allSeries = append(allSeries, series4...)
177+
178+
res, err := client.Push(allSeries)
179+
require.NoError(t, err)
180+
require.Equal(t, 200, res.StatusCode)
181+
182+
// Query TSDB status with default limit.
183+
status, err := client.TSDBStatus(10)
184+
require.NoError(t, err)
185+
186+
assert.Equal(t, uint64(4), status.NumSeries)
187+
require.GreaterOrEqual(t, len(status.SeriesCountByMetricName), 2)
188+
assert.Equal(t, "http_requests_total", status.SeriesCountByMetricName[0].Name)
189+
assert.Equal(t, uint64(3), status.SeriesCountByMetricName[0].Value)
190+
assert.Equal(t, "process_cpu_seconds_total", status.SeriesCountByMetricName[1].Name)
191+
assert.Equal(t, uint64(1), status.SeriesCountByMetricName[1].Value)
192+
assert.NotEmpty(t, status.LabelValueCountByLabelName)
193+
assert.Greater(t, status.MinTime, int64(0))
194+
assert.Greater(t, status.MaxTime, int64(0))
195+
196+
// Query TSDB status with limit=1 to verify truncation.
197+
status, err = client.TSDBStatus(1)
198+
require.NoError(t, err)
199+
assert.Len(t, status.SeriesCountByMetricName, 1)
200+
assert.Equal(t, "http_requests_total", status.SeriesCountByMetricName[0].Name)
201+
}

integration/e2ecortex/client.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"go.opentelemetry.io/collector/pdata/pmetric"
3535
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
3636

37+
"github.com/cortexproject/cortex/pkg/distributor"
3738
"github.com/cortexproject/cortex/pkg/ingester"
3839
"github.com/cortexproject/cortex/pkg/ruler"
3940
"github.com/cortexproject/cortex/pkg/util/backoff"
@@ -164,6 +165,40 @@ func (c *Client) AllUserStats() ([]ingester.UserIDStats, error) {
164165
return userStats, nil
165166
}
166167

168+
func (c *Client) TSDBStatus(limit int) (*distributor.TSDBStatusResult, error) {
169+
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1/status/tsdb?limit=%d", c.distributorAddress, limit), nil)
170+
if err != nil {
171+
return nil, err
172+
}
173+
req.Header.Set("Accept", "application/json")
174+
req.Header.Set("X-Scope-OrgID", c.orgID)
175+
176+
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
177+
defer cancel()
178+
179+
res, err := c.httpClient.Do(req.WithContext(ctx))
180+
if err != nil {
181+
return nil, err
182+
}
183+
defer res.Body.Close()
184+
if res.StatusCode != http.StatusOK {
185+
body, _ := io.ReadAll(res.Body)
186+
return nil, fmt.Errorf("unexpected status code %d: %s", res.StatusCode, string(body))
187+
}
188+
189+
body, err := io.ReadAll(res.Body)
190+
if err != nil {
191+
return nil, err
192+
}
193+
194+
var result distributor.TSDBStatusResult
195+
if err := json.Unmarshal(body, &result); err != nil {
196+
return nil, err
197+
}
198+
199+
return &result, nil
200+
}
201+
167202
// Push the input timeseries to the remote endpoint
168203
func (c *Client) Push(timeseries []prompb.TimeSeries, metadata ...prompb.MetricMetadata) (*http.Response, error) {
169204
// Create write request

0 commit comments

Comments
 (0)