Bladeren bron

CloudWatch proxy support

Mitsuhiro Tanda 10 jaren geleden
bovenliggende
commit
00f76ecaf6

+ 8 - 4
pkg/api/dataproxy.go

@@ -67,8 +67,12 @@ func ProxyDataSourceRequest(c *middleware.Context) {
 		return
 	}
 
-	proxyPath := c.Params("*")
-	proxy := NewReverseProxy(&query.Result, proxyPath)
-	proxy.Transport = dataProxyTransport
-	proxy.ServeHTTP(c.RW(), c.Req.Request)
+	if query.Result.Type == m.DS_CLOUDWATCH {
+		ProxyCloudWatchDataSourceRequest(c)
+	} else {
+		proxyPath := c.Params("*")
+		proxy := NewReverseProxy(&query.Result, proxyPath)
+		proxy.Transport = dataProxyTransport
+		proxy.ServeHTTP(c.RW(), c.Req.Request)
+	}
 }

+ 107 - 0
pkg/api/dataproxy_cloudwatch.go

@@ -0,0 +1,107 @@
+package api
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/service/cloudwatch"
+	"github.com/grafana/grafana/pkg/middleware"
+)
+
+func ProxyCloudWatchDataSourceRequest(c *middleware.Context) {
+	body, _ := ioutil.ReadAll(c.Req.Request.Body)
+
+	reqInfo := &struct {
+		Region  string `json:"region"`
+		Service string `json:"service"`
+		Action  string `json:"action"`
+	}{}
+	json.Unmarshal([]byte(body), reqInfo)
+
+	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 []map[string]string `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)
+
+		statistics := make([]*string, 0)
+		for k := range reqParam.Parameters.Statistics {
+			statistics = append(statistics, &reqParam.Parameters.Statistics[k])
+		}
+		dimensions := make([]*cloudwatch.Dimension, 0)
+		for _, d := range reqParam.Parameters.Dimensions {
+			dimensions = append(dimensions, &cloudwatch.Dimension{
+				Name:  aws.String(d["Name"]),
+				Value: aws.String(d["Value"]),
+			})
+		}
+
+		params := &cloudwatch.GetMetricStatisticsInput{
+			Namespace:  aws.String(reqParam.Parameters.Namespace),
+			MetricName: aws.String(reqParam.Parameters.MetricName),
+			Dimensions: dimensions,
+			Statistics: 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 []map[string]string `json:"Dimensions"`
+			} `json:"parameters"`
+		}{}
+		json.Unmarshal([]byte(body), reqParam)
+
+		dimensions := make([]*cloudwatch.DimensionFilter, 0)
+		for _, d := range reqParam.Parameters.Dimensions {
+			dimensions = append(dimensions, &cloudwatch.DimensionFilter{
+				Name:  aws.String(d["Name"]),
+				Value: aws.String(d["Value"]),
+			})
+		}
+
+		params := &cloudwatch.ListMetricsInput{
+			Namespace:  aws.String(reqParam.Parameters.Namespace),
+			MetricName: aws.String(reqParam.Parameters.MetricName),
+			Dimensions: 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))
+	}
+}

+ 1 - 0
pkg/models/datasource.go

@@ -11,6 +11,7 @@ const (
 	DS_INFLUXDB_08   = "influxdb_08"
 	DS_ES            = "elasticsearch"
 	DS_OPENTSDB      = "opentsdb"
+	DS_CLOUDWATCH    = "cloudwatch"
 	DS_ACCESS_DIRECT = "direct"
 	DS_ACCESS_PROXY  = "proxy"
 )

+ 41 - 8
public/app/plugins/datasource/cloudwatch/datasource.js

@@ -18,6 +18,8 @@ function (angular, _, kbn) {
       this.type = 'cloudwatch';
       this.name = datasource.name;
       this.supportMetrics = true;
+      this.proxyMode = (datasource.jsonData.access === 'proxy');
+      this.proxyUrl = datasource.url;
 
       this.defaultRegion = datasource.jsonData.defaultRegion;
       this.credentials = {
@@ -194,9 +196,9 @@ function (angular, _, kbn) {
           };
         });
         query.statistics = getActivatedStatistics(target.statistics);
-        query.period = target.period;
+        query.period = parseInt(target.period, 10);
 
-        var range = (end.getTime() - start.getTime()) / 1000;
+        var range = end - start;
         // CloudWatch limit datapoints up to 1440
         if (range / query.period >= 1440) {
           query.period = Math.floor(range / 1440 / 60) * 60;
@@ -400,11 +402,42 @@ function (angular, _, kbn) {
     };
 
     CloudWatchDatasource.prototype.getCloudWatchClient = function(region) {
-      return new AWS.CloudWatch({
-        region: region,
-        accessKeyId: this.credentials.accessKeyId,
-        secretAccessKey: this.credentials.secretAccessKey
-      });
+      if (!this.proxyMode) {
+        return new AWS.CloudWatch({
+          region: region,
+          accessKeyId: this.credentials.accessKeyId,
+          secretAccessKey: this.credentials.secretAccessKey
+        });
+      } else {
+        var self = this;
+        var generateRequestProxy = function(service, action) {
+          return function(params, callback) {
+            var data = {
+              region: region,
+              service: service,
+              action: action,
+              parameters: params
+            };
+
+            var options = {
+              method: 'POST',
+              url: self.proxyUrl,
+              data: data
+            };
+
+            $http(options).then(function(response) {
+              callback(null, response.data);
+            }, function(err) {
+              callback(err, []);
+            });
+          };
+        };
+
+        return {
+          getMetricStatistics: generateRequestProxy('CloudWatch', 'GetMetricStatistics'),
+          listMetrics: generateRequestProxy('CloudWatch', 'ListMetrics')
+        };
+      }
     };
 
     CloudWatchDatasource.prototype.getDefaultRegion = function() {
@@ -440,7 +473,7 @@ function (angular, _, kbn) {
     }
 
     function convertToCloudWatchTime(date) {
-      return kbn.parseDate(date);
+      return Math.round(kbn.parseDate(date).getTime() / 1000);
     }
 
     return CloudWatchDatasource;

+ 8 - 0
public/app/plugins/datasource/cloudwatch/partials/config.html

@@ -9,6 +9,14 @@
       <input type="text" class="tight-form-input input-large" ng-model='current.jsonData.defaultRegion' placeholder="" required></input>
     </li>
   </ul>
+  <ul class="tight-form-list">
+    <li class="tight-form-item">
+      Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</label>
+    </li>
+    <li>
+      <select class="input-medium tight-form-input" ng-model="current.jsonData.access" ng-options="f for f in ['direct', 'proxy']" ng-init="current.jsonData.access = current.jsonData.access || 'direct'"></select>
+    </li>
+  </ul>
   <div class="clearfix"></div>
 </div>
 <div class="tight-form last">