grafana/pkg/tsdb/influxdb/response_parser_test.go

397 lines
9.5 KiB
Go

package influxdb
import (
"encoding/json"
"io"
"io/ioutil"
"strings"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/require"
)
func prepare(text string) io.ReadCloser {
return ioutil.NopCloser(strings.NewReader(text))
}
// nolint:staticcheck // plugins.DataQueryResult deprecated
func decodedFrames(t *testing.T, result plugins.DataQueryResult) data.Frames {
decoded, err := result.Dataframes.Decoded()
require.NoError(t, err)
return decoded
}
// nolint:staticcheck // plugins.DataQueryResult deprecated
func assertSeriesName(t *testing.T, result plugins.DataQueryResult, index int, name string) {
decoded := decodedFrames(t, result)
frame := decoded[index]
require.Equal(t, frame.Name, name)
// the current version of the alerting-code does not use the dataframe-name
// when generating the metric-names for the alerts.
// instead, it goes through multiple attributes on the Field.
// we use the `field.Config.DisplayNameFromDS` attribute.
valueFieldConfig := frame.Fields[1].Config
require.NotNil(t, valueFieldConfig)
require.Equal(t, valueFieldConfig.DisplayNameFromDS, name)
}
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), query)
require.Nil(t, result.Dataframes)
require.Error(t, result.Error)
})
t.Run("Influxdb response parser should parse everything normally", 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]
]
}
]
}
]
}
`
query := &Query{}
result := parser.Parse(prepare(response), query)
decoded := decodedFrames(t, result)
require.Len(t, decoded, 2)
frame1 := decoded[0]
frame2 := decoded[1]
assertSeriesName(t, result, 0, "cpu.mean { datacenter: America }")
assertSeriesName(t, result, 1, "cpu.sum { datacenter: America }")
require.Len(t, frame1.Fields, 2)
require.Len(t, frame2.Fields, 2)
require.Equal(t, frame1.Fields[0].Len(), 3)
require.Equal(t, frame1.Fields[1].Len(), 3)
require.Equal(t, frame2.Fields[0].Len(), 3)
require.Equal(t, frame2.Fields[1].Len(), 3)
require.Equal(t, *frame1.Fields[1].At(1).(*float64), 222.0)
require.Equal(t, *frame2.Fields[1].At(1).(*float64), 333.0)
require.Nil(t, frame1.Fields[1].At(2))
})
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{}
result := parser.Parse(prepare(response), query)
// the current behavior is that we do not report an error, we turn the invalid value into `nil`
require.Nil(t, result.Error)
require.Equal(t, result.ErrorString, "")
decoded := decodedFrames(t, result)
require.Len(t, decoded, 1)
frame := decoded[0]
require.Len(t, frame.Fields, 2)
field1 := frame.Fields[0]
field2 := frame.Fields[1]
require.Equal(t, field1.Len(), 3)
require.Equal(t, field2.Len(), 3)
require.Equal(t, *field2.At(0).(*float64), 50.0)
require.Nil(t, field2.At(1))
require.Equal(t, *field2.At(2).(*float64), 52.0)
})
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{}
result := parser.Parse(prepare(response), query)
// the current behavior is that we do not report an error, we skip the item with the invalid timestamp
require.Nil(t, result.Error)
require.Equal(t, result.ErrorString, "")
decoded := decodedFrames(t, result)
require.Len(t, decoded, 1)
frame := decoded[0]
require.Len(t, frame.Fields, 2)
field1 := frame.Fields[0]
field2 := frame.Fields[1]
require.Equal(t, field1.Len(), 2)
require.Equal(t, field2.Len(), 2)
require.Equal(t, *field2.At(0).(*float64), 50.0)
require.Equal(t, *field2.At(1).(*float64), 52.0)
})
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"}
result := parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "series alias")
query = &Query{Alias: "alias $m $measurement", Measurement: "10m"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias 10m 10m")
query = &Query{Alias: "alias $col", Measurement: "10m"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias mean")
assertSeriesName(t, result, 1, "alias sum")
query = &Query{Alias: "alias $tag_datacenter"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias America")
query = &Query{Alias: "alias $1"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias upc")
query = &Query{Alias: "alias $5"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias $5")
query = &Query{Alias: "series alias"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "series alias")
query = &Query{Alias: "alias [[m]] [[measurement]]", Measurement: "10m"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias 10m 10m")
query = &Query{Alias: "alias [[col]]", Measurement: "10m"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias mean")
assertSeriesName(t, result, 1, "alias sum")
query = &Query{Alias: "alias [[tag_datacenter]]"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias America")
query = &Query{Alias: "alias [[tag_dc.region.name]]"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias Northeast")
query = &Query{Alias: "alias [[tag_cluster-name]]"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias Cluster")
query = &Query{Alias: "alias [[tag_/cluster/name/]]"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias Cluster/")
query = &Query{Alias: "alias [[tag_@cluster@name@]]"}
result = parser.Parse(prepare(response), query)
assertSeriesName(t, result, 0, "alias Cluster@")
})
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{}
result := parser.Parse(prepare(response), query)
decoded := decodedFrames(t, result)
require.Len(t, decoded, 2)
require.Equal(t, decoded[0].Fields[0].Len(), 3)
require.Equal(t, decoded[0].Fields[1].Len(), 3)
require.Equal(t, decoded[1].Fields[0].Len(), 3)
require.Equal(t, decoded[1].Fields[1].Len(), 3)
require.EqualError(t, result.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), query)
require.Nil(t, result.Dataframes)
require.EqualError(t, result.Error, "error parsing query: found THING")
})
t.Run("Influxdb response parser parseValue nil", func(t *testing.T) {
value := parseValue(nil)
require.Nil(t, value)
})
t.Run("Influxdb response parser parseValue valid JSON.number", func(t *testing.T) {
value := parseValue(json.Number("95.4"))
require.Equal(t, *value, 95.4)
})
t.Run("Influxdb response parser parseValue invalid type", func(t *testing.T) {
value := parseValue("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 parseValue invalid type", func(t *testing.T) {
_, err := parseTimestamp("hello")
require.Error(t, err)
})
}