mirror of https://github.com/grafana/loki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
307 lines
8.4 KiB
307 lines
8.4 KiB
package queryrangebase
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
jsoniter "github.com/json-iterator/go"
|
|
"github.com/opentracing/opentracing-go/mocktracer"
|
|
"github.com/prometheus/prometheus/model/timestamp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/loki/v3/pkg/logproto"
|
|
)
|
|
|
|
func TestResponse(t *testing.T) {
|
|
r := *parsedResponse
|
|
r.Headers = respHeaders
|
|
for i, tc := range []struct {
|
|
body string
|
|
expected *PrometheusResponse
|
|
}{
|
|
{
|
|
body: responseBody,
|
|
expected: &r,
|
|
},
|
|
} {
|
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
response := &http.Response{
|
|
StatusCode: 200,
|
|
Header: http.Header{"Content-Type": []string{"application/json"}},
|
|
Body: io.NopCloser(bytes.NewBuffer([]byte(tc.body))),
|
|
}
|
|
resp, err := PrometheusCodecForRangeQueries.DecodeResponse(context.Background(), response, nil)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.expected, resp)
|
|
|
|
// Reset response, as the above call will have consumed the body reader.
|
|
response = &http.Response{
|
|
StatusCode: 200,
|
|
Header: http.Header{"Content-Type": []string{"application/json"}},
|
|
Body: io.NopCloser(bytes.NewBuffer([]byte(tc.body))),
|
|
ContentLength: int64(len(tc.body)),
|
|
}
|
|
resp2, err := PrometheusCodecForRangeQueries.EncodeResponse(context.Background(), nil, resp)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, response, resp2)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeAPIResponses(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
input []Response
|
|
expected Response
|
|
}{
|
|
{
|
|
name: "No responses shouldn't panic and return a non-null result and result type.",
|
|
input: []Response{},
|
|
expected: &PrometheusResponse{
|
|
Status: StatusSuccess,
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "A single empty response shouldn't panic.",
|
|
input: []Response{
|
|
&PrometheusResponse{
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{},
|
|
},
|
|
},
|
|
},
|
|
expected: &PrometheusResponse{
|
|
Status: StatusSuccess,
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "Multiple empty responses shouldn't panic.",
|
|
input: []Response{
|
|
&PrometheusResponse{
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{},
|
|
},
|
|
},
|
|
&PrometheusResponse{
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{},
|
|
},
|
|
},
|
|
},
|
|
expected: &PrometheusResponse{
|
|
Status: StatusSuccess,
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "Basic merging of two responses.",
|
|
input: []Response{
|
|
&PrometheusResponse{
|
|
Warnings: []string{"warning1", "warning2"},
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{
|
|
{
|
|
Labels: []logproto.LabelAdapter{},
|
|
Samples: []logproto.LegacySample{
|
|
{Value: 0, TimestampMs: 0},
|
|
{Value: 1, TimestampMs: 1},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
&PrometheusResponse{
|
|
Warnings: []string{"warning2", "warning3"},
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{
|
|
{
|
|
Labels: []logproto.LabelAdapter{},
|
|
Samples: []logproto.LegacySample{
|
|
{Value: 2, TimestampMs: 2},
|
|
{Value: 3, TimestampMs: 3},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: &PrometheusResponse{
|
|
Status: StatusSuccess,
|
|
Warnings: []string{"warning1", "warning2", "warning3"},
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{
|
|
{
|
|
Labels: []logproto.LabelAdapter{},
|
|
Samples: []logproto.LegacySample{
|
|
{Value: 0, TimestampMs: 0},
|
|
{Value: 1, TimestampMs: 1},
|
|
{Value: 2, TimestampMs: 2},
|
|
{Value: 3, TimestampMs: 3},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "Merging of responses when labels are in different order.",
|
|
input: []Response{
|
|
mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[0,"0"],[1,"1"]]}]}}`),
|
|
mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"]]}]}}`),
|
|
},
|
|
expected: &PrometheusResponse{
|
|
Status: StatusSuccess,
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{
|
|
{
|
|
Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}},
|
|
Samples: []logproto.LegacySample{
|
|
{Value: 0, TimestampMs: 0},
|
|
{Value: 1, TimestampMs: 1000},
|
|
{Value: 2, TimestampMs: 2000},
|
|
{Value: 3, TimestampMs: 3000},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "Merging of samples where there is single overlap.",
|
|
input: []Response{
|
|
mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[1,"1"],[2,"2"]]}]}}`),
|
|
mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"]]}]}}`),
|
|
},
|
|
expected: &PrometheusResponse{
|
|
Status: StatusSuccess,
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{
|
|
{
|
|
Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}},
|
|
Samples: []logproto.LegacySample{
|
|
{Value: 1, TimestampMs: 1000},
|
|
{Value: 2, TimestampMs: 2000},
|
|
{Value: 3, TimestampMs: 3000},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Merging of samples where there is multiple partial overlaps.",
|
|
input: []Response{
|
|
mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[1,"1"],[2,"2"],[3,"3"]]}]}}`),
|
|
mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"],[4,"4"],[5,"5"]]}]}}`),
|
|
},
|
|
expected: &PrometheusResponse{
|
|
Status: StatusSuccess,
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{
|
|
{
|
|
Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}},
|
|
Samples: []logproto.LegacySample{
|
|
{Value: 1, TimestampMs: 1000},
|
|
{Value: 2, TimestampMs: 2000},
|
|
{Value: 3, TimestampMs: 3000},
|
|
{Value: 4, TimestampMs: 4000},
|
|
{Value: 5, TimestampMs: 5000},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Merging of samples where there is complete overlap.",
|
|
input: []Response{
|
|
mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[2,"2"],[3,"3"]]}]}}`),
|
|
mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"],[4,"4"],[5,"5"]]}]}}`),
|
|
},
|
|
expected: &PrometheusResponse{
|
|
Status: StatusSuccess,
|
|
Data: PrometheusData{
|
|
ResultType: matrix,
|
|
Result: []SampleStream{
|
|
{
|
|
Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}},
|
|
Samples: []logproto.LegacySample{
|
|
{Value: 2, TimestampMs: 2000},
|
|
{Value: 3, TimestampMs: 3000},
|
|
{Value: 4, TimestampMs: 4000},
|
|
{Value: 5, TimestampMs: 5000},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
output, err := PrometheusCodecForRangeQueries.MergeResponse(tc.input...)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, output)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrometheusRequestSpanLogging(t *testing.T) {
|
|
now := time.Now()
|
|
end := now.Add(1000 * time.Second)
|
|
req := PrometheusRequest{
|
|
Start: now,
|
|
End: end,
|
|
}
|
|
|
|
span := mocktracer.MockSpan{}
|
|
req.LogToSpan(&span)
|
|
|
|
for _, l := range span.Logs() {
|
|
for _, field := range l.Fields {
|
|
if field.Key == "start" {
|
|
require.Equal(t, timestamp.Time(now.UnixMilli()).String(), field.ValueString)
|
|
}
|
|
if field.Key == "end" {
|
|
require.Equal(t, timestamp.Time(end.UnixMilli()).String(), field.ValueString)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func mustParse(t *testing.T, response string) Response {
|
|
var resp PrometheusResponse
|
|
// Needed as goimports automatically add a json import otherwise.
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
|
require.NoError(t, json.Unmarshal([]byte(response), &resp))
|
|
return &resp
|
|
}
|
|
|