CloudWatch: Pass label in GetMetricData API request when dynamic label feature toggle is enabled (#48574)

pull/48869/head
Shirley 3 years ago committed by GitHub
parent 610247d52a
commit b35ca8c08d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      pkg/tsdb/cloudwatch/cloudwatch_query.go
  2. 5
      pkg/tsdb/cloudwatch/metric_data_query_builder.go
  3. 39
      pkg/tsdb/cloudwatch/metric_data_query_builder_test.go
  4. 2
      pkg/tsdb/cloudwatch/request_parser.go
  5. 12
      pkg/tsdb/cloudwatch/request_parser_test.go
  6. 15
      pkg/tsdb/cloudwatch/response_parser.go
  7. 33
      pkg/tsdb/cloudwatch/response_parser_test.go
  8. 107
      pkg/tsdb/cloudwatch/time_series_query_test.go
  9. 9
      pkg/tsdb/cloudwatch/utils_test.go

@ -21,6 +21,7 @@ type cloudWatchQuery struct {
Dimensions map[string][]string Dimensions map[string][]string
Period int Period int
Alias string Alias string
Label string
MatchExact bool MatchExact bool
UsedExpression string UsedExpression string
MetricQueryType metricQueryType MetricQueryType metricQueryType

@ -8,6 +8,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/grafana/grafana/pkg/services/featuremgmt"
) )
func (e *cloudWatchExecutor) buildMetricDataQuery(query *cloudWatchQuery) (*cloudwatch.MetricDataQuery, error) { func (e *cloudWatchExecutor) buildMetricDataQuery(query *cloudWatchQuery) (*cloudwatch.MetricDataQuery, error) {
@ -16,6 +17,10 @@ func (e *cloudWatchExecutor) buildMetricDataQuery(query *cloudWatchQuery) (*clou
ReturnData: aws.Bool(query.ReturnData), ReturnData: aws.Bool(query.ReturnData),
} }
if e.features.IsEnabled(featuremgmt.FlagCloudWatchDynamicLabels) && len(query.Label) > 0 {
mdq.Label = &query.Label
}
switch query.getGMDAPIMode() { switch query.getGMDAPIMode() {
case GMDApiModeMathExpression: case GMDApiModeMathExpression:
mdq.Period = aws.Int64(int64(query.Period)) mdq.Period = aws.Int64(int64(query.Period))

@ -71,6 +71,45 @@ func TestMetricDataQueryBuilder(t *testing.T) {
assert.Equal(t, int64(300), *mdq.Period) assert.Equal(t, int64(300), *mdq.Period)
assert.Equal(t, `SUM([a,b])`, *mdq.Expression) assert.Equal(t, `SUM([a,b])`, *mdq.Expression)
}) })
t.Run("should set label when dynamic labels feature toggle is enabled", func(t *testing.T) {
executor := newExecutor(nil, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures(featuremgmt.FlagCloudWatchDynamicLabels))
query := getBaseQuery()
query.Label = "some label"
mdq, err := executor.buildMetricDataQuery(query)
assert.NoError(t, err)
require.NotNil(t, mdq.Label)
assert.Equal(t, "some label", *mdq.Label)
})
testCases := map[string]struct {
feature *featuremgmt.FeatureManager
label string
}{
"should not set label when dynamic labels feature toggle is disabled": {
feature: featuremgmt.WithFeatures(),
label: "some label",
},
"should not set label for empty string query label": {
feature: featuremgmt.WithFeatures(featuremgmt.FlagCloudWatchDynamicLabels),
label: "",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
executor := newExecutor(nil, newTestConfig(), &fakeSessionCache{}, tc.feature)
query := getBaseQuery()
query.Label = tc.label
mdq, err := executor.buildMetricDataQuery(query)
assert.NoError(t, err)
assert.Nil(t, mdq.Label)
})
}
}) })
t.Run("Query should be matched exact", func(t *testing.T) { t.Run("Query should be matched exact", func(t *testing.T) {

@ -198,6 +198,7 @@ func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time
expression := model.Get("expression").MustString("") expression := model.Get("expression").MustString("")
sqlExpression := model.Get("sqlExpression").MustString("") sqlExpression := model.Get("sqlExpression").MustString("")
alias := model.Get("alias").MustString() alias := model.Get("alias").MustString()
label := model.Get("label").MustString()
returnData := !model.Get("hide").MustBool(false) returnData := !model.Get("hide").MustBool(false)
queryType := model.Get("type").MustString() queryType := model.Get("type").MustString()
if queryType == "" { if queryType == "" {
@ -231,6 +232,7 @@ func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time
Dimensions: dimensions, Dimensions: dimensions,
Period: period, Period: period,
Alias: alias, Alias: alias,
Label: label,
MatchExact: matchExact, MatchExact: matchExact,
UsedExpression: "", UsedExpression: "",
MetricQueryType: metricQueryType, MetricQueryType: metricQueryType,

@ -311,6 +311,18 @@ func TestRequestParser(t *testing.T) {
assert.Equal(t, "$$", res.RefId) assert.Equal(t, "$$", res.RefId)
assert.Regexp(t, validMetricDataID, res.Id) assert.Regexp(t, validMetricDataID, res.Id)
}) })
t.Run("parseRequestQuery sets label when label is present in json query", func(t *testing.T) {
query := getBaseJsonQuery()
query.Set("alias", "some alias")
query.Set("label", "some label")
res, err := parseRequestQuery(query, "ref1", time.Now().Add(-2*time.Hour), time.Now().Add(-time.Hour))
assert.NoError(t, err)
assert.Equal(t, "some alias", res.Alias) // alias is unmodified
assert.Equal(t, "some label", res.Label)
})
} }
func getBaseJsonQuery() *simplejson.Json { func getBaseJsonQuery() *simplejson.Json {

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/featuremgmt"
) )
func (e *cloudWatchExecutor) parseResponse(startTime time.Time, endTime time.Time, metricDataOutputs []*cloudwatch.GetMetricDataOutput, func (e *cloudWatchExecutor) parseResponse(startTime time.Time, endTime time.Time, metricDataOutputs []*cloudwatch.GetMetricDataOutput,
@ -31,7 +32,7 @@ func (e *cloudWatchExecutor) parseResponse(startTime time.Time, endTime time.Tim
} }
var err error var err error
dataRes.Frames, err = buildDataFrames(startTime, endTime, response, queryRow) dataRes.Frames, err = buildDataFrames(startTime, endTime, response, queryRow, e.features.IsEnabled(featuremgmt.FlagCloudWatchDynamicLabels))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -117,7 +118,7 @@ func getLabels(cloudwatchLabel string, query *cloudWatchQuery) data.Labels {
} }
func buildDataFrames(startTime time.Time, endTime time.Time, aggregatedResponse queryRowResponse, func buildDataFrames(startTime time.Time, endTime time.Time, aggregatedResponse queryRowResponse,
query *cloudWatchQuery) (data.Frames, error) { query *cloudWatchQuery, dynamicLabelEnabled bool) (data.Frames, error) {
frames := data.Frames{} frames := data.Frames{}
for _, label := range aggregatedResponse.Labels { for _, label := range aggregatedResponse.Labels {
metric := aggregatedResponse.Metrics[label] metric := aggregatedResponse.Metrics[label]
@ -150,7 +151,10 @@ func buildDataFrames(startTime time.Time, endTime time.Time, aggregatedResponse
timeField := data.NewField(data.TimeSeriesTimeFieldName, nil, []*time.Time{}) timeField := data.NewField(data.TimeSeriesTimeFieldName, nil, []*time.Time{})
valueField := data.NewField(data.TimeSeriesValueFieldName, labels, []*float64{}) valueField := data.NewField(data.TimeSeriesValueFieldName, labels, []*float64{})
frameName := formatAlias(query, query.Statistic, labels, label) frameName := label
if !dynamicLabelEnabled {
frameName = formatAlias(query, query.Statistic, labels, label)
}
valueField.SetConfig(&data.FieldConfig{DisplayNameFromDS: frameName, Links: createDataLinks(deepLink)}) valueField.SetConfig(&data.FieldConfig{DisplayNameFromDS: frameName, Links: createDataLinks(deepLink)})
emptyFrame := data.Frame{ emptyFrame := data.Frame{
@ -179,7 +183,10 @@ func buildDataFrames(startTime time.Time, endTime time.Time, aggregatedResponse
timeField := data.NewField(data.TimeSeriesTimeFieldName, nil, timestamps) timeField := data.NewField(data.TimeSeriesTimeFieldName, nil, timestamps)
valueField := data.NewField(data.TimeSeriesValueFieldName, labels, points) valueField := data.NewField(data.TimeSeriesValueFieldName, labels, points)
frameName := formatAlias(query, query.Statistic, labels, label) frameName := label
if !dynamicLabelEnabled {
frameName = formatAlias(query, query.Statistic, labels, label)
}
valueField.SetConfig(&data.FieldConfig{DisplayNameFromDS: frameName, Links: createDataLinks(deepLink)}) valueField.SetConfig(&data.FieldConfig{DisplayNameFromDS: frameName, Links: createDataLinks(deepLink)})
frame := data.Frame{ frame := data.Frame{

@ -143,7 +143,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
MetricQueryType: MetricQueryTypeSearch, MetricQueryType: MetricQueryTypeSearch,
MetricEditorMode: MetricEditorModeBuilder, MetricEditorMode: MetricEditorModeBuilder,
} }
frames, err := buildDataFrames(startTime, endTime, *response, query) frames, err := buildDataFrames(startTime, endTime, *response, query, false)
require.NoError(t, err) require.NoError(t, err)
frame1 := frames[0] frame1 := frames[0]
@ -207,7 +207,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
MetricQueryType: MetricQueryTypeSearch, MetricQueryType: MetricQueryTypeSearch,
MetricEditorMode: MetricEditorModeBuilder, MetricEditorMode: MetricEditorModeBuilder,
} }
frames, err := buildDataFrames(startTime, endTime, *response, query) frames, err := buildDataFrames(startTime, endTime, *response, query, false)
require.NoError(t, err) require.NoError(t, err)
frame1 := frames[0] frame1 := frames[0]
@ -272,7 +272,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
MetricQueryType: MetricQueryTypeSearch, MetricQueryType: MetricQueryTypeSearch,
MetricEditorMode: MetricEditorModeBuilder, MetricEditorMode: MetricEditorModeBuilder,
} }
frames, err := buildDataFrames(startTime, endTime, *response, query) frames, err := buildDataFrames(startTime, endTime, *response, query, false)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "lb3 Expanded", frames[0].Name) assert.Equal(t, "lb3 Expanded", frames[0].Name)
@ -311,7 +311,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
MetricQueryType: MetricQueryTypeSearch, MetricQueryType: MetricQueryTypeSearch,
MetricEditorMode: MetricEditorModeBuilder, MetricEditorMode: MetricEditorModeBuilder,
} }
frames, err := buildDataFrames(startTime, endTime, *response, query) frames, err := buildDataFrames(startTime, endTime, *response, query, false)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, frames, 2) assert.Len(t, frames, 2)
@ -354,7 +354,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
MetricQueryType: MetricQueryTypeSearch, MetricQueryType: MetricQueryTypeSearch,
MetricEditorMode: MetricEditorModeBuilder, MetricEditorMode: MetricEditorModeBuilder,
} }
frames, err := buildDataFrames(startTime, endTime, *response, query) frames, err := buildDataFrames(startTime, endTime, *response, query, false)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, frames, 2) assert.Len(t, frames, 2)
@ -395,7 +395,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
MetricQueryType: MetricQueryTypeQuery, MetricQueryType: MetricQueryTypeQuery,
MetricEditorMode: MetricEditorModeRaw, MetricEditorMode: MetricEditorModeRaw,
} }
frames, err := buildDataFrames(startTime, endTime, *response, query) frames, err := buildDataFrames(startTime, endTime, *response, query, false)
require.NoError(t, err) require.NoError(t, err)
assert.False(t, strings.Contains(frames[0].Name, "AWS/ApplicationELB")) assert.False(t, strings.Contains(frames[0].Name, "AWS/ApplicationELB"))
@ -445,7 +445,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
MetricQueryType: MetricQueryTypeSearch, MetricQueryType: MetricQueryTypeSearch,
MetricEditorMode: MetricEditorModeBuilder, MetricEditorMode: MetricEditorModeBuilder,
} }
frames, err := buildDataFrames(startTime, endTime, *response, query) frames, err := buildDataFrames(startTime, endTime, *response, query, false)
require.NoError(t, err) require.NoError(t, err)
frame := frames[0] frame := frames[0]
@ -458,4 +458,23 @@ func TestCloudWatchResponseParser(t *testing.T) {
assert.Equal(t, "Value", frame.Fields[1].Name) assert.Equal(t, "Value", frame.Fields[1].Name)
assert.Equal(t, "", frame.Fields[1].Config.DisplayName) assert.Equal(t, "", frame.Fields[1].Config.DisplayName)
}) })
t.Run("buildDataFrames should use response label as frame name when dynamic label is enabled", func(t *testing.T) {
response := &queryRowResponse{
Labels: []string{"some response label"},
Metrics: map[string]*cloudwatch.MetricDataResult{
"some response label": {
Timestamps: []*time.Time{},
Values: []*float64{aws.Float64(10)},
StatusCode: aws.String("Complete"),
},
},
}
frames, err := buildDataFrames(startTime, endTime, *response, &cloudWatchQuery{}, true)
assert.NoError(t, err)
require.Len(t, frames, 1)
assert.Equal(t, "some response label", frames[0].Name)
})
} }

