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
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 {
return nil, err
}
period = p
}
if period == 0 && !model.PrefixMatching {
prefixMatching := false
if model.PrefixMatching != nil {
prefixMatching = *model.PrefixMatching
}
if period == 0 && !prefixMatching {
period = 300
}
@ -49,19 +54,23 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont
}
var alarmNames []*string
if model.PrefixMatching {
metricName := ""
if model.MetricName != nil {
metricName = *model.MetricName
}
if prefixMatching {
params := &cloudwatch.DescribeAlarmsInput{
MaxRecords: aws.Int64(100),
ActionPrefix: aws.String(actionPrefix),
AlarmNamePrefix: aws.String(alarmNamePrefix),
ActionPrefix: actionPrefix,
AlarmNamePrefix: alarmNamePrefix,
}
resp, err := cli.DescribeAlarms(params)
if err != nil {
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 {
if model.Region == "" || model.Namespace == "" || model.MetricName == "" || statistic == "" {
if model.Region == "" || model.Namespace == "" || metricName == "" || statistic == "" {
return result, errors.New("invalid annotations query")
}
@ -80,7 +89,7 @@ func (e *cloudWatchExecutor) executeAnnotationQuery(pluginCtx backend.PluginCont
}
params := &cloudwatch.DescribeAlarmsForMetricInput{
Namespace: aws.String(model.Namespace),
MetricName: aws.String(model.MetricName),
MetricName: aws.String(metricName),
Dimensions: qd,
Statistic: aws.String(statistic),
Period: aws.Int64(period),

View File

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

View File

@ -112,7 +112,7 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, logger log.Lo
}
var data *data.Frame = nil
switch logsQuery.SubType {
switch logsQuery.Subtype {
case "StartQuery":
data, err = e.handleStartQuery(ctx, logger, logsClient, logsQuery, query.TimeRange, query.RefID)
case "StopQuery":
@ -123,7 +123,7 @@ func (e *cloudWatchExecutor) executeLogAction(ctx context.Context, logger log.Lo
data, err = e.handleGetLogEvents(ctx, logsClient, logsQuery)
}
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
@ -214,7 +214,7 @@ func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient c
if logsQuery.LogGroups != nil && len(logsQuery.LogGroups) > 0 {
var logGroupIdentifiers []string
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
logGroupIdentifiers = append(logGroupIdentifiers, strings.TrimSuffix(arn, "*"))
}

View File

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

View File

@ -17,22 +17,23 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery"
)
type (
MetricEditorMode uint32
MetricQueryType uint32
MetricEditorMode dataquery.MetricEditorMode
MetricQueryType dataquery.MetricQueryType
GMDApiMode uint32
)
const (
MetricEditorModeBuilder MetricEditorMode = iota
MetricEditorModeRaw
MetricEditorModeBuilder = dataquery.CloudWatchMetricsQueryMetricEditorModeN0
MetricEditorModeRaw = dataquery.CloudWatchMetricsQueryMetricEditorModeN1
)
const (
MetricQueryTypeSearch MetricQueryType = iota
MetricQueryTypeQuery
MetricQueryTypeSearch = dataquery.CloudWatchMetricsQueryMetricQueryTypeN0
MetricQueryTypeQuery = dataquery.CloudWatchMetricsQueryMetricQueryTypeN1
)
const (
@ -67,8 +68,8 @@ type CloudWatchQuery struct {
MatchExact bool
UsedExpression string
TimezoneUTCOffset string
MetricQueryType MetricQueryType
MetricEditorMode MetricEditorMode
MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType
MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode
AccountId *string
}
@ -213,25 +214,9 @@ const timeSeriesQuery = "timeSeriesQuery"
var validMetricDataID = regexp.MustCompile(`^[a-z][a-zA-Z0-9_]*$`)
type metricsDataQuery struct {
Dimensions map[string]interface{} `json:"dimensions"`
Expression string `json:"expression"`
Label *string `json:"label"`
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"`
dataquery.CloudWatchMetricsQuery
Type string `json:"type"`
TimezoneUTCOffset string `json:"timezoneUTCOffset"`
}
// 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}
}
queryType := metricsDataQuery.QueryType
queryType := metricsDataQuery.Type
if queryType != timeSeriesQuery && queryType != "" {
continue
}
@ -255,19 +240,35 @@ func ParseMetricDataQueries(dataQueries []backend.DataQuery, startTime time.Time
}
result := make([]*CloudWatchQuery, 0, len(metricDataQueries))
for refId, mdq := range metricDataQueries {
cwQuery := &CloudWatchQuery{
logger: logger,
Alias: mdq.Alias,
RefId: refId,
Id: mdq.Id,
Region: mdq.Region,
Namespace: mdq.Namespace,
MetricName: mdq.MetricName,
MetricQueryType: mdq.MetricQueryType,
SqlExpression: mdq.SqlExpression,
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 {
@ -337,14 +338,14 @@ func (q *CloudWatchQuery) validateAndSetDefaults(refId string, metricsDataQuery
if metricsDataQuery.Hide != nil {
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!
// 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.
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
q.MetricEditorMode = MetricEditorModeRaw
} else {
@ -369,7 +370,7 @@ func (q *CloudWatchQuery) validateAndSetDefaults(refId string, metricsDataQuery
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 query.Statistic == nil {
return *query.Statistics[0]
return query.Statistics[0]
}
return *query.Statistic
}
@ -389,14 +390,17 @@ func getLabel(query metricsDataQuery, dynamicLabelsEnabled bool) string {
if query.Label != nil {
return *query.Label
}
if query.Alias == "" {
if query.Alias != nil && *query.Alias == "" {
return ""
}
var result string
if dynamicLabelsEnabled {
fullAliasField := query.Alias
matches := legacyAliasRegexp.FindAllStringSubmatch(query.Alias, -1)
fullAliasField := ""
if query.Alias != nil {
fullAliasField = *query.Alias
}
matches := legacyAliasRegexp.FindAllStringSubmatch(fullAliasField, -1)
for _, groups := range matches {
fullMatch := groups[0]
@ -428,7 +432,10 @@ func calculatePeriodBasedOnTimeRange(startTime, endTime time.Time) int {
}
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 err error
if strings.ToLower(periodString) == "auto" || periodString == "" {

View File

@ -8,10 +8,12 @@ import (
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/kindsys"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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"
)
@ -294,7 +296,7 @@ func TestQueryJSON(t *testing.T) {
var res metricsDataQuery
err := json.Unmarshal(jsonString, &res)
require.NoError(t, err)
assert.Equal(t, "timeSeriesQuery", res.QueryType)
assert.Equal(t, "timeSeriesQuery", res.Type)
}
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) {
const dummyTestEditorMode MetricEditorMode = 99
const dummyTestEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode = 99
testCases := map[string]struct {
extraDataQueryJson string
expectedMetricQueryType MetricQueryType
expectedMetricEditorMode MetricEditorMode
expectedMetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType
expectedMetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode
expectedGMDApiMode GMDApiMode
}{
"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
queryToMigrate := metricsDataQuery{
Region: "us-east-1",
Namespace: "ec2",
MetricName: "CPUUtilization",
Alias: tc.inputAlias,
Dimensions: map[string]interface{}{
"InstanceId": []interface{}{"test"},
CloudWatchMetricsQuery: dataquery.CloudWatchMetricsQuery{
Region: "us-east-1",
Namespace: "ec2",
MetricName: kindsys.Ptr("CPUUtilization"),
Alias: kindsys.Ptr(tc.inputAlias),
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))

View File

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

View File

@ -17,6 +17,7 @@ import (
"github.com/stretchr/testify/mock"
"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/models"
@ -270,16 +271,16 @@ type queryDimensions struct {
}
type queryParameters struct {
MetricQueryType models.MetricQueryType `json:"metricQueryType"`
MetricEditorMode models.MetricEditorMode `json:"metricEditorMode"`
Dimensions queryDimensions `json:"dimensions"`
Expression string `json:"expression"`
Alias string `json:"alias"`
Label *string `json:"label"`
Statistic string `json:"statistic"`
Period string `json:"period"`
MatchExact bool `json:"matchExact"`
MetricName string `json:"metricName"`
MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType `json:"metricQueryType"`
MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode `json:"metricEditorMode"`
Dimensions queryDimensions `json:"dimensions"`
Expression string `json:"expression"`
Alias string `json:"alias"`
Label *string `json:"label"`
Statistic string `json:"statistic"`
Period string `json:"period"`
MatchExact bool `json:"matchExact"`
MetricName string `json:"metricName"`
}
var queryId = "query id"
@ -288,11 +289,11 @@ func newTestQuery(t testing.TB, p queryParameters) json.RawMessage {
t.Helper()
tsq := struct {
Type string `json:"type"`
MetricQueryType models.MetricQueryType `json:"metricQueryType"`
MetricEditorMode models.MetricEditorMode `json:"metricEditorMode"`
Namespace string `json:"namespace"`
MetricName string `json:"metricName"`
Type string `json:"type"`
MetricQueryType dataquery.CloudWatchMetricsQueryMetricQueryType `json:"metricQueryType"`
MetricEditorMode dataquery.CloudWatchMetricsQueryMetricEditorMode `json:"metricEditorMode"`
Namespace string `json:"namespace"`
MetricName string `json:"metricName"`
Dimensions struct {
InstanceID []string `json:"InstanceId,omitempty"`
} `json:"dimensions"`

View File

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