Commit 8e7166b5 by Mikael Olenfalk Committed by Torkel Ödegaard

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.
parent 0506cfdc
...@@ -78,11 +78,14 @@ CloudWatch Datasource Plugin provides the following queries you can specify in t ...@@ -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` edit view. They allow you to fill a variable's options list with things like `region`, `namespaces`, `metric names`
and `dimension keys/values`. 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 Name | Description
------- | -------- ------- | --------
*regions()* | Returns a list of regions AWS provides their service. *regions()* | Returns a list of regions AWS provides their service.
*namespaces()* | Returns a list of namespaces CloudWatch support. *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_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`. *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`. *ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`.
......
...@@ -141,6 +141,11 @@ func ec2RoleProvider(sess *session.Session) credentials.Provider { ...@@ -141,6 +141,11 @@ func ec2RoleProvider(sess *session.Session) credentials.Provider {
} }
func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo { 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() authType := e.DataSource.JsonData.Get("authType").MustString()
assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString() assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
accessKey := "" accessKey := ""
......
...@@ -39,7 +39,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { ...@@ -39,7 +39,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
!!item.metricName && !!item.metricName &&
!_.isEmpty(item.statistics); !_.isEmpty(item.statistics);
}).map(function (item) { }).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.namespace = templateSrv.replace(item.namespace, options.scopedVars);
item.metricName = templateSrv.replace(item.metricName, options.scopedVars); item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars); item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
...@@ -165,21 +165,21 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { ...@@ -165,21 +165,21 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
this.getMetrics = function (namespace, region) { this.getMetrics = function (namespace, region) {
return this.doMetricQueryRequest('metrics', { return this.doMetricQueryRequest('metrics', {
region: templateSrv.replace(region), region: templateSrv.replace(this.getActualRegion(region)),
namespace: templateSrv.replace(namespace) namespace: templateSrv.replace(namespace)
}); });
}; };
this.getDimensionKeys = function(namespace, region) { this.getDimensionKeys = function(namespace, region) {
return this.doMetricQueryRequest('dimension_keys', { return this.doMetricQueryRequest('dimension_keys', {
region: templateSrv.replace(region), region: templateSrv.replace(this.getActualRegion(region)),
namespace: templateSrv.replace(namespace) namespace: templateSrv.replace(namespace)
}); });
}; };
this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) { this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
return this.doMetricQueryRequest('dimension_values', { return this.doMetricQueryRequest('dimension_values', {
region: templateSrv.replace(region), region: templateSrv.replace(this.getActualRegion(region)),
namespace: templateSrv.replace(namespace), namespace: templateSrv.replace(namespace),
metricName: templateSrv.replace(metricName), metricName: templateSrv.replace(metricName),
dimensionKey: templateSrv.replace(dimensionKey), dimensionKey: templateSrv.replace(dimensionKey),
...@@ -189,14 +189,14 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { ...@@ -189,14 +189,14 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
this.getEbsVolumeIds = function(region, instanceId) { this.getEbsVolumeIds = function(region, instanceId) {
return this.doMetricQueryRequest('ebs_volume_ids', { return this.doMetricQueryRequest('ebs_volume_ids', {
region: templateSrv.replace(region), region: templateSrv.replace(this.getActualRegion(region)),
instanceId: templateSrv.replace(instanceId) instanceId: templateSrv.replace(instanceId)
}); });
}; };
this.getEc2InstanceAttribute = function(region, attributeName, filters) { this.getEc2InstanceAttribute = function(region, attributeName, filters) {
return this.doMetricQueryRequest('ec2_instance_attribute', { return this.doMetricQueryRequest('ec2_instance_attribute', {
region: templateSrv.replace(region), region: templateSrv.replace(this.getActualRegion(region)),
attributeName: templateSrv.replace(attributeName), attributeName: templateSrv.replace(attributeName),
filters: filters filters: filters
}); });
...@@ -267,7 +267,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { ...@@ -267,7 +267,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
period = parseInt(period, 10); period = parseInt(period, 10);
var parameters = { var parameters = {
prefixMatching: annotation.prefixMatching, prefixMatching: annotation.prefixMatching,
region: templateSrv.replace(annotation.region), region: templateSrv.replace(this.getActualRegion(annotation.region)),
namespace: templateSrv.replace(annotation.namespace), namespace: templateSrv.replace(annotation.namespace),
metricName: templateSrv.replace(annotation.metricName), metricName: templateSrv.replace(annotation.metricName),
dimensions: this.convertDimensionFormat(annotation.dimensions, {}), dimensions: this.convertDimensionFormat(annotation.dimensions, {}),
...@@ -341,6 +341,13 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) { ...@@ -341,6 +341,13 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
return this.defaultRegion; return this.defaultRegion;
}; };
this.getActualRegion = function(region) {
if (region === 'default' || _.isEmpty(region)) {
return this.getDefaultRegion();
}
return region;
};
this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) { this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) {
/* if the all checkbox is marked we should add all values to the targets */ /* if the all checkbox is marked we should add all values to the targets */
var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'}); var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
......
...@@ -28,7 +28,7 @@ export class CloudWatchQueryParameterCtrl { ...@@ -28,7 +28,7 @@ export class CloudWatchQueryParameterCtrl {
target.statistics = target.statistics || ['Average']; target.statistics = target.statistics || ['Average'];
target.dimensions = target.dimensions || {}; target.dimensions = target.dimensions || {};
target.period = target.period || ''; target.period = target.period || '';
target.region = target.region || ''; target.region = target.region || 'default';
$scope.regionSegment = uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region'); $scope.regionSegment = uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region');
$scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace'); $scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace');
...@@ -51,7 +51,7 @@ export class CloudWatchQueryParameterCtrl { ...@@ -51,7 +51,7 @@ export class CloudWatchQueryParameterCtrl {
$scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'}); $scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'});
if (_.isEmpty($scope.target.region)) { if (_.isEmpty($scope.target.region)) {
$scope.target.region = $scope.datasource.getDefaultRegion(); $scope.target.region = 'default';
} }
if (!$scope.onChange) { if (!$scope.onChange) {
...@@ -148,6 +148,10 @@ export class CloudWatchQueryParameterCtrl { ...@@ -148,6 +148,10 @@ export class CloudWatchQueryParameterCtrl {
$scope.getRegions = function() { $scope.getRegions = function() {
return $scope.datasource.metricFindQuery('regions()') return $scope.datasource.metricFindQuery('regions()')
.then(function(results) {
results.unshift({ text: 'default'});
return results;
})
.then($scope.transformToSegments(true)); .then($scope.transformToSegments(true));
}; };
......
...@@ -165,6 +165,55 @@ describe('CloudWatchDatasource', function() { ...@@ -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() { describe('When performing CloudWatch query for extended statistics', function() {
var requestParams; var requestParams;
...@@ -348,6 +397,26 @@ describe('CloudWatchDatasource', function() { ...@@ -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 () { it('should caclculate the correct period', function () {
var hourSec = 60 * 60; var hourSec = 60 * 60;
var daySec = hourSec * 24; var daySec = hourSec * 24;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment