The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
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.
 
 
 
 
 
 
grafana/pkg/tsdb/azuremonitor/applicationinsights-datasou...

384 lines
14 KiB

package azuremonitor
import (
"fmt"
"io/ioutil"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/stretchr/testify/require"
. "github.com/smartystreets/goconvey/convey"
)
func TestApplicationInsightsDatasource(t *testing.T) {
Convey("ApplicationInsightsDatasource", t, func() {
datasource := &ApplicationInsightsDatasource{}
Convey("Parse queries from frontend and build AzureMonitor API queries", func() {
fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
tsdbQuery := &tsdb.TsdbQuery{
TimeRange: &tsdb.TimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
Queries: []*tsdb.Query{
{
DataSource: &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{}),
},
Model: simplejson.NewFromAny(map[string]interface{}{
"appInsights": map[string]interface{}{
"rawQuery": false,
"timeGrain": "PT1M",
"aggregation": "Average",
"metricName": "server/exceptions",
"alias": "testalias",
"queryType": "Application Insights",
},
}),
RefId: "A",
IntervalMs: 1234,
},
},
}
Convey("and is a normal query", func() {
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(len(queries), ShouldEqual, 1)
So(queries[0].RefID, ShouldEqual, "A")
So(queries[0].ApiURL, ShouldEqual, "metrics/server/exceptions")
So(queries[0].Target, ShouldEqual, "aggregation=Average&interval=PT1M&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
So(len(queries[0].Params), ShouldEqual, 3)
So(queries[0].Params["timespan"][0], ShouldEqual, "2018-03-15T13:00:00Z/2018-03-15T13:34:00Z")
So(queries[0].Params["aggregation"][0], ShouldEqual, "Average")
So(queries[0].Params["interval"][0], ShouldEqual, "PT1M")
So(queries[0].Alias, ShouldEqual, "testalias")
})
Convey("and has a time grain set to auto", func() {
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
"appInsights": map[string]interface{}{
"rawQuery": false,
"timeGrain": "auto",
"aggregation": "Average",
"metricName": "Percentage CPU",
"alias": "testalias",
"queryType": "Application Insights",
},
})
tsdbQuery.Queries[0].IntervalMs = 400000
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Params["interval"][0], ShouldEqual, "PT15M")
})
Convey("and has a time grain set to auto and the metric has a limited list of allowed time grains", func() {
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
"appInsights": map[string]interface{}{
"rawQuery": false,
"timeGrain": "auto",
"aggregation": "Average",
"metricName": "Percentage CPU",
"alias": "testalias",
"queryType": "Application Insights",
"allowedTimeGrainsMs": []int64{60000, 300000},
},
})
tsdbQuery.Queries[0].IntervalMs = 400000
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Params["interval"][0], ShouldEqual, "PT5M")
})
Convey("and has a dimension filter", func() {
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
"appInsights": map[string]interface{}{
"rawQuery": false,
"timeGrain": "PT1M",
"aggregation": "Average",
"metricName": "Percentage CPU",
"alias": "testalias",
"queryType": "Application Insights",
"dimension": "blob",
"dimensionFilter": "blob eq '*'",
},
})
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Target, ShouldEqual, "aggregation=Average&filter=blob+eq+%27%2A%27&interval=PT1M&segment=blob&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
So(queries[0].Params["filter"][0], ShouldEqual, "blob eq '*'")
})
Convey("and has a dimension filter set to None", func() {
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
"appInsights": map[string]interface{}{
"rawQuery": false,
"timeGrain": "PT1M",
"aggregation": "Average",
"metricName": "Percentage CPU",
"alias": "testalias",
"queryType": "Application Insights",
"dimension": "None",
},
})
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Target, ShouldEqual, "aggregation=Average&interval=PT1M&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
})
Convey("id a raw query", func() {
tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
"appInsights": map[string]interface{}{
"rawQuery": true,
"rawQueryString": "exceptions | where $__timeFilter(timestamp) | summarize count=count() by bin(timestamp, $__interval)",
"timeColumn": "timestamp",
"valueColumn": "count",
},
})
queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
So(err, ShouldBeNil)
So(queries[0].Params["query"][0], ShouldEqual, "exceptions | where ['timestamp'] >= datetime('2018-03-15T13:00:00Z') and ['timestamp'] <= datetime('2018-03-15T13:34:00Z') | summarize count=count() by bin(timestamp, 1234ms)")
So(queries[0].Target, ShouldEqual, "query=exceptions+%7C+where+%5B%27timestamp%27%5D+%3E%3D+datetime%28%272018-03-15T13%3A00%3A00Z%27%29+and+%5B%27timestamp%27%5D+%3C%3D+datetime%28%272018-03-15T13%3A34%3A00Z%27%29+%7C+summarize+count%3Dcount%28%29+by+bin%28timestamp%2C+1234ms%29")
})
})
Convey("Parse Application Insights query API response in the time series format", func() {
Convey("no segments", func() {
data, err := ioutil.ReadFile("testdata/applicationinsights/1-application-insights-response-raw-query.json")
So(err, ShouldBeNil)
query := &ApplicationInsightsQuery{
IsRaw: true,
TimeColumnName: "timestamp",
ValueColumnName: "value",
}
series, _, err := datasource.parseTimeSeriesFromQuery(data, query)
So(err, ShouldBeNil)
So(len(series), ShouldEqual, 1)
So(series[0].Name, ShouldEqual, "value")
So(len(series[0].Points), ShouldEqual, 2)
So(series[0].Points[0][0].Float64, ShouldEqual, 1)
So(series[0].Points[0][1].Float64, ShouldEqual, int64(1568336523000))
So(series[0].Points[1][0].Float64, ShouldEqual, 2)
So(series[0].Points[1][1].Float64, ShouldEqual, int64(1568340123000))
})
Convey("with segments", func() {
data, err := ioutil.ReadFile("testdata/applicationinsights/2-application-insights-response-raw-query-segmented.json")
So(err, ShouldBeNil)
query := &ApplicationInsightsQuery{
IsRaw: true,
TimeColumnName: "timestamp",
ValueColumnName: "value",
SegmentColumnName: "segment",
}
series, _, err := datasource.parseTimeSeriesFromQuery(data, query)
So(err, ShouldBeNil)
So(len(series), ShouldEqual, 2)
So(series[0].Name, ShouldEqual, "{segment=a}.value")
So(len(series[0].Points), ShouldEqual, 2)
So(series[0].Points[0][0].Float64, ShouldEqual, 1)
So(series[0].Points[0][1].Float64, ShouldEqual, int64(1568336523000))
So(series[0].Points[1][0].Float64, ShouldEqual, 3)
So(series[0].Points[1][1].Float64, ShouldEqual, int64(1568426523000))
So(series[1].Name, ShouldEqual, "{segment=b}.value")
So(series[1].Points[0][0].Float64, ShouldEqual, 2)
So(series[1].Points[0][1].Float64, ShouldEqual, int64(1568336523000))
So(series[1].Points[1][0].Float64, ShouldEqual, 4)
So(series[1].Points[1][1].Float64, ShouldEqual, int64(1568426523000))
Convey("with alias", func() {
data, err := ioutil.ReadFile("testdata/applicationinsights/2-application-insights-response-raw-query-segmented.json")
So(err, ShouldBeNil)
query := &ApplicationInsightsQuery{
IsRaw: true,
TimeColumnName: "timestamp",
ValueColumnName: "value",
SegmentColumnName: "segment",
Alias: "{{metric}} {{dimensionname}} {{dimensionvalue}}",
}
series, _, err := datasource.parseTimeSeriesFromQuery(data, query)
So(err, ShouldBeNil)
So(len(series), ShouldEqual, 2)
So(series[0].Name, ShouldEqual, "value segment a")
So(series[1].Name, ShouldEqual, "value segment b")
})
})
})
Convey("Parse Application Insights metrics API", func() {
Convey("single value", func() {
data, err := ioutil.ReadFile("testdata/applicationinsights/3-application-insights-response-metrics-single-value.json")
So(err, ShouldBeNil)
query := &ApplicationInsightsQuery{
IsRaw: false,
}
series, err := datasource.parseTimeSeriesFromMetrics(data, query)
So(err, ShouldBeNil)
So(len(series), ShouldEqual, 1)
So(series[0].Name, ShouldEqual, "value")
So(len(series[0].Points), ShouldEqual, 1)
So(series[0].Points[0][0].Float64, ShouldEqual, 1.2)
So(series[0].Points[0][1].Float64, ShouldEqual, int64(1568340123000))
})
Convey("1H separation", func() {
data, err := ioutil.ReadFile("testdata/applicationinsights/4-application-insights-response-metrics-no-segment.json")
So(err, ShouldBeNil)
query := &ApplicationInsightsQuery{
IsRaw: false,
}
series, err := datasource.parseTimeSeriesFromMetrics(data, query)
So(err, ShouldBeNil)
So(len(series), ShouldEqual, 1)
So(series[0].Name, ShouldEqual, "value")
So(len(series[0].Points), ShouldEqual, 2)
So(series[0].Points[0][0].Float64, ShouldEqual, 1)
So(series[0].Points[0][1].Float64, ShouldEqual, int64(1568340123000))
So(series[0].Points[1][0].Float64, ShouldEqual, 2)
So(series[0].Points[1][1].Float64, ShouldEqual, int64(1568343723000))
Convey("with segmentation", func() {
data, err := ioutil.ReadFile("testdata/applicationinsights/4-application-insights-response-metrics-segmented.json")
So(err, ShouldBeNil)
query := &ApplicationInsightsQuery{
IsRaw: false,
}
series, err := datasource.parseTimeSeriesFromMetrics(data, query)
So(err, ShouldBeNil)
So(len(series), ShouldEqual, 2)
So(series[0].Name, ShouldEqual, "{blob=a}.value")
So(len(series[0].Points), ShouldEqual, 2)
So(series[0].Points[0][0].Float64, ShouldEqual, 1)
So(series[0].Points[0][1].Float64, ShouldEqual, int64(1568340123000))
So(series[0].Points[1][0].Float64, ShouldEqual, 2)
So(series[0].Points[1][1].Float64, ShouldEqual, int64(1568343723000))
So(series[1].Name, ShouldEqual, "{blob=b}.value")
So(len(series[1].Points), ShouldEqual, 2)
So(series[1].Points[0][0].Float64, ShouldEqual, 3)
So(series[1].Points[0][1].Float64, ShouldEqual, int64(1568340123000))
So(series[1].Points[1][0].Float64, ShouldEqual, 4)
So(series[1].Points[1][1].Float64, ShouldEqual, int64(1568343723000))
Convey("with alias", func() {
data, err := ioutil.ReadFile("testdata/applicationinsights/4-application-insights-response-metrics-segmented.json")
So(err, ShouldBeNil)
query := &ApplicationInsightsQuery{
IsRaw: false,
Alias: "{{metric}} {{dimensionname}} {{dimensionvalue}}",
}
series, err := datasource.parseTimeSeriesFromMetrics(data, query)
So(err, ShouldBeNil)
So(len(series), ShouldEqual, 2)
So(series[0].Name, ShouldEqual, "value blob a")
So(series[1].Name, ShouldEqual, "value blob b")
})
})
})
})
})
}
func TestAppInsightsPluginRoutes(t *testing.T) {
datasource := &ApplicationInsightsDatasource{}
plugin := &plugins.DataSourcePlugin{
Routes: []*plugins.AppPluginRoute{
{
Path: "appinsights",
Method: "GET",
URL: "https://api.applicationinsights.io",
Headers: []plugins.AppPluginRouteHeader{
{Name: "X-API-Key", Content: "{{.SecureJsonData.appInsightsApiKey}}"},
{Name: "x-ms-app", Content: "Grafana"},
},
},
{
Path: "chinaappinsights",
Method: "GET",
URL: "https://api.applicationinsights.azure.cn",
Headers: []plugins.AppPluginRouteHeader{
{Name: "X-API-Key", Content: "{{.SecureJsonData.appInsightsApiKey}}"},
{Name: "x-ms-app", Content: "Grafana"},
},
},
},
}
tests := []struct {
name string
cloudName string
expectedRouteName string
expectedRouteURL string
Err require.ErrorAssertionFunc
}{
{
name: "plugin proxy route for the Azure public cloud",
cloudName: "azuremonitor",
expectedRouteName: "appinsights",
expectedRouteURL: "https://api.applicationinsights.io",
Err: require.NoError,
},
{
name: "plugin proxy route for the Azure China cloud",
cloudName: "chinaazuremonitor",
expectedRouteName: "chinaappinsights",
expectedRouteURL: "https://api.applicationinsights.azure.cn",
Err: require.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
route, routeName, err := datasource.getPluginRoute(plugin, tt.cloudName)
tt.Err(t, err)
if diff := cmp.Diff(tt.expectedRouteURL, route.URL, cmpopts.EquateNaNs()); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(tt.expectedRouteName, routeName, cmpopts.EquateNaNs()); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
}
}