@ -148,6 +148,7 @@ type queryParameters struct {
Dimensions queryDimensions `json:"dimensions"` Dimensions queryDimensions `json:"dimensions"`
Expression string `json:"expression"` Expression string `json:"expression"`
Alias string `json:"alias"` Alias string `json:"alias"`
Label *string `json:"label"`
Statistic string `json:"statistic"` Statistic string `json:"statistic"`
Period string `json:"period"` Period string `json:"period"`
MatchExact bool `json:"matchExact"` MatchExact bool `json:"matchExact"`
@ -168,14 +169,15 @@ func newTestQuery(t testing.TB, p queryParameters) json.RawMessage {
Dimensions struct { Dimensions struct {
InstanceID []string `json:"InstanceId,omitempty"` InstanceID []string `json:"InstanceId,omitempty"`
} `json:"dimensions"` } `json:"dimensions"`
Expression string `json:"expression"` Expression string `json:"expression"`
Region string `json:"region"` Region string `json:"region"`
ID string `json:"id"` ID string `json:"id"`
Alias string `json:"alias"` Alias string `json:"alias"`
Statistic string `json:"statistic"` Label *string `json:"label"`
Period string `json:"period"` Statistic string `json:"statistic"`
MatchExact bool `json:"matchExact"` Period string `json:"period"`
RefID string `json:"refId"` MatchExact bool `json:"matchExact"`
RefID string `json:"refId"`
}{ }{
Type: "timeSeriesQuery", Type: "timeSeriesQuery",
Region: "us-east-2", Region: "us-east-2",
@ -188,6 +190,7 @@ func newTestQuery(t testing.TB, p queryParameters) json.RawMessage {
Dimensions: p.Dimensions, Dimensions: p.Dimensions,
Expression: p.Expression, Expression: p.Expression,
Alias: p.Alias, Alias: p.Alias,
Label: p.Label,
Statistic: p.Statistic, Statistic: p.Statistic,
Period: p.Period, Period: p.Period,
MetricName: p.MetricName, MetricName: p.MetricName,
@ -199,6 +202,94 @@ func newTestQuery(t testing.TB, p queryParameters) json.RawMessage {
return marshalled return marshalled
} }
func Test_QueryData_timeSeriesQuery_GetMetricDataWithContext(t *testing.T) {
origNewCWClient := NewCWClient
t.Cleanup(func() {
NewCWClient = origNewCWClient
})
var cwClient fakeCWClient
NewCWClient = func(sess *session.Session) cloudwatchiface.CloudWatchAPI {
return &cwClient
}
im := datasource.NewInstanceManager(func(s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
return datasourceInfo{}, nil
})
t.Run("passes query label as GetMetricData label when dynamic labels feature toggle is enabled", func(t *testing.T) {
cwClient = fakeCWClient{}
executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures(featuremgmt.FlagCloudWatchDynamicLabels))
query := newTestQuery(t, queryParameters{
Label: aws.String("${PROP('Period')} some words ${PROP('Dim.InstanceId')}"),
})
_, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{
PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}},
Queries: []backend.DataQuery{
{
RefID: "A",
TimeRange: backend.TimeRange{
From: time.Now().Add(time.Hour * -2),
To: time.Now().Add(time.Hour * -1),
},
JSON: query,
},
},
})
assert.NoError(t, err)
require.Len(t, cwClient.callsGetMetricDataWithContext, 1)
require.Len(t, cwClient.callsGetMetricDataWithContext[0].MetricDataQueries, 1)
require.NotNil(t, cwClient.callsGetMetricDataWithContext[0].MetricDataQueries[0].Label)
assert.Equal(t, "${PROP('Period')} some words ${PROP('Dim.InstanceId')}", *cwClient.callsGetMetricDataWithContext[0].MetricDataQueries[0].Label)
})
testCases := map[string]struct {
feature *featuremgmt.FeatureManager
parameters queryParameters
}{
"should not pass GetMetricData label when query label is empty, dynamic labels is enabled": {
feature: featuremgmt.WithFeatures(featuremgmt.FlagCloudWatchDynamicLabels),
},
"should not pass GetMetricData label when query label is empty string, dynamic labels is enabled": {
feature: featuremgmt.WithFeatures(featuremgmt.FlagCloudWatchDynamicLabels),
parameters: queryParameters{Label: aws.String("")},
},
"should not pass GetMetricData label when dynamic labels is disabled": {
feature: featuremgmt.WithFeatures(),
parameters: queryParameters{Label: aws.String("${PROP('Period')} some words ${PROP('Dim.InstanceId')}")},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
cwClient = fakeCWClient{}
executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, tc.feature)
_, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{
PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}},
Queries: []backend.DataQuery{
{
RefID: "A",
TimeRange: backend.TimeRange{
From: time.Now().Add(time.Hour * -2),
To: time.Now().Add(time.Hour * -1),
},
JSON: newTestQuery(t, tc.parameters),
},
},
})
assert.NoError(t, err)
require.Len(t, cwClient.callsGetMetricDataWithContext, 1)
require.Len(t, cwClient.callsGetMetricDataWithContext[0].MetricDataQueries, 1)
assert.Nil(t, cwClient.callsGetMetricDataWithContext[0].MetricDataQueries[0].Label)
})
}
}
func Test_QueryData_response_data_frame_names(t *testing.T) { func Test_QueryData_response_data_frame_names(t *testing.T) {
origNewCWClient := NewCWClient origNewCWClient := NewCWClient
t.Cleanup(func() { t.Cleanup(func() {

@ -62,12 +62,15 @@ type fakeCWClient struct {
cloudwatchiface.CloudWatchAPI cloudwatchiface.CloudWatchAPI
cloudwatch.GetMetricDataOutput cloudwatch.GetMetricDataOutput
Metrics []*cloudwatch.Metric Metrics []*cloudwatch.Metric
MetricsPerPage int MetricsPerPage int
callsGetMetricDataWithContext []*cloudwatch.GetMetricDataInput
} }
func (c *fakeCWClient) GetMetricDataWithContext(aws.Context, *cloudwatch.GetMetricDataInput, ...request.Option) (*cloudwatch.GetMetricDataOutput, error) { func (c *fakeCWClient) GetMetricDataWithContext(ctx aws.Context, input *cloudwatch.GetMetricDataInput, opts ...request.Option) (*cloudwatch.GetMetricDataOutput, error) {
c.callsGetMetricDataWithContext = append(c.callsGetMetricDataWithContext, input)
return &c.GetMetricDataOutput, nil return &c.GetMetricDataOutput, nil
} }

Loading…
Cancel
Save