CloudWatch: Use generated type from schema in backend (#66420)

* CloudWatch: Use generated type from schema in backend

* address comments

* don't explicity set default metric query type
This commit is contained in:
Kevin Yu 2023-04-18 11:56:00 -07:00 committed by GitHub
parent 255d8f3326
commit a21fdd9c81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 126 additions and 118 deletions

View File

@ -28,15 +28,20 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont
} }
var period int64 var period int64
if model.Period != "" {
p, err := strconv.ParseInt(model.Period, 10, 64) if model.Period != nil && *model.Period != "" {
p, err := strconv.ParseInt(*model.Period, 10, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
period = p period = p
} }
if period == 0 && !model.PrefixMatching { prefixMatching := false
if model.PrefixMatching != nil {
prefixMatching = *model.PrefixMatching
}
if period == 0 && !prefixMatching {
period = 300 period = 300
} }
@ -49,19 +54,23 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont
} }
var alarmNames []*string var alarmNames []*string
if model.PrefixMatching { metricName := ""
if model.MetricName != nil {
metricName = *model.MetricName
}
if prefixMatching {
params := &cloudwatch.DescribeAlarmsInput{ params := &cloudwatch.DescribeAlarmsInput{
MaxRecords: aws.Int64(100), MaxRecords: aws.Int64(100),
ActionPrefix: aws.String(actionPrefix), ActionPrefix: actionPrefix,
AlarmNamePrefix: aws.String(alarmNamePrefix), AlarmNamePrefix: alarmNamePrefix,
} }
resp, err := cli.DescribeAlarms(params) resp, err := cli.DescribeAlarms(params)
if err != nil { if err != nil {
return nil, fmt.Errorf("%v: %w", "failed to call cloudwatch:DescribeAlarms", err) return nil, fmt.Errorf("%v: %w", "failed to call cloudwatch:DescribeAlarms", err)
} }
alarmNames = filterAlarms(resp, model.Namespace, model.MetricName, model.Dimensions, statistic, period) alarmNames = filterAlarms(resp, model.Namespace, metricName, model.Dimensions, statistic, period)
} else { } else {
if model.Region == "" || model.Namespace == "" || model.MetricName == "" || statistic == "" { if model.Region == "" || model.Namespace == "" || metricName == "" || statistic == "" {
return result, errors.New("invalid annotations query") return result, errors.New("invalid annotations query")
} }
@ -80,7 +89,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont
} }
params := &cloudwatch.DescribeAlarmsForMetricInput{ params := &cloudwatch.DescribeAlarmsForMetricInput{
Namespace: aws.String(model.Namespace), Namespace: aws.String(model.Namespace),
MetricName: aws.String(model.MetricName), MetricName: aws.String(metricName),
Dimensions: qd, Dimensions: qd,
Statistic: aws.String(statistic), Statistic: aws.String(statistic),
Period: aws.Int64(period), Period: aws.Int64(period),

View File

@ -27,21 +27,13 @@ import (
"github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/query"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/clients" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/clients"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
) )
type DataQueryJson struct { type DataQueryJson struct {
QueryType string `json:"type,omitempty"` dataquery.CloudWatchAnnotationQuery
QueryMode string Type string `json:"type,omitempty"`
PrefixMatching bool
Region string
Namespace string
MetricName string
Dimensions map[string]interface{}
Statistic *string
Period string
ActionPrefix string
AlarmNamePrefix string
} }
type DataSource struct { type DataSource struct {
@ -179,7 +171,7 @@ func (e *cloudWatchExecutor) QueryData(ctx context.Context, req *backend.QueryDa
} }
var result *backend.QueryDataResponse var result *backend.QueryDataResponse
switch model.QueryType { switch model.Type {
case annotationQuery: case annotationQuery:
result, err = e.executeAnnotationQuery(req.PluginContext, model, q) result, err = e.executeAnnotationQuery(req.PluginContext, model, q)
case logAction: case logAction:

View File

@ -112,7 +112,7 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, logger log.Lo
} }
var data *data.Frame = nil var data *data.Frame = nil
switch logsQuery.SubType { switch logsQuery.Subtype {
case "StartQuery": case "StartQuery":
data, err = e.handleStartQuery(ctx, logger, logsClient, logsQuery, query.TimeRange, query.RefID) data, err = e.handleStartQuery(ctx, logger, logsClient, logsQuery, query.TimeRange, query.RefID)
case "StopQuery": case "StopQuery":
@ -123,7 +123,7 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, logger log.Lo
data, err = e.handleGetLogEvents(ctx, logsClient, logsQuery) data, err = e.handleGetLogEvents(ctx, logsClient, logsQuery)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute log action with subtype: %s: %w", logsQuery.SubType, err) return nil, fmt.Errorf("failed to execute log action with subtype: %s: %w", logsQuery.Subtype, err)
} }
return data, nil return data, nil
@ -214,7 +214,7 @@ func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient c
if logsQuery.LogGroups != nil && len(logsQuery.LogGroups) > 0 { if logsQuery.LogGroups != nil && len(logsQuery.LogGroups) > 0 {
var logGroupIdentifiers []string var logGroupIdentifiers []string
for _, lg := range logsQuery.LogGroups { for _, lg := range logsQuery.LogGroups {
arn := lg.ARN arn := lg.Arn
// due to a bug in the startQuery api, we remove * from the arn, otherwise it throws an error // due to a bug in the startQuery api, we remove * from the arn, otherwise it throws an error
logGroupIdentifiers = append(logGroupIdentifiers, strings.TrimSuffix(arn, "*")) logGroupIdentifiers = append(logGroupIdentifiers, strings.TrimSuffix(arn, "*"))
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface" "github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
"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/tsdb/cloudwatch/kinds/dataquery"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
) )
@ -29,7 +30,9 @@ var executeSyncLogQuery = func(ctx context.Context, e *cloudWatchExecutor, req *
} }
logsQuery.Subtype = "StartQuery" logsQuery.Subtype = "StartQuery"
logsQuery.QueryString = logsQuery.Expression if logsQuery.Expression != nil {
logsQuery.QueryString = *logsQuery.Expression
}
region := logsQuery.Region region := logsQuery.Region
if logsQuery.Region == "" || region == defaultRegion { if logsQuery.Region == "" || region == defaultRegion {
@ -86,7 +89,9 @@ func (e *cloudWatchExecutor) syncQuery(ctx context.Context, logsClient cloudwatc
} }
requestParams := models.LogsQuery{ requestParams := models.LogsQuery{
Region: logsQuery.Region, CloudWatchLogsQuery: dataquery.CloudWatchLogsQuery{
Region: logsQuery.Region,
},
QueryId: *startQueryOutput.QueryId, QueryId: *startQueryOutput.QueryId,
} }

View File

@ -17,22 +17,23 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery"
) )
type ( type (
MetricEditorMode uint32 MetricEditorMode dataquery.MetricEditorMode
MetricQueryType uint32 MetricQueryType dataquery.MetricQueryType
GMDApiMode uint32 GMDApiMode uint32
) )
const ( const (
MetricEditorModeBuilder MetricEditorMode = iota MetricEditorModeBuilder = dataquery.CloudWatchMetricsQueryMetricEditorModeN0
MetricEditorModeRaw MetricEditorModeRaw = dataquery.CloudWatchMetricsQueryMetricEditorModeN1
) )
const ( const (
MetricQueryTypeSearch MetricQueryType = iota MetricQueryTypeSearch = dataquery.CloudWatchMetricsQueryMetricQueryTypeN0
MetricQueryTypeQuery MetricQueryTypeQuery = dataquery.CloudWatchMetricsQueryMetricQueryTypeN1
) )
const ( const (
@ -67,8 +68,8 @@ type CloudWatchQuery struct {
MatchExact bool MatchExact bool
UsedExpression string UsedExpression string
TimezoneUTCOffset string TimezoneUTCOffset string
MetricQueryType MetricQueryType MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType
MetricEditorMode MetricEditorMode MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode
AccountId *string AccountId *string
} }
@ -213,25 +214,9 @@ const timeSeriesQuery = "timeSeriesQuery"
var validMetricDataID = regexp.MustCompile(`^[a-z][a-zA-Z0-9_]*$`) var validMetricDataID = regexp.MustCompile(`^[a-z][a-zA-Z0-9_]*$`)
type metricsDataQuery struct { type metricsDataQuery struct {
Dimensions map[string]interface{} `json:"dimensions"` dataquery.CloudWatchMetricsQuery
Expression string `json:"expression"` Type string `json:"type"`
Label *string `json:"label"` TimezoneUTCOffset string `json:"timezoneUTCOffset"`
Id string `json:"id"`
MatchExact *bool `json:"matchExact"`
MetricEditorMode *MetricEditorMode `json:"metricEditorMode"`
MetricName string `json:"metricName"`
MetricQueryType MetricQueryType `json:"metricQueryType"`
Namespace string `json:"namespace"`
Period string `json:"period"`
Region string `json:"region"`
SqlExpression string `json:"sqlExpression"`
Statistic *string `json:"statistic"`
Statistics []*string `json:"statistics"`
TimezoneUTCOffset string `json:"timezoneUTCOffset"`
QueryType string `json:"type"`
Hide *bool `json:"hide"`
Alias string `json:"alias"`
AccountId *string `json:"accountId"`
} }
// ParseMetricDataQueries decodes the metric data queries json, validates, sets default values and returns an array of CloudWatchQueries. // ParseMetricDataQueries decodes the metric data queries json, validates, sets default values and returns an array of CloudWatchQueries.
@ -246,7 +231,7 @@ func ParseMetricDataQueries(dataQueries []backend.DataQuery, startTime time.Time
return nil, &QueryError{Err: err, RefID: query.RefID} return nil, &QueryError{Err: err, RefID: query.RefID}
} }
queryType := metricsDataQuery.QueryType queryType := metricsDataQuery.Type
if queryType != timeSeriesQuery && queryType != "" { if queryType != timeSeriesQuery && queryType != "" {
continue continue
} }
@ -255,19 +240,35 @@ func ParseMetricDataQueries(dataQueries []backend.DataQuery, startTime time.Time
} }
result := make([]*CloudWatchQuery, 0, len(metricDataQueries)) result := make([]*CloudWatchQuery, 0, len(metricDataQueries))
for refId, mdq := range metricDataQueries { for refId, mdq := range metricDataQueries {
cwQuery := &CloudWatchQuery{ cwQuery := &CloudWatchQuery{
logger: logger, logger: logger,
Alias: mdq.Alias,
RefId: refId, RefId: refId,
Id: mdq.Id, Id: mdq.Id,
Region: mdq.Region, Region: mdq.Region,
Namespace: mdq.Namespace, Namespace: mdq.Namespace,
MetricName: mdq.MetricName,
MetricQueryType: mdq.MetricQueryType,
SqlExpression: mdq.SqlExpression,
TimezoneUTCOffset: mdq.TimezoneUTCOffset, TimezoneUTCOffset: mdq.TimezoneUTCOffset,
Expression: mdq.Expression, }
if mdq.Alias != nil {
cwQuery.Alias = *mdq.Alias
}
if mdq.MetricName != nil {
cwQuery.MetricName = *mdq.MetricName
}
if mdq.MetricQueryType != nil {
cwQuery.MetricQueryType = *mdq.MetricQueryType
}
if mdq.SqlExpression != nil {
cwQuery.SqlExpression = *mdq.SqlExpression
}
if mdq.Expression != nil {
cwQuery.Expression = *mdq.Expression
} }
if err := cwQuery.validateAndSetDefaults(refId, mdq, startTime, endTime, defaultRegion, crossAccountQueryingEnabled); err != nil { if err := cwQuery.validateAndSetDefaults(refId, mdq, startTime, endTime, defaultRegion, crossAccountQueryingEnabled); err != nil {
@ -337,14 +338,14 @@ func (q *CloudWatchQuery) validateAndSetDefaults(refId string, metricsDataQuery
if metricsDataQuery.Hide != nil { if metricsDataQuery.Hide != nil {
q.ReturnData = !*metricsDataQuery.Hide q.ReturnData = !*metricsDataQuery.Hide
} }
if metricsDataQuery.QueryType == "" { if metricsDataQuery.Type == "" {
// If no type is provided we assume we are called by alerting service, which requires to return data! // If no type is provided we assume we are called by alerting service, which requires to return data!
// Note, this is sort of a hack, but the official Grafana interfaces do not carry the information // Note, this is sort of a hack, but the official Grafana interfaces do not carry the information
// who (which service) called the TsdbQueryEndpoint.Query(...) function. // who (which service) called the TsdbQueryEndpoint.Query(...) function.
q.ReturnData = true q.ReturnData = true
} }
if metricsDataQuery.MetricEditorMode == nil && len(metricsDataQuery.Expression) > 0 { if metricsDataQuery.MetricEditorMode == nil && metricsDataQuery.Expression != nil && len(*metricsDataQuery.Expression) > 0 {
// this should only ever happen if this is an alerting query that has not yet been migrated in the frontend // this should only ever happen if this is an alerting query that has not yet been migrated in the frontend
q.MetricEditorMode = MetricEditorModeRaw q.MetricEditorMode = MetricEditorModeRaw
} else { } else {
@ -369,7 +370,7 @@ func (q *CloudWatchQuery) validateAndSetDefaults(refId string, metricsDataQuery
func getStatistic(query metricsDataQuery) string { func getStatistic(query metricsDataQuery) string {
// If there's not a statistic property in the json, we know it's the legacy format and then it has to be migrated // If there's not a statistic property in the json, we know it's the legacy format and then it has to be migrated
if query.Statistic == nil { if query.Statistic == nil {
return *query.Statistics[0] return query.Statistics[0]
} }
return *query.Statistic return *query.Statistic
} }
@ -389,14 +390,17 @@ func getLabel(query metricsDataQuery, dynamicLabelsEnabled bool) string {
if query.Label != nil { if query.Label != nil {
return *query.Label return *query.Label
} }
if query.Alias == "" { if query.Alias != nil && *query.Alias == "" {
return "" return ""
} }
var result string var result string
if dynamicLabelsEnabled { if dynamicLabelsEnabled {
fullAliasField := query.Alias fullAliasField := ""
matches := legacyAliasRegexp.FindAllStringSubmatch(query.Alias, -1) if query.Alias != nil {
fullAliasField = *query.Alias
}
matches := legacyAliasRegexp.FindAllStringSubmatch(fullAliasField, -1)
for _, groups := range matches { for _, groups := range matches {
fullMatch := groups[0] fullMatch := groups[0]
@ -428,7 +432,10 @@ func calculatePeriodBasedOnTimeRange(startTime, endTime time.Time) int {
} }
func getPeriod(query metricsDataQuery, startTime, endTime time.Time) (int, error) { func getPeriod(query metricsDataQuery, startTime, endTime time.Time) (int, error) {
periodString := query.Period periodString := ""
if query.Period != nil {
periodString = *query.Period
}
var period int var period int
var err error var err error
if strings.ToLower(periodString) == "auto" || periodString == "" { if strings.ToLower(periodString) == "auto" || periodString == "" {

View File

@ -8,10 +8,12 @@ import (
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/kindsys"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log/logtest" "github.com/grafana/grafana/pkg/infra/log/logtest"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils"
) )
@ -294,7 +296,7 @@ func TestQueryJSON(t *testing.T) {
var res metricsDataQuery var res metricsDataQuery
err := json.Unmarshal(jsonString, &res) err := json.Unmarshal(jsonString, &res)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "timeSeriesQuery", res.QueryType) assert.Equal(t, "timeSeriesQuery", res.Type)
} }
func TestRequestParser(t *testing.T) { func TestRequestParser(t *testing.T) {
@ -622,11 +624,11 @@ func Test_ParseMetricDataQueries_periods(t *testing.T) {
} }
func Test_ParseMetricDataQueries_query_type_and_metric_editor_mode_and_GMD_query_api_mode(t *testing.T) { func Test_ParseMetricDataQueries_query_type_and_metric_editor_mode_and_GMD_query_api_mode(t *testing.T) {
const dummyTestEditorMode MetricEditorMode = 99 const dummyTestEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode = 99
testCases := map[string]struct { testCases := map[string]struct {
extraDataQueryJson string extraDataQueryJson string
expectedMetricQueryType MetricQueryType expectedMetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType
expectedMetricEditorMode MetricEditorMode expectedMetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode
expectedGMDApiMode GMDApiMode expectedGMDApiMode GMDApiMode
}{ }{
"no metric query type, no metric editor mode, no expression": { "no metric query type, no metric editor mode, no expression": {
@ -930,16 +932,18 @@ func Test_migrateAliasToDynamicLabel_single_query_preserves_old_alias_and_create
false := false false := false
queryToMigrate := metricsDataQuery{ queryToMigrate := metricsDataQuery{
Region: "us-east-1", CloudWatchMetricsQuery: dataquery.CloudWatchMetricsQuery{
Namespace: "ec2", Region: "us-east-1",
MetricName: "CPUUtilization", Namespace: "ec2",
Alias: tc.inputAlias, MetricName: kindsys.Ptr("CPUUtilization"),
Dimensions: map[string]interface{}{ Alias: kindsys.Ptr(tc.inputAlias),
"InstanceId": []interface{}{"test"}, Dimensions: map[string]interface{}{
"InstanceId": []interface{}{"test"},
},
Statistic: &average,
Period: kindsys.Ptr("600"),
Hide: &false,
}, },
Statistic: &average,
Period: "600",
Hide: &false,
} }
assert.Equal(t, tc.expectedLabel, getLabel(queryToMigrate, true)) assert.Equal(t, tc.expectedLabel, getLabel(queryToMigrate, true))

View File

@ -1,28 +1,18 @@
package models package models
type LogGroup struct { import (
ARN string `json:"arn"` "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery"
Name string `json:"name"` )
AccountID string `json:"accountId"`
}
type LogsQuery struct { type LogsQuery struct {
LogType string `json:"type"` dataquery.CloudWatchLogsQuery
SubType string StartTime *int64
Limit *int64 EndTime *int64
Time int64 Limit *int64
StartTime *int64 LogGroupName string
EndTime *int64 LogStreamName string
LogGroupName string QueryId string
LogGroupNames []string QueryString string
LogGroups []LogGroup `json:"logGroups"` StartFromHead bool
LogGroupNamePrefix string Subtype string
LogStreamName string
StartFromHead bool
Region string
QueryString string
QueryId string
StatsGroups []string
Subtype string
Expression string
} }

View File

@ -17,6 +17,7 @@ import (
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
@ -270,16 +271,16 @@ type queryDimensions struct {
} }
type queryParameters struct { type queryParameters struct {
MetricQueryType models.MetricQueryType `json:"metricQueryType"` MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType `json:"metricQueryType"`
MetricEditorMode models.MetricEditorMode `json:"metricEditorMode"` MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode `json:"metricEditorMode"`
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"` 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"`
MetricName string `json:"metricName"` MetricName string `json:"metricName"`
} }
var queryId = "query id" var queryId = "query id"
@ -288,11 +289,11 @@ func newTestQuery(t testing.TB, p queryParameters) json.RawMessage {
t.Helper() t.Helper()
tsq := struct { tsq := struct {
Type string `json:"type"` Type string `json:"type"`
MetricQueryType models.MetricQueryType `json:"metricQueryType"` MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType `json:"metricQueryType"`
MetricEditorMode models.MetricEditorMode `json:"metricEditorMode"` MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode `json:"metricEditorMode"`
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
MetricName string `json:"metricName"` MetricName string `json:"metricName"`
Dimensions struct { Dimensions struct {
InstanceID []string `json:"InstanceId,omitempty"` InstanceID []string `json:"InstanceId,omitempty"`
} `json:"dimensions"` } `json:"dimensions"`

View File

@ -23,7 +23,7 @@ import (
pfs.GrafanaPlugin pfs.GrafanaPlugin
composableKinds: DataQuery: { composableKinds: DataQuery: {
maturity: "merged" maturity: "experimental"
lineage: { lineage: {
seqs: [ seqs: [