Commit 180ba33a by Torkel Ödegaard

feat(cloudwatch): refactoring and cleanup of backend code, started moving hard…

feat(cloudwatch): refactoring and cleanup of backend code, started moving hard coded stuff in the frontend to the backend, changed name of metricFind queries region() -> regions() , and namespace() -> namespaces() to be more consistent with the others, #684
parent 04f44549
package api
package cloudwatch
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"time"
......@@ -11,115 +10,141 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/util"
)
func ProxyCloudWatchDataSourceRequest(c *middleware.Context) {
body, _ := ioutil.ReadAll(c.Req.Request.Body)
type actionHandler func(*cwRequest, *middleware.Context)
reqInfo := &struct {
Region string `json:"region"`
Service string `json:"service"`
Action string `json:"action"`
var actionHandlers map[string]actionHandler
type cwRequest struct {
Region string `json:"region"`
Action string `json:"action"`
Body []byte `json:"-"`
}
func init() {
actionHandlers = map[string]actionHandler{
"GetMetricStatistics": handleGetMetricStatistics,
"ListMetrics": handleListMetrics,
"DescribeInstances": handleDescribeInstances,
"__GetRegions": handleGetRegions,
}
}
func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) {
svc := cloudwatch.New(&aws.Config{Region: aws.String(req.Region)})
reqParam := &struct {
Parameters struct {
Namespace string `json:"namespace"`
MetricName string `json:"metricName"`
Dimensions []*cloudwatch.Dimension `json:"dimensions"`
Statistics []*string `json:"statistics"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
Period int64 `json:"period"`
} `json:"parameters"`
}{}
json.Unmarshal(req.Body, reqParam)
params := &cloudwatch.GetMetricStatisticsInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Dimensions: reqParam.Parameters.Dimensions,
Statistics: reqParam.Parameters.Statistics,
StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)),
EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)),
Period: aws.Int64(reqParam.Parameters.Period),
}
resp, err := svc.GetMetricStatistics(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
c.JSON(200, resp)
}
func handleListMetrics(req *cwRequest, c *middleware.Context) {
svc := cloudwatch.New(&aws.Config{Region: aws.String(req.Region)})
reqParam := &struct {
Parameters struct {
Namespace string `json:"namespace"`
MetricName string `json:"metricName"`
Dimensions []*cloudwatch.DimensionFilter `json:"dimensions"`
} `json:"parameters"`
}{}
json.Unmarshal(req.Body, reqParam)
params := &cloudwatch.ListMetricsInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Dimensions: reqParam.Parameters.Dimensions,
}
resp, err := svc.ListMetrics(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
c.JSON(200, resp)
}
func handleDescribeInstances(req *cwRequest, c *middleware.Context) {
svc := ec2.New(&aws.Config{Region: aws.String(req.Region)})
reqParam := &struct {
Parameters struct {
Filters []*ec2.Filter `json:"filters"`
InstanceIds []*string `json:"instanceIds"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqInfo)
switch reqInfo.Service {
case "CloudWatch":
svc := cloudwatch.New(&aws.Config{Region: aws.String(reqInfo.Region)})
switch reqInfo.Action {
case "GetMetricStatistics":
reqParam := &struct {
Parameters struct {
Namespace string `json:"namespace"`
MetricName string `json:"metricName"`
Dimensions []*cloudwatch.Dimension `json:"dimensions"`
Statistics []*string `json:"statistics"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
Period int64 `json:"period"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqParam)
params := &cloudwatch.GetMetricStatisticsInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Dimensions: reqParam.Parameters.Dimensions,
Statistics: reqParam.Parameters.Statistics,
StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)),
EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)),
Period: aws.Int64(reqParam.Parameters.Period),
}
resp, err := svc.GetMetricStatistics(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
respJson, _ := json.Marshal(resp)
fmt.Fprint(c.RW(), string(respJson))
case "ListMetrics":
reqParam := &struct {
Parameters struct {
Namespace string `json:"namespace"`
MetricName string `json:"metricName"`
Dimensions []*cloudwatch.DimensionFilter `json:"dimensions"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqParam)
params := &cloudwatch.ListMetricsInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Dimensions: reqParam.Parameters.Dimensions,
}
resp, err := svc.ListMetrics(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
respJson, _ := json.Marshal(resp)
fmt.Fprint(c.RW(), string(respJson))
default:
c.JsonApiErr(500, "Unexpected CloudWatch action", errors.New(reqInfo.Action))
}
case "EC2":
svc := ec2.New(&aws.Config{Region: aws.String(reqInfo.Region)})
switch reqInfo.Action {
case "DescribeInstances":
reqParam := &struct {
Parameters struct {
Filters []*ec2.Filter `json:"filters"`
InstanceIds []*string `json:"instanceIds"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqParam)
params := &ec2.DescribeInstancesInput{}
if len(reqParam.Parameters.Filters) > 0 {
params.Filters = reqParam.Parameters.Filters
}
if len(reqParam.Parameters.InstanceIds) > 0 {
params.InstanceIDs = reqParam.Parameters.InstanceIds
}
resp, err := svc.DescribeInstances(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
respJson, _ := json.Marshal(resp)
fmt.Fprint(c.RW(), string(respJson))
default:
c.JsonApiErr(500, "Unexpected EC2 action", errors.New(reqInfo.Action))
}
default:
c.JsonApiErr(500, "Unexpected service", errors.New(reqInfo.Service))
json.Unmarshal(req.Body, reqParam)
params := &ec2.DescribeInstancesInput{}
if len(reqParam.Parameters.Filters) > 0 {
params.Filters = reqParam.Parameters.Filters
}
if len(reqParam.Parameters.InstanceIds) > 0 {
params.InstanceIDs = reqParam.Parameters.InstanceIds
}
resp, err := svc.DescribeInstances(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
c.JSON(200, resp)
}
func handleGetRegions(req *cwRequest, c *middleware.Context) {
regions := []string{
"us-west-2", "us-west-1", "eu-west-1", "eu-central-1", "ap-southeast-1",
"ap-southeast-2", "ap-northeast-1", "sa-east-1",
}
result := []interface{}{}
for _, region := range regions {
result = append(result, util.DynMap{"text": region, "value": region})
}
c.JSON(200, result)
}
func HandleRequest(c *middleware.Context) {
var req cwRequest
req.Body, _ = ioutil.ReadAll(c.Req.Request.Body)
json.Unmarshal(req.Body, &req)
if handler, found := actionHandlers[req.Action]; !found {
c.JsonApiErr(500, "Unexpected AWS Action", errors.New(req.Action))
return
} else {
handler(&req, c)
}
}
......@@ -8,6 +8,7 @@ import (
"net/url"
"time"
"github.com/grafana/grafana/pkg/api/cloudwatch"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
......@@ -83,7 +84,7 @@ func ProxyDataSourceRequest(c *middleware.Context) {
}
if query.Result.Type == m.DS_CLOUDWATCH {
ProxyCloudWatchDataSourceRequest(c)
cloudwatch.HandleRequest(c)
} else {
proxyPath := c.Params("*")
proxy := NewReverseProxy(&ds, proxyPath, targetUrl)
......
......@@ -20,9 +20,6 @@ function (angular, _) {
this.defaultRegion = datasource.jsonData.defaultRegion;
/* jshint -W101 */
this.supportedRegion = [
'us-east-1', 'us-west-2', 'us-west-1', 'eu-west-1', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'sa-east-1'
];
this.supportedMetrics = {
'AWS/AutoScaling': [
......@@ -265,7 +262,6 @@ function (angular, _) {
CloudWatchDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {
return this.awsRequest({
region: query.region,
service: 'CloudWatch',
action: 'GetMetricStatistics',
parameters: {
namespace: query.namespace,
......@@ -280,7 +276,7 @@ function (angular, _) {
};
CloudWatchDatasource.prototype.getRegions = function() {
return $q.when(this.supportedRegion);
return this.awsRequest({action: '__GetRegions'});
};
CloudWatchDatasource.prototype.getNamespaces = function() {
......@@ -300,7 +296,6 @@ function (angular, _) {
CloudWatchDatasource.prototype.getDimensionValues = function(region, namespace, metricName, dimensions) {
var request = {
region: templateSrv.replace(region),
service: 'CloudWatch',
action: 'ListMetrics',
parameters: {
namespace: templateSrv.replace(namespace),
......@@ -321,7 +316,6 @@ function (angular, _) {
CloudWatchDatasource.prototype.performEC2DescribeInstances = function(region, filters, instanceIds) {
return this.awsRequest({
region: region,
service: 'EC2',
action: 'DescribeInstances',
parameters: {
filter: filters,
......@@ -341,12 +335,12 @@ function (angular, _) {
});
};
var regionQuery = query.match(/^region\(\)/);
var regionQuery = query.match(/^regions\(\)/);
if (regionQuery) {
return this.getRegions().then(transformSuggestData);
return this.getRegions();
}
var namespaceQuery = query.match(/^namespace\(\)/);
var namespaceQuery = query.match(/^namespaces\(\)/);
if (namespaceQuery) {
return this.getNamespaces().then(transformSuggestData);
}
......
......@@ -25,12 +25,12 @@ function (angular, _) {
};
$scope.getRegions = function() {
return $scope.datasource.metricFindQuery('region()')
return $scope.datasource.metricFindQuery('regions()')
.then($scope.transformToSegments(true));
};
$scope.getNamespaces = function() {
return $scope.datasource.metricFindQuery('namespace()')
return $scope.datasource.metricFindQuery('namespaces()')
.then($scope.transformToSegments(true));
};
......
......@@ -108,17 +108,27 @@ describe('CloudWatchDatasource', function() {
};
});
it('should return suggest list for region()', function(done) {
var query = 'region()';
ctx.ds.metricFindQuery(query).then(function(result) {
describe('regions()', () => {
let params, result;
beforeEach(() => {
ctx.backendSrv.datasourceRequest = args => {
params = args;
return ctx.$q.when({data: [{text: 'us-east-1'}]});
};
ctx.ds.metricFindQuery("regions()").then(args => {
result = args;
});
ctx.$rootScope.$apply();
});
it('should issue __GetRegions request', () => {
expect(result[0].text).to.contain('us-east-1');
done();
expect(params.data.action).to.be('__GetRegions');
});
ctx.$rootScope.$apply();
});
it('should return suggest list for namespace()', function(done) {
var query = 'namespace()';
var query = 'namespaces()';
ctx.ds.metricFindQuery(query).then(function(result) {
result = result.map(function(v) { return v.text; });
expect(result).to.contain('AWS/EC2');
......
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