From 8e7166b5c4a77c52d68dc0c01adc67eeb8b2a54c Mon Sep 17 00:00:00 2001 From: Mikael Olenfalk Date: Mon, 11 Dec 2017 09:37:27 +0100 Subject: [PATCH] Explicitly specify default region in CloudWatch datasource (#9440) The datasource uses the default region in the query if the region is "" in the settings. However setting the region to an empty string is almost impossible and rendered incorrectly. This commit introduces a special value "default" for region which is shown in the drop down and is translated to the default region of the data source when performing queries. --- .../features/datasources/cloudwatch.md | 5 +- pkg/tsdb/cloudwatch/credentials.go | 5 ++ .../datasource/cloudwatch/datasource.js | 21 ++++-- .../cloudwatch/query_parameter_ctrl.ts | 8 ++- .../cloudwatch/specs/datasource_specs.ts | 69 +++++++++++++++++++ 5 files changed, 98 insertions(+), 10 deletions(-) diff --git a/docs/sources/features/datasources/cloudwatch.md b/docs/sources/features/datasources/cloudwatch.md index bdf661dc4fc..648957ed96e 100644 --- a/docs/sources/features/datasources/cloudwatch.md +++ b/docs/sources/features/datasources/cloudwatch.md @@ -78,11 +78,14 @@ CloudWatch Datasource Plugin provides the following queries you can specify in t edit view. They allow you to fill a variable's options list with things like `region`, `namespaces`, `metric names` and `dimension keys/values`. +In place of `region` you can specify `default` to use the default region configured in the datasource for the query, +e.g. `metrics(AWS/DynamoDB, default)` or `dimension_values(default, ..., ..., ...)`. + Name | Description ------- | -------- *regions()* | Returns a list of regions AWS provides their service. *namespaces()* | Returns a list of namespaces CloudWatch support. -*metrics(namespace, [region])* | Returns a list of metrics in the namespace. (specify region for custom metrics) +*metrics(namespace, [region])* | Returns a list of metrics in the namespace. (specify region or use "default" for custom metrics) *dimension_keys(namespace)* | Returns a list of dimension keys in the namespace. *dimension_values(region, namespace, metric, dimension_key)* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`. *ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`. diff --git a/pkg/tsdb/cloudwatch/credentials.go b/pkg/tsdb/cloudwatch/credentials.go index 784f3b729ac..0c142bd4ea0 100644 --- a/pkg/tsdb/cloudwatch/credentials.go +++ b/pkg/tsdb/cloudwatch/credentials.go @@ -141,6 +141,11 @@ func ec2RoleProvider(sess *session.Session) credentials.Provider { } func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo { + defaultRegion := e.DataSource.JsonData.Get("defaultRegion").MustString() + if region == "default" { + region = defaultRegion + } + authType := e.DataSource.JsonData.Get("authType").MustString() assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString() accessKey := "" diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index ac4573ef43a..b0d37fd2186 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -39,7 +39,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { !!item.metricName && !_.isEmpty(item.statistics); }).map(function (item) { - item.region = templateSrv.replace(item.region, options.scopedVars); + item.region = templateSrv.replace(self.getActualRegion(item.region), options.scopedVars); item.namespace = templateSrv.replace(item.namespace, options.scopedVars); item.metricName = templateSrv.replace(item.metricName, options.scopedVars); item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars); @@ -165,21 +165,21 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { this.getMetrics = function (namespace, region) { return this.doMetricQueryRequest('metrics', { - region: templateSrv.replace(region), + region: templateSrv.replace(this.getActualRegion(region)), namespace: templateSrv.replace(namespace) }); }; this.getDimensionKeys = function(namespace, region) { return this.doMetricQueryRequest('dimension_keys', { - region: templateSrv.replace(region), + region: templateSrv.replace(this.getActualRegion(region)), namespace: templateSrv.replace(namespace) }); }; this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) { return this.doMetricQueryRequest('dimension_values', { - region: templateSrv.replace(region), + region: templateSrv.replace(this.getActualRegion(region)), namespace: templateSrv.replace(namespace), metricName: templateSrv.replace(metricName), dimensionKey: templateSrv.replace(dimensionKey), @@ -189,14 +189,14 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { this.getEbsVolumeIds = function(region, instanceId) { return this.doMetricQueryRequest('ebs_volume_ids', { - region: templateSrv.replace(region), + region: templateSrv.replace(this.getActualRegion(region)), instanceId: templateSrv.replace(instanceId) }); }; this.getEc2InstanceAttribute = function(region, attributeName, filters) { return this.doMetricQueryRequest('ec2_instance_attribute', { - region: templateSrv.replace(region), + region: templateSrv.replace(this.getActualRegion(region)), attributeName: templateSrv.replace(attributeName), filters: filters }); @@ -267,7 +267,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { period = parseInt(period, 10); var parameters = { prefixMatching: annotation.prefixMatching, - region: templateSrv.replace(annotation.region), + region: templateSrv.replace(this.getActualRegion(annotation.region)), namespace: templateSrv.replace(annotation.namespace), metricName: templateSrv.replace(annotation.metricName), dimensions: this.convertDimensionFormat(annotation.dimensions, {}), @@ -341,6 +341,13 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { return this.defaultRegion; }; + this.getActualRegion = function(region) { + if (region === 'default' || _.isEmpty(region)) { + return this.getDefaultRegion(); + } + return region; + }; + this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) { /* if the all checkbox is marked we should add all values to the targets */ var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'}); diff --git a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts index 57a5fba443b..6bf22b0f2e7 100644 --- a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts +++ b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.ts @@ -28,7 +28,7 @@ export class CloudWatchQueryParameterCtrl { target.statistics = target.statistics || ['Average']; target.dimensions = target.dimensions || {}; target.period = target.period || ''; - target.region = target.region || ''; + target.region = target.region || 'default'; $scope.regionSegment = uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region'); $scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace'); @@ -51,7 +51,7 @@ export class CloudWatchQueryParameterCtrl { $scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'}); if (_.isEmpty($scope.target.region)) { - $scope.target.region = $scope.datasource.getDefaultRegion(); + $scope.target.region = 'default'; } if (!$scope.onChange) { @@ -148,6 +148,10 @@ export class CloudWatchQueryParameterCtrl { $scope.getRegions = function() { return $scope.datasource.metricFindQuery('regions()') + .then(function(results) { + results.unshift({ text: 'default'}); + return results; + }) .then($scope.transformToSegments(true)); }; diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index f278ce9305d..5eda60d5b9e 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -165,6 +165,55 @@ describe('CloudWatchDatasource', function() { }); }); + describe('When query region is "default"', function () { + it('should return the datasource region if empty or "default"', function() { + var defaultRegion = instanceSettings.jsonData.defaultRegion; + + expect(ctx.ds.getActualRegion()).to.be(defaultRegion); + expect(ctx.ds.getActualRegion('')).to.be(defaultRegion); + expect(ctx.ds.getActualRegion("default")).to.be(defaultRegion); + }); + + it('should return the specified region if specified', function() { + expect(ctx.ds.getActualRegion('some-fake-region-1')).to.be('some-fake-region-1'); + }); + + var requestParams; + beforeEach(function() { + ctx.ds.performTimeSeriesQuery = function(request) { + requestParams = request; + return ctx.$q.when({data: {}}); + }; + }); + + it('should query for the datasource region if empty or "default"', function(done) { + var query = { + range: { from: 'now-1h', to: 'now' }, + rangeRaw: { from: 1483228800, to: 1483232400 }, + targets: [ + { + region: 'default', + namespace: 'AWS/EC2', + metricName: 'CPUUtilization', + dimensions: { + InstanceId: 'i-12345678' + }, + statistics: ['Average'], + period: 300 + } + ] + }; + + ctx.ds.query(query).then(function(result) { + expect(requestParams.queries[0].region).to.be(instanceSettings.jsonData.defaultRegion); + done(); + }); + ctx.$rootScope.$apply(); + }); + + + }); + describe('When performing CloudWatch query for extended statistics', function() { var requestParams; @@ -348,6 +397,26 @@ describe('CloudWatchDatasource', function() { }); }); + describeMetricFindQuery('dimension_values(default,AWS/EC2,CPUUtilization,InstanceId)', scenario => { + scenario.setup(() => { + scenario.requestResponse = { + results: { + metricFindQuery: { + tables: [ + { rows: [['i-12345678', 'i-12345678']] } + ] + } + } + }; + }); + + it('should call __ListMetrics and return result', () => { + expect(scenario.result[0].text).to.contain('i-12345678'); + expect(scenario.request.queries[0].type).to.be('metricFindQuery'); + expect(scenario.request.queries[0].subtype).to.be('dimension_values'); + }); + }); + it('should caclculate the correct period', function () { var hourSec = 60 * 60; var daySec = hourSec * 24;