Quellcode durchsuchen

Merge pull request #3632 from mtanda/cloudwatch_annotation_describe_alarms

(cloudwatch) multiple CloudWatch alarm annotation support
Carl Bergquist vor 9 Jahren
Ursprung
Commit
f788c8cb37

+ 44 - 0
pkg/api/cloudwatch/cloudwatch.go

@@ -33,6 +33,7 @@ func init() {
 	actionHandlers = map[string]actionHandler{
 	actionHandlers = map[string]actionHandler{
 		"GetMetricStatistics":     handleGetMetricStatistics,
 		"GetMetricStatistics":     handleGetMetricStatistics,
 		"ListMetrics":             handleListMetrics,
 		"ListMetrics":             handleListMetrics,
+		"DescribeAlarms":          handleDescribeAlarms,
 		"DescribeAlarmsForMetric": handleDescribeAlarmsForMetric,
 		"DescribeAlarmsForMetric": handleDescribeAlarmsForMetric,
 		"DescribeAlarmHistory":    handleDescribeAlarmHistory,
 		"DescribeAlarmHistory":    handleDescribeAlarmHistory,
 		"DescribeInstances":       handleDescribeInstances,
 		"DescribeInstances":       handleDescribeInstances,
@@ -142,6 +143,49 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
 	c.JSON(200, resp)
 	c.JSON(200, resp)
 }
 }
 
 
+func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
+	cfg := &aws.Config{
+		Region:      aws.String(req.Region),
+		Credentials: getCredentials(req.DataSource.Database),
+	}
+
+	svc := cloudwatch.New(session.New(cfg), cfg)
+
+	reqParam := &struct {
+		Parameters struct {
+			ActionPrefix    string    `json:"actionPrefix"`
+			AlarmNamePrefix string    `json:"alarmNamePrefix"`
+			AlarmNames      []*string `json:"alarmNames"`
+			StateValue      string    `json:"stateValue"`
+		} `json:"parameters"`
+	}{}
+	json.Unmarshal(req.Body, reqParam)
+
+	params := &cloudwatch.DescribeAlarmsInput{
+		MaxRecords: aws.Int64(100),
+	}
+	if reqParam.Parameters.ActionPrefix != "" {
+		params.ActionPrefix = aws.String(reqParam.Parameters.ActionPrefix)
+	}
+	if reqParam.Parameters.AlarmNamePrefix != "" {
+		params.AlarmNamePrefix = aws.String(reqParam.Parameters.AlarmNamePrefix)
+	}
+	if len(reqParam.Parameters.AlarmNames) != 0 {
+		params.AlarmNames = reqParam.Parameters.AlarmNames
+	}
+	if reqParam.Parameters.StateValue != "" {
+		params.StateValue = aws.String(reqParam.Parameters.StateValue)
+	}
+
+	resp, err := svc.DescribeAlarms(params)
+	if err != nil {
+		c.JsonApiErr(500, "Unable to call AWS API", err)
+		return
+	}
+
+	c.JSON(200, resp)
+}
+
 func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
 func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
 	cfg := &aws.Config{
 	cfg := &aws.Config{
 		Region:      aws.String(req.Region),
 		Region:      aws.String(req.Region),

+ 2 - 0
public/app/plugins/datasource/cloudwatch/annotation_query.d.ts

@@ -0,0 +1,2 @@
+declare var test: any;
+export default test;

+ 105 - 0
public/app/plugins/datasource/cloudwatch/annotation_query.js

@@ -0,0 +1,105 @@
+define([
+  'lodash',
+],
+function (_) {
+  'use strict';
+
+  function CloudWatchAnnotationQuery(datasource, annotation, $q, templateSrv) {
+    this.datasource = datasource;
+    this.annotation = annotation;
+    this.$q = $q;
+    this.templateSrv = templateSrv;
+  }
+
+  CloudWatchAnnotationQuery.prototype.process = function(from, to) {
+    var self = this;
+    var usePrefixMatch = this.annotation.prefixMatching;
+    var region = this.templateSrv.replace(this.annotation.region);
+    var namespace = this.templateSrv.replace(this.annotation.namespace);
+    var metricName = this.templateSrv.replace(this.annotation.metricName);
+    var dimensions = this.datasource.convertDimensionFormat(this.annotation.dimensions);
+    var statistics = _.map(this.annotation.statistics, function(s) { return self.templateSrv.replace(s); });
+    var defaultPeriod = usePrefixMatch ? '' : '300';
+    var period = this.annotation.period || defaultPeriod;
+    period = parseInt(period, 10);
+    var actionPrefix = this.annotation.actionPrefix || '';
+    var alarmNamePrefix = this.annotation.alarmNamePrefix || '';
+
+    var d = this.$q.defer();
+    var allQueryPromise;
+    if (usePrefixMatch) {
+      allQueryPromise = [
+        this.datasource.performDescribeAlarms(region, actionPrefix, alarmNamePrefix, [], '').then(function(alarms) {
+          alarms.MetricAlarms = self.filterAlarms(alarms, namespace, metricName, dimensions, statistics, period);
+          return alarms;
+        })
+      ];
+    } else {
+      if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return this.$q.when([]); }
+
+      allQueryPromise = _.map(statistics, function(statistic) {
+        return self.datasource.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
+      });
+    }
+    this.$q.all(allQueryPromise).then(function(alarms) {
+      var eventList = [];
+
+      var start = self.datasource.convertToCloudWatchTime(from, false);
+      var end = self.datasource.convertToCloudWatchTime(to, true);
+      _.chain(alarms)
+      .pluck('MetricAlarms')
+      .flatten()
+      .each(function(alarm) {
+        if (!alarm) {
+          d.resolve(eventList);
+          return;
+        }
+
+        self.datasource.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
+          _.each(history.AlarmHistoryItems, function(h) {
+            var event = {
+              annotation: self.annotation,
+              time: Date.parse(h.Timestamp),
+              title: h.AlarmName,
+              tags: [h.HistoryItemType],
+              text: h.HistorySummary
+            };
+
+            eventList.push(event);
+          });
+
+          d.resolve(eventList);
+        });
+      });
+    });
+
+    return d.promise;
+  };
+
+  CloudWatchAnnotationQuery.prototype.filterAlarms = function(alarms, namespace, metricName, dimensions, statistics, period) {
+    return _.filter(alarms.MetricAlarms, function(alarm) {
+      if (!_.isEmpty(namespace) && alarm.Namespace !== namespace) {
+        return false;
+      }
+      if (!_.isEmpty(metricName) && alarm.MetricName !== metricName) {
+        return false;
+      }
+      var sd = function(d) {
+        return d.Name;
+      };
+      var isSameDimensions = JSON.stringify(_.sortBy(alarm.Dimensions, sd)) === JSON.stringify(_.sortBy(dimensions, sd));
+      if (!_.isEmpty(dimensions) && !isSameDimensions) {
+        return false;
+      }
+      if (!_.isEmpty(statistics) && !_.contains(statistics, alarm.Statistic)) {
+        return false;
+      }
+      if (!_.isNaN(period) && alarm.Period !== period) {
+        return false;
+      }
+      return true;
+    });
+  };
+
+  return CloudWatchAnnotationQuery;
+});

+ 21 - 58
public/app/plugins/datasource/cloudwatch/datasource.js

@@ -3,8 +3,9 @@ define([
   'lodash',
   'lodash',
   'moment',
   'moment',
   'app/core/utils/datemath',
   'app/core/utils/datemath',
+  './annotation_query',
 ],
 ],
-function (angular, _, moment, dateMath) {
+function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) {
   'use strict';
   'use strict';
 
 
   /** @ngInject */
   /** @ngInject */
@@ -15,9 +16,10 @@ function (angular, _, moment, dateMath) {
     this.proxyUrl = instanceSettings.url;
     this.proxyUrl = instanceSettings.url;
     this.defaultRegion = instanceSettings.jsonData.defaultRegion;
     this.defaultRegion = instanceSettings.jsonData.defaultRegion;
 
 
+    var self = this;
     this.query = function(options) {
     this.query = function(options) {
-      var start = convertToCloudWatchTime(options.range.from, false);
-      var end = convertToCloudWatchTime(options.range.to, true);
+      var start = self.convertToCloudWatchTime(options.range.from, false);
+      var end = self.convertToCloudWatchTime(options.range.to, true);
 
 
       var queries = [];
       var queries = [];
       options = angular.copy(options);
       options = angular.copy(options);
@@ -30,7 +32,7 @@ function (angular, _, moment, dateMath) {
         query.region = templateSrv.replace(target.region, options.scopedVars);
         query.region = templateSrv.replace(target.region, options.scopedVars);
         query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
         query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
         query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
         query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
-        query.dimensions = convertDimensionFormat(target.dimensions, options.scopedVars);
+        query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
         query.statistics = target.statistics;
         query.statistics = target.statistics;
 
 
         var range = end - start;
         var range = end - start;
@@ -117,7 +119,7 @@ function (angular, _, moment, dateMath) {
         parameters: {
         parameters: {
           namespace: templateSrv.replace(namespace),
           namespace: templateSrv.replace(namespace),
           metricName: templateSrv.replace(metricName),
           metricName: templateSrv.replace(metricName),
-          dimensions: convertDimensionFormat(filterDimensions, {}),
+          dimensions: this.convertDimensionFormat(filterDimensions, {}),
         }
         }
       };
       };
 
 
@@ -206,6 +208,14 @@ function (angular, _, moment, dateMath) {
       return $q.when([]);
       return $q.when([]);
     };
     };
 
 
+    this.performDescribeAlarms = function(region, actionPrefix, alarmNamePrefix, alarmNames, stateValue) {
+      return this.awsRequest({
+        region: region,
+        action: 'DescribeAlarms',
+        parameters: { actionPrefix: actionPrefix, alarmNamePrefix: alarmNamePrefix, alarmNames: alarmNames, stateValue: stateValue }
+      });
+    };
+
     this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
     this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
       return this.awsRequest({
       return this.awsRequest({
         region: region,
         region: region,
@@ -223,55 +233,8 @@ function (angular, _, moment, dateMath) {
     };
     };
 
 
     this.annotationQuery = function(options) {
     this.annotationQuery = function(options) {
-      var annotation = options.annotation;
-      var region = templateSrv.replace(annotation.region);
-      var namespace = templateSrv.replace(annotation.namespace);
-      var metricName = templateSrv.replace(annotation.metricName);
-      var dimensions = convertDimensionFormat(annotation.dimensions);
-      var statistics = _.map(annotation.statistics, function(s) { return templateSrv.replace(s); });
-      var period = annotation.period || '300';
-      period = parseInt(period, 10);
-
-      if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return $q.when([]); }
-
-      var d = $q.defer();
-      var self = this;
-      var allQueryPromise = _.map(statistics, function(statistic) {
-        return self.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
-      });
-      $q.all(allQueryPromise).then(function(alarms) {
-        var eventList = [];
-
-        var start = convertToCloudWatchTime(options.range.from, false);
-        var end = convertToCloudWatchTime(options.range.to, true);
-        _.chain(alarms)
-        .pluck('MetricAlarms')
-        .flatten()
-        .each(function(alarm) {
-          if (!alarm) {
-            d.resolve(eventList);
-            return;
-          }
-
-          self.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
-            _.each(history.AlarmHistoryItems, function(h) {
-              var event = {
-                annotation: annotation,
-                time: Date.parse(h.Timestamp),
-                title: h.AlarmName,
-                tags: [h.HistoryItemType],
-                text: h.HistorySummary
-              };
-
-              eventList.push(event);
-            });
-
-            d.resolve(eventList);
-          });
-        });
-      });
-
-      return d.promise;
+      var annotationQuery = new CloudWatchAnnotationQuery(this, options.annotation, $q, templateSrv);
+      return annotationQuery.process(options.range.from, options.range.to);
     };
     };
 
 
     this.testDatasource = function() {
     this.testDatasource = function() {
@@ -347,21 +310,21 @@ function (angular, _, moment, dateMath) {
       });
       });
     }
     }
 
 
