grafana/pkg/tsdb/influxdb/response_parser_test.go
Joey Tawadrous 955b921c88
InfluxDB: new row value types (#44789)
* Check type and act accordingly

* Add string type

* Add bool type

* Change method name in test

* Remove comment

* Changed test var names to represent the float case

* Added string test case

* Added bool test case

* Update response in test

* Change string val

* Fix frame meta missing in tests

* Fixed test parse query

* Fixed out of bounds test

* parseFloatSeries

* parseStringSeries and parseBoolSeries

* Formatting

* Use multi frames for now

* strings.ToLower for time col check

* Move timeField out of if checks
2022-02-18 17:37:45 +00:00

722 lines
22 KiB
Go

package influxdb
import (
"encoding/json"
"io"
"io/ioutil"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xorcare/pointer"
)
func prepare(text string) io.ReadCloser {
return ioutil.NopCloser(strings.NewReader(text))
}
func addQueryToQueries(query Query) []Query {
var queries []Query
query.RefID = "A"
query.RawQuery = "Test raw query"
queries = append(queries, query)
return queries
}
func TestInfluxdbResponseParser(t *testing.T) {
t.Run("Influxdb response parser should handle invalid JSON", func(t *testing.T) {
parser := &ResponseParser{}
response := `{ invalid }`
query := &Query{}
result := parser.Parse(prepare(response), addQueryToQueries(*query))
require.Nil(t, result.Responses["A"].Frames)
require.Error(t, result.Responses["A"].Error)
})
t.Run("Influxdb response parser should parse everything normally", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [
{
"name": "cpu",
"columns": ["time","mean","path","isActive"],
"tags": {"datacenter": "America"},
"values": [
[111,222,"/usr/path",true],
[111,222,"/usr/path",false],
[111,null,"/usr/path",true]
]
}
]
}
]
}
`
query := &Query{}
labels, err := data.LabelsFromString("datacenter=America")
require.Nil(t, err)
floatField := data.NewField("value", labels, []*float64{
pointer.Float64(222), pointer.Float64(222), nil,
})
floatField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean { datacenter: America }"}
floatFrame := data.NewFrame("cpu.mean { datacenter: America }",
data.NewField("time", nil,
[]time.Time{
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
}),
floatField,
)
floatFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
stringField := data.NewField("value", labels, []string{
"/usr/path", "/usr/path", "/usr/path",
})
stringField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.path { datacenter: America }"}
stringFrame := data.NewFrame("cpu.path { datacenter: America }",
data.NewField("time", nil,
[]time.Time{
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
}),
stringField,
)
stringFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
boolField := data.NewField("value", labels, []bool{
true, false, true,
})
boolField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.isActive { datacenter: America }"}
boolFrame := data.NewFrame("cpu.isActive { datacenter: America }",
data.NewField("time", nil,
[]time.Time{
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
}),
boolField,
)
boolFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), addQueryToQueries(*query))
frame := result.Responses["A"]
if diff := cmp.Diff(floatFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(stringFrame, frame.Frames[1], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(boolFrame, frame.Frames[2], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
t.Run("Influxdb response parser should parse metricFindQueries normally", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [
{
"refId": "metricFindQuery",
"name": "cpu",
"values": [
["cpu"],
["disk"],
["logs"]
]
}
]
}
]
}
`
var queries []Query
queries = append(queries, Query{RefID: "metricFindQuery"})
newField := data.NewField("value", nil, []string{
"cpu", "disk", "logs",
})
testFrame := data.NewFrame("cpu",
newField,
)
result := parser.Parse(prepare(response), queries)
frame := result.Responses["metricFindQuery"]
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
t.Run("Influxdb response parser should parse metricFindQueries->SHOW TAG VALUES normally", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [
{
"name": "cpu",
"values": [
["values", "cpu-total"],
["values", "cpu0"],
["values", "cpu1"]
]
}
]
}
]
}
`
var queries []Query
queries = append(queries, Query{RawQuery: "SHOW TAG VALUES", RefID: "metricFindQuery"})
newField := data.NewField("value", nil, []string{
"cpu-total", "cpu0", "cpu1",
})
testFrame := data.NewFrame("cpu",
newField,
)
result := parser.Parse(prepare(response), queries)
frame := result.Responses["metricFindQuery"]
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
t.Run("Influxdb response parser should parse two responses with different refIDs", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [{}]
},
{
"series": [{}]
}
]
}
`
query := &Query{}
var queries = addQueryToQueries(*query)
queryB := &Query{}
queryB.RefID = "B"
queries = append(queries, *queryB)
result := parser.Parse(prepare(response), queries)
assert.Len(t, result.Responses, 2)
assert.Contains(t, result.Responses, "A")
assert.Contains(t, result.Responses, "B")
assert.NotContains(t, result.Responses, "C")
})
t.Run("Influxdb response parser populates the RawQuery in the response meta ExecutedQueryString", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [
{
"name": "cpu",
"columns": ["time","cpu"],
"values": [
["values", "cpu-total"],
["values", "cpu0"],
["values", "cpu1"]
]
}
]
}
]
}
`
query := &Query{}
query.RawQuery = "Test raw query"
result := parser.Parse(prepare(response), addQueryToQueries(*query))
frame := result.Responses["A"]
assert.Equal(t, frame.Frames[0].Meta.ExecutedQueryString, "Test raw query")
})
t.Run("Influxdb response parser with invalid value-format", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [
{
"name": "cpu",
"columns": ["time","mean"],
"values": [
[100,50],
[101,"hello"],
[102,52]
]
}
]
}
]
}
`
query := &Query{}
newField := data.NewField("value", nil, []*float64{
pointer.Float64(50), nil, pointer.Float64(52),
})
newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
testFrame := data.NewFrame("cpu.mean",
data.NewField("time", nil,
[]time.Time{
time.Date(1970, 1, 1, 0, 1, 40, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 41, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 42, 0, time.UTC),
}),
newField,
)
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), addQueryToQueries(*query))
frame := result.Responses["A"]
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
t.Run("Influxdb response parser with invalid timestamp-format", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [
{
"name": "cpu",
"columns": ["time","mean"],
"values": [
[100,50],
["hello",51],
["hello","hello"],
[102,52]
]
}
]
}
]
}
`
query := &Query{}
newField := data.NewField("value", nil, []*float64{
pointer.Float64(50), pointer.Float64(52),
})
newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
testFrame := data.NewFrame("cpu.mean",
data.NewField("time", nil,
[]time.Time{
time.Date(1970, 1, 1, 0, 1, 40, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 42, 0, time.UTC),
}),
newField,
)
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), addQueryToQueries(*query))
frame := result.Responses["A"]
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
t.Run("Influxdb response parser with alias", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [
{
"name": "cpu.upc",
"columns": ["time","mean","sum"],
"tags": {
"datacenter": "America",
"dc.region.name": "Northeast",
"cluster-name": "Cluster",
"/cluster/name/": "Cluster/",
"@cluster@name@": "Cluster@"
},
"values": [
[111,222,333]
]
}
]
}
]
}
`
query := &Query{Alias: "series alias"}
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{
pointer.Float64(222),
})
newField.Config = &data.FieldConfig{DisplayNameFromDS: "series alias"}
testFrame := data.NewFrame("series alias",
data.NewField("time", nil,
[]time.Time{
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
}),
newField,
)
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), addQueryToQueries(*query))
t.Run("should parse aliases", func(t *testing.T) {
frame := result.Responses["A"]
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias $m $measurement", Measurement: "10m"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name := "alias 10m 10m"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias $col", Measurement: "10m"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias mean"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
name = "alias sum"
testFrame.Name = name
newField = data.NewField("value", labels, []*float64{
pointer.Float64(333),
})
testFrame.Fields[1] = newField
testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
if diff := cmp.Diff(testFrame, frame.Frames[1], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias $tag_datacenter"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias America"
testFrame.Name = name
newField = data.NewField("value", labels, []*float64{
pointer.Float64(222),
})
testFrame.Fields[1] = newField
testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias $tag_datacenter/$tag_datacenter"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias America/America"
testFrame.Name = name
newField = data.NewField("value", labels, []*float64{
pointer.Float64(222),
})
testFrame.Fields[1] = newField
testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias [[col]]", Measurement: "10m"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias mean"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias $0 $1 $2 $3 $4"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias cpu upc $2 $3 $4"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias $1"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias upc"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias $5"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias $5"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "series alias"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "series alias"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias [[m]] [[measurement]]", Measurement: "10m"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias 10m 10m"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias [[tag_datacenter]]"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias America"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias [[tag_dc.region.name]]"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias Northeast"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias [[tag_cluster-name]]"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias Cluster"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias [[tag_/cluster/name/]]"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias Cluster/"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias [[tag_@cluster@name@]]"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias Cluster@"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
t.Run("shouldn't parse aliases", func(t *testing.T) {
query = &Query{Alias: "alias words with no brackets"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame := result.Responses["A"]
name := "alias words with no brackets"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias Test 1.5"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias Test 1.5"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
query = &Query{Alias: "alias Test -1"}
result = parser.Parse(prepare(response), addQueryToQueries(*query))
frame = result.Responses["A"]
name = "alias Test -1"
testFrame.Name = name
testFrame.Fields[1].Config.DisplayNameFromDS = name
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
})
t.Run("Influxdb response parser with errors", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"results": [
{
"series": [
{
"name": "cpu",
"columns": ["time","mean","sum"],
"tags": {"datacenter": "America"},
"values": [
[111,222,333],
[111,222,333],
[111,null,333]
]
}
]
},
{
"error": "query-timeout limit exceeded"
}
]
}
`
query := &Query{}
var queries = addQueryToQueries(*query)
queryB := &Query{}
queryB.RefID = "B"
queries = append(queries, *queryB)
labels, err := data.LabelsFromString("datacenter=America")
require.Nil(t, err)
newField := data.NewField("value", labels, []*float64{
pointer.Float64(222), pointer.Float64(222), nil,
})
newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean { datacenter: America }"}
testFrame := data.NewFrame("cpu.mean { datacenter: America }",
data.NewField("time", nil,
[]time.Time{
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
time.Date(1970, 1, 1, 0, 1, 51, 0, time.UTC),
}),
newField,
)
testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
result := parser.Parse(prepare(response), queries)
frame := result.Responses["A"]
if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
require.EqualError(t, result.Responses["B"].Error, "query-timeout limit exceeded")
})
t.Run("Influxdb response parser with top-level error", func(t *testing.T) {
parser := &ResponseParser{}
response := `
{
"error": "error parsing query: found THING"
}
`
query := &Query{}
result := parser.Parse(prepare(response), addQueryToQueries(*query))
require.Nil(t, result.Responses["A"].Frames)
require.EqualError(t, result.Responses["A"].Error, "error parsing query: found THING")
})
t.Run("Influxdb response parser parseNumber nil", func(t *testing.T) {
value := parseNumber(nil)
require.Nil(t, value)
})
t.Run("Influxdb response parser parseNumber valid JSON.number", func(t *testing.T) {
value := parseNumber(json.Number("95.4"))
require.Equal(t, *value, 95.4)
})
t.Run("Influxdb response parser parseNumber invalid type", func(t *testing.T) {
value := parseNumber("95.4")
require.Nil(t, value)
})
t.Run("Influxdb response parser parseTimestamp valid JSON.number", func(t *testing.T) {
// currently we use seconds-precision with influxdb, so the test works with that.
// if we change this to for example milliseconds-precision, the tests will have to change.
timestamp, err := parseTimestamp(json.Number("1609556645"))
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 := parseTimestamp("hello")
require.Error(t, err)
})
}