mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 21:53:00 +08:00

This PR adds `backend.ErrorSourceDownstream` values to all `backend.DataResponse` values where it's certain that the error wasn't the result of the InfluxDB datasource plugin.
416 lines
16 KiB
Go
416 lines
16 KiB
Go
package buffered
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/grafana/grafana-plugin-sdk-go/experimental"
|
|
"github.com/influxdata/influxql"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/tsdb/influxdb/influxql/util"
|
|
"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
|
|
)
|
|
|
|
const (
|
|
shouldUpdate = false
|
|
testPath = "../testdata"
|
|
)
|
|
|
|
func readJsonFile(filePath string) io.ReadCloser {
|
|
bytes, err := os.ReadFile(filepath.Join(testPath, filepath.Clean(filePath)+".json"))
|
|
if err != nil {
|
|
panic(fmt.Sprintf("cannot read the file: %s", filePath))
|
|
}
|
|
|
|
return io.NopCloser(strings.NewReader(string(bytes)))
|
|
}
|
|
|
|
func generateQuery(query, resFormat, alias string) *models.Query {
|
|
statement, _ := influxql.ParseStatement(query)
|
|
return &models.Query{
|
|
RawQuery: query,
|
|
UseRawQuery: true,
|
|
Alias: alias,
|
|
ResultFormat: resFormat,
|
|
Statement: statement,
|
|
}
|
|
}
|
|
|
|
// show_diagnostics file won't be added to test files because of its inconsistent
|
|
// json data with other responses. But I do add it here just to have it.
|
|
// It can be parsed with time_series response but not table.
|
|
// It only works with InfluxDB v1.x. The usage of it is quite limited.
|
|
var testFiles = []string{
|
|
"all_values_are_null",
|
|
"influx_select_all_from_cpu",
|
|
"one_measurement_with_two_columns",
|
|
"response_with_weird_tag",
|
|
"some_values_are_null",
|
|
"simple_response",
|
|
"multiple_series_with_tags_and_multiple_columns",
|
|
"multiple_series_with_tags",
|
|
"empty_response",
|
|
"metric_find_queries",
|
|
"show_tag_values_response",
|
|
"retention_policy",
|
|
"simple_response_with_diverse_data_types",
|
|
"multiple_measurements",
|
|
"string_column_with_null_value",
|
|
"string_column_with_null_value2",
|
|
"many_columns",
|
|
"response_with_nil_bools_and_nil_strings",
|
|
"invalid_value_format",
|
|
}
|
|
|
|
func TestReadInfluxAsTimeSeries(t *testing.T) {
|
|
for _, f := range testFiles {
|
|
t.Run(f, runScenario(f, "time_series"))
|
|
}
|
|
}
|
|
|
|
func TestReadInfluxAsTable(t *testing.T) {
|
|
for _, f := range testFiles {
|
|
t.Run(f, runScenario(f, "table"))
|
|
}
|
|
}
|
|
|
|
func runScenario(tf string, resultFormat string) func(t *testing.T) {
|
|
return func(t *testing.T) {
|
|
f := readJsonFile(tf)
|
|
|
|
query := generateQuery("Test raw query", resultFormat, "")
|
|
|
|
runQuery(t, f, tf, resultFormat, query)
|
|
}
|
|
}
|
|
|
|
func runQuery(t *testing.T, f io.ReadCloser, tf string, rf string, query *models.Query) {
|
|
rsp := ResponseParse(f, 200, query)
|
|
|
|
if strings.Contains(tf, "error") {
|
|
require.Error(t, rsp.Error)
|
|
return
|
|
}
|
|
require.NoError(t, rsp.Error)
|
|
|
|
fname := tf + "." + rf + ".golden"
|
|
experimental.CheckGoldenJSONResponse(t, testPath, fname, rsp, shouldUpdate)
|
|
}
|
|
|
|
func TestParsingAsTimeSeriesWithoutTimeColumn(t *testing.T) {
|
|
t.Run("cardinality", func(t *testing.T) {
|
|
f := readJsonFile("cardinality")
|
|
|
|
query := generateQuery(`SHOW TAG VALUES CARDINALITY with key = "host"`, "time_series", "")
|
|
|
|
runQuery(t, f, "cardinality", "time_series", query)
|
|
})
|
|
}
|
|
|
|
func TestInfluxdbResponseParser(t *testing.T) {
|
|
t.Run("Influxdb response parser should handle invalid JSON", func(t *testing.T) {
|
|
result := ResponseParse(
|
|
io.NopCloser(strings.NewReader(`{ invalid }`)),
|
|
200,
|
|
generateQuery("Test raw query", "time_series", ""),
|
|
)
|
|
|
|
require.Nil(t, result.Frames)
|
|
require.Error(t, result.Error)
|
|
})
|
|
|
|
t.Run("Influxdb response parser with alias", func(t *testing.T) {
|
|
labels, err := data.LabelsFromString("/cluster/name/=Cluster/, @cluster@name@=Cluster@, cluster-name=Cluster, datacenter=America, dc.region.name=Northeast")
|
|
require.Nil(t, err)
|
|
newField := data.NewField("Value", labels, []*float64{
|
|
util.ToPtr(222.0),
|
|
})
|
|
newField.Config = &data.FieldConfig{DisplayNameFromDS: "series alias"}
|
|
testFrame := data.NewFrame("series alias",
|
|
data.NewField("Time", nil,
|
|
[]time.Time{
|
|
time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
|
|
}),
|
|
newField,
|
|
)
|
|
testFrame.Meta = &data.FrameMeta{PreferredVisualization: util.GraphVisType, ExecutedQueryString: "Test raw query"}
|
|
testFrameWithoutMeta := data.NewFrame("series alias",
|
|
data.NewField("Time", nil,
|
|
[]time.Time{
|
|
time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
|
|
}),
|
|
newField,
|
|
)
|
|
|
|
t.Run("should parse aliases", func(t *testing.T) {
|
|
result := ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_sereies", "alias $m $measurement"))
|
|
|
|
name := "alias cpu.upc cpu.upc"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
query := generateQuery("Test raw query", "time_series", "alias $col")
|
|
query.Measurement = "10m"
|
|
result = ResponseParse(readJsonFile("response"), 200, query)
|
|
name = "alias mean"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
name = "alias sum"
|
|
testFrameWithoutMeta.Name = name
|
|
newField = data.NewField("Value", labels, []*float64{
|
|
util.ToPtr(333.0),
|
|
})
|
|
testFrameWithoutMeta.Fields[1] = newField
|
|
testFrameWithoutMeta.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
|
|
testFrameWithoutMeta.Meta = nil
|
|
if diff := cmp.Diff(testFrameWithoutMeta, result.Frames[1], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias $tag_datacenter"))
|
|
name = "alias America"
|
|
testFrame.Name = name
|
|
newField = data.NewField("Value", labels, []*float64{
|
|
util.ToPtr(222.0),
|
|
})
|
|
testFrame.Fields[1] = newField
|
|
testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias $tag_datacenter/$tag_datacenter"))
|
|
name = "alias America/America"
|
|
testFrame.Name = name
|
|
newField = data.NewField("Value", labels, []*float64{
|
|
util.ToPtr(222.0),
|
|
})
|
|
testFrame.Fields[1] = newField
|
|
testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
query = generateQuery("Test raw query", "time_series", "alias [[col]]")
|
|
query.Measurement = "10m"
|
|
result = ResponseParse(readJsonFile("response"), 200, query)
|
|
name = "alias mean"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias $0 $1 $2 $3 $4"))
|
|
name = "alias cpu upc $2 $3 $4"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias $0, $1 - $2 - $3, $4: something"))
|
|
name = "alias cpu, upc - $2 - $3, $4: something"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias $1"))
|
|
name = "alias upc"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias $5"))
|
|
name = "alias $5"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "series alias"))
|
|
name = "series alias"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
query = generateQuery("Test raw query", "time_series", "alias [[m]] [[measurement]]")
|
|
query.Measurement = "10m"
|
|
result = ResponseParse(readJsonFile("response"), 200, query)
|
|
name = "alias cpu.upc cpu.upc"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias [[tag_datacenter]]"))
|
|
name = "alias America"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias [[tag_dc.region.name]]"))
|
|
name = "alias Northeast"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias [[tag_cluster-name]]"))
|
|
name = "alias Cluster"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias [[tag_/cluster/name/]]"))
|
|
name = "alias Cluster/"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias [[tag_@cluster@name@]]"))
|
|
name = "alias Cluster@"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
|
|
t.Run("shouldn't parse aliases", func(t *testing.T) {
|
|
result := ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias words with no brackets"))
|
|
name := "alias words with no brackets"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias Test 1.5"))
|
|
name = "alias Test 1.5"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
result = ResponseParse(readJsonFile("response"), 200, generateQuery("Test raw query", "time_series", "alias Test -1"))
|
|
name = "alias Test -1"
|
|
testFrame.Name = name
|
|
testFrame.Fields[1].Config.DisplayNameFromDS = name
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("create frames for tag values and without time column even the query string has cardinality as string", func(t *testing.T) {
|
|
res := ResponseParse(readJsonFile("show_tag_values_response"), 200, generateQuery("SHOW TAG VALUES FROM custom_influxdb_cardinality WITH KEY = \"database\"", "time_series", ""))
|
|
require.NoError(t, res.Error)
|
|
require.Equal(t, "Value", res.Frames[0].Fields[0].Name)
|
|
require.Equal(t, "cpu-total", *res.Frames[0].Fields[0].At(0).(*string))
|
|
})
|
|
|
|
t.Run("Influxdb response parser with errors", func(t *testing.T) {
|
|
result := ResponseParse(readJsonFile("error_response"), 200, generateQuery("Test raw query", "time_series", ""))
|
|
|
|
require.EqualError(t, result.Error, "query-timeout limit exceeded")
|
|
})
|
|
|
|
t.Run("Influxdb response parser with top-level error", func(t *testing.T) {
|
|
result := ResponseParse(readJsonFile("error_on_top_level_response"), 400, generateQuery("Test raw query", "time_series", ""))
|
|
require.Nil(t, result.Frames)
|
|
require.EqualError(t, result.Error, "InfluxDB returned error: error parsing query: found THING")
|
|
require.Equal(t, backend.ErrorSourceDownstream, result.ErrorSource)
|
|
})
|
|
|
|
t.Run("Influxdb response parser with error message", func(t *testing.T) {
|
|
result := ResponseParse(readJsonFile("invalid_response"), 400, generateQuery("Test raw query", "time_series", ""))
|
|
require.Nil(t, result.Frames)
|
|
require.EqualError(t, result.Error, "InfluxDB returned error: failed to parse query: found WERE, expected ; at line 1, char 38")
|
|
require.Equal(t, backend.ErrorSourceDownstream, result.ErrorSource)
|
|
})
|
|
|
|
t.Run("Influxdb response parser parseNumber nil", func(t *testing.T) {
|
|
value := util.ParseNumber(nil)
|
|
require.Nil(t, value)
|
|
})
|
|
|
|
t.Run("Influxdb response parser parseNumber valid JSON.number", func(t *testing.T) {
|
|
value := util.ParseNumber(json.Number("95.4"))
|
|
require.Equal(t, *value, 95.4)
|
|
})
|
|
|
|
t.Run("Influxdb response parser parseNumber invalid type", func(t *testing.T) {
|
|
value := util.ParseNumber("95.4")
|
|
require.Nil(t, value)
|
|
})
|
|
|
|
t.Run("Influxdb response parser with invalid timestamp-format", func(t *testing.T) {
|
|
newField := data.NewField("Value", nil, []*float64{
|
|
util.ToPtr(50.0), util.ToPtr(52.0),
|
|
})
|
|
newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
|
|
testFrame := data.NewFrame("cpu.mean",
|
|
data.NewField("Time", nil,
|
|
[]time.Time{
|
|
time.Date(1970, 1, 1, 0, 0, 0, 100000000, time.UTC),
|
|
time.Date(1970, 1, 1, 0, 0, 0, 102000000, time.UTC),
|
|
}),
|
|
newField,
|
|
)
|
|
testFrame.Meta = &data.FrameMeta{PreferredVisualization: util.GraphVisType, ExecutedQueryString: "Test raw query"}
|
|
|
|
result := ResponseParse(readJsonFile("invalid_timestamp_format"), 200, generateQuery("Test raw query", "time_series", ""))
|
|
|
|
if diff := cmp.Diff(testFrame, result.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
|
|
t.Errorf("Result mismatch (-want +got):\n%s", diff)
|
|
}
|
|
})
|
|
|
|
t.Run("Influxdb response parser parseTimestamp valid number", func(t *testing.T) {
|
|
// currently we use milliseconds-precision with influxdb, so the test works with that.
|
|
// if we change this to for example nanoseconds-precision, the tests will have to change.
|
|
ts := float64(1609556645000)
|
|
timestamp, err := util.ParseTimestamp(ts)
|
|
require.NoError(t, err)
|
|
require.Equal(t, timestamp.Format(time.RFC3339), "2021-01-02T03:04:05Z")
|
|
})
|
|
|
|
t.Run("Influxdb response parser parseNumber invalid type", func(t *testing.T) {
|
|
_, err := util.ParseTimestamp("hello")
|
|
require.Error(t, err)
|
|
})
|
|
}
|