-    function convertToCloudWatchTime(date, roundUp) {
+    this.convertToCloudWatchTime = function(date, roundUp) {
       if (_.isString(date)) {
       if (_.isString(date)) {
         date = dateMath.parse(date, roundUp);
         date = dateMath.parse(date, roundUp);
       }
       }
       return Math.round(date.valueOf() / 1000);
       return Math.round(date.valueOf() / 1000);
-    }
+    };
 
 
-    function convertDimensionFormat(dimensions, scopedVars) {
+    this.convertDimensionFormat = function(dimensions, scopedVars) {
       return _.map(dimensions, function(value, key) {
       return _.map(dimensions, function(value, key) {
         return {
         return {
           Name: templateSrv.replace(key, scopedVars),
           Name: templateSrv.replace(key, scopedVars),
           Value: templateSrv.replace(value, scopedVars)
           Value: templateSrv.replace(value, scopedVars)
         };
         };
       });
       });
-    }
+    };
 
 
   }
   }
 
 

+ 18 - 0
public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html

@@ -1 +1,19 @@
 <cloudwatch-query-parameter target="ctrl.annotation" datasource="ctrl.datasource"></cloudwatch-query-parameter>
 <cloudwatch-query-parameter target="ctrl.annotation" datasource="ctrl.datasource"></cloudwatch-query-parameter>
+<div class="editor-row">
+	<div class="section">
+		<h5>Prefix matching</h5>
+		<div class="editor-option">
+			<editor-checkbox text="Enable" model="ctrl.annotation.prefixMatching"></editor-checkbox>
+		</div>
+
+		<div class="editor-option" ng-if="ctrl.annotation.prefixMatching">
+			<label class="small">Action</label>
+			<input type="text" class="input-small" ng-model='ctrl.annotation.actionPrefix'></input>
+		</div>
+
+		<div class="editor-option" ng-if="ctrl.annotation.prefixMatching">
+			<label class="small">Alarm Name</label>
+			<input type="text" class="input-small" ng-model='ctrl.annotation.alarmNamePrefix'></input>
+		</div>
+	</div>
+</div>

+ 81 - 0
public/app/plugins/datasource/cloudwatch/specs/annotation_query_specs.ts

@@ -0,0 +1,81 @@
+import "../datasource";
+import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
+import moment from 'moment';
+import helpers from 'test/specs/helpers';
+import {CloudWatchDatasource} from "../datasource";
+import CloudWatchAnnotationQuery from '../annotation_query';
+
+describe('CloudWatchAnnotationQuery', function() {
+  var ctx = new helpers.ServiceTestContext();
+  var instanceSettings = {
+    jsonData: {defaultRegion: 'us-east-1', access: 'proxy'},
+  };
+
+  beforeEach(angularMocks.module('grafana.core'));
+  beforeEach(angularMocks.module('grafana.services'));
+  beforeEach(angularMocks.module('grafana.controllers'));
+  beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
+
+  beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
+    ctx.$q = $q;
+    ctx.$httpBackend =  $httpBackend;
+    ctx.$rootScope = $rootScope;
+    ctx.ds = $injector.instantiate(CloudWatchDatasource, {instanceSettings: instanceSettings});
+  }));
+
+  describe('When performing annotationQuery', function() {
+    var parameter = {
+      annotation: {
+        region: 'us-east-1',
+        namespace: 'AWS/EC2',
+        metricName: 'CPUUtilization',
+        dimensions: {
+          InstanceId: 'i-12345678'
+        },
+        statistics: ['Average'],
+        period: 300
+      },
+      range: {
+        from: moment(1443438674760),
+        to: moment(1443460274760)
+      }
+    };
+    var alarmResponse = {
+      MetricAlarms: [
+        {
+          AlarmName: 'test_alarm_name'
+        }
+      ]
+    };
+    var historyResponse = {
+      AlarmHistoryItems: [
+        {
+          Timestamp: '2015-01-01T00:00:00.000Z',
+          HistoryItemType: 'StateUpdate',
+          AlarmName: 'test_alarm_name',
+          HistoryData: '{}',
+          HistorySummary: 'test_history_summary'
+        }
+      ]
+    };
+    beforeEach(function() {
+      ctx.backendSrv.datasourceRequest = function(params) {
+        switch (params.data.action) {
+        case 'DescribeAlarmsForMetric':
+          return ctx.$q.when({data: alarmResponse});
+        case 'DescribeAlarmHistory':
+          return ctx.$q.when({data: historyResponse});
+        }
+      };
+    });
+    it('should return annotation list', function(done) {
+      var annotationQuery = new CloudWatchAnnotationQuery(ctx.ds, parameter.annotation, ctx.$q, ctx.templateSrv);
+      annotationQuery.process(parameter.range.from, parameter.range.to).then(function(result) {
+        expect(result[0].title).to.be('test_alarm_name');
+        expect(result[0].text).to.be('test_history_summary');
+        done();
+      });
+      ctx.$rootScope.$apply();
+    });
+  });
+});

+ 0 - 55
public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts

@@ -187,59 +187,4 @@ describe('CloudWatchDatasource', function() {
       expect(scenario.request.data.action).to.be('ListMetrics');
       expect(scenario.request.data.action).to.be('ListMetrics');
     });
     });
   });
   });
-
-  describe('When performing annotationQuery', function() {
-    var parameter = {
-      annotation: {
-        region: 'us-east-1',
-        namespace: 'AWS/EC2',
-        metricName: 'CPUUtilization',
-        dimensions: {
-          InstanceId: 'i-12345678'
-        },
-        statistics: ['Average'],
-        period: 300
-      },
-      range: {
-        from: moment(1443438674760),
-        to: moment(1443460274760)
-      }
-    };
-    var alarmResponse = {
-      MetricAlarms: [
-        {
-          AlarmName: 'test_alarm_name'
-        }
-      ]
-    };
-    var historyResponse = {
-      AlarmHistoryItems: [
-        {
-          Timestamp: '2015-01-01T00:00:00.000Z',
-          HistoryItemType: 'StateUpdate',
-          AlarmName: 'test_alarm_name',
-          HistoryData: '{}',
-          HistorySummary: 'test_history_summary'
-        }
-      ]
-    };
-    beforeEach(function() {
-      ctx.backendSrv.datasourceRequest = function(params) {
-        switch (params.data.action) {
-        case 'DescribeAlarmsForMetric':
-          return ctx.$q.when({data: alarmResponse});
-        case 'DescribeAlarmHistory':
-          return ctx.$q.when({data: historyResponse});
-        }
-      };
-    });
-    it('should return annotation list', function(done) {
-      ctx.ds.annotationQuery(parameter).then(function(result) {
-        expect(result[0].title).to.be('test_alarm_name');
-        expect(result[0].text).to.be('test_history_summary');
-        done();
-      });
-      ctx.$rootScope.$apply();
-    });
-  });
 });
 });