浏览代码

support POST for query and query_range

Mitsuhiro Tanda 8 年之前
父节点
当前提交
a83ede0193

+ 8 - 2
pkg/api/pluginproxy/ds_proxy.go

@@ -189,8 +189,14 @@ func (proxy *DataSourceProxy) validateRequest() error {
 	}
 
 	if proxy.ds.Type == m.DS_PROMETHEUS {
-		if proxy.ctx.Req.Request.Method != http.MethodGet || !strings.HasPrefix(proxy.proxyPath, "api/") {
-			return errors.New("GET is only allowed on proxied Prometheus datasource")
+		if proxy.ctx.Req.Request.Method == "DELETE" {
+			return errors.New("Deletes not allowed on proxied Prometheus datasource")
+		}
+		if proxy.ctx.Req.Request.Method == "PUT" {
+			return errors.New("Puts not allowed on proxied Prometheus datasource")
+		}
+		if proxy.ctx.Req.Request.Method == "POST" && !(proxy.proxyPath == "api/v1/query" || proxy.proxyPath == "api/v1/query_range") {
+			return errors.New("Posts not allowed on proxied Prometheus datasource except on /query and /query_range")
 		}
 	}
 

+ 43 - 15
public/app/plugins/datasource/prometheus/datasource.ts

@@ -1,5 +1,6 @@
 import _ from 'lodash';
 
+import $ from 'jquery';
 import kbn from 'app/core/utils/kbn';
 import * as dateMath from 'app/core/utils/datemath';
 import PrometheusMetricFindQuery from './metric_find_query';
@@ -20,6 +21,7 @@ export class PrometheusDatasource {
   withCredentials: any;
   metricsNameCache: any;
   interval: string;
+  httpMethod: string;
 
   /** @ngInject */
   constructor(instanceSettings, private $q, private backendSrv, private templateSrv, private timeSrv) {
@@ -32,14 +34,33 @@ export class PrometheusDatasource {
     this.basicAuth = instanceSettings.basicAuth;
     this.withCredentials = instanceSettings.withCredentials;
     this.interval = instanceSettings.jsonData.timeInterval || '15s';
+    this.httpMethod = instanceSettings.jsonData.httpMethod || 'GET';
   }
 
-  _request(method, url, requestId?) {
+  _request(method, url, data?, requestId?) {
     var options: any = {
       url: this.url + url,
       method: method,
       requestId: requestId,
     };
+    if (method === 'GET') {
+      if (!_.isEmpty(data)) {
+        options.url =
+          options.url +
+          '?' +
+          _.map(data, (v, k) => {
+            return encodeURIComponent(k) + '=' + encodeURIComponent(v);
+          }).join('&');
+      }
+    } else {
+      options.headers = {
+        'Content-Type': 'application/x-www-form-urlencoded',
+      };
+      options.transformRequest = data => {
+        return $.param(data);
+      };
+      options.data = data;
+    }
 
     if (this.basicAuth || this.withCredentials) {
       options.withCredentials = true;
@@ -173,21 +194,23 @@ export class PrometheusDatasource {
       throw { message: 'Invalid time range' };
     }
 
-    var url =
-      '/api/v1/query_range?query=' +
-      encodeURIComponent(query.expr) +
-      '&start=' +
-      start +
-      '&end=' +
-      end +
-      '&step=' +
-      query.step;
-    return this._request('GET', url, query.requestId);
+    var url = '/api/v1/query_range';
+    var data = {
+      query: query.expr,
+      start: start,
+      end: end,
+      step: query.step,
+    };
+    return this._request(this.httpMethod, url, data, query.requestId);
   }
 
   performInstantQuery(query, time) {
-    var url = '/api/v1/query?query=' + encodeURIComponent(query.expr) + '&time=' + time;
-    return this._request('GET', url, query.requestId);
+    var url = '/api/v1/query';
+    var data = {
+      query: query.expr,
+      time: time,
+    };
+    return this._request(this.httpMethod, url, data, query.requestId);
   }
 
   performSuggestQuery(query, cache = false) {
@@ -279,8 +302,13 @@ export class PrometheusDatasource {
   }
 
   testDatasource() {
-    return this.metricFindQuery('metrics(.*)').then(function() {
-      return { status: 'success', message: 'Data source is working' };
+    let now = new Date().getTime();
+    return this.performInstantQuery({ expr: '1+1' }, now / 1000).then(response => {
+      if (response.data.status === 'success') {
+        return { status: 'success', message: 'Data source is working' };
+      } else {
+        return { status: 'error', message: response.error };
+      }
     });
   }
 

+ 14 - 4
public/app/plugins/datasource/prometheus/partials/config.html

@@ -4,13 +4,23 @@
 <div class="gf-form-group">
 	<div class="gf-form-inline">
 		<div class="gf-form">
-			<span class="gf-form-label">Scrape interval</span>
-			<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="15s"></input>
+			<span class="gf-form-label width-8">Scrape interval</span>
+			<input type="text" class="gf-form-input width-8" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="15s"></input>
 			<info-popover mode="right-absolute">
-                Set this to your global scrape interval defined in your Prometheus config file. This will be used as a lower limit for 
+                Set this to your global scrape interval defined in your Prometheus config file. This will be used as a lower limit for
                 the Prometheus step query parameter.
 			</info-popover>
 		</div>
-	</div>
+  </div>
+
+  <div class="gf-form">
+    <label class="gf-form-label width-8">HTTP Method</label>
+    <div class="gf-form-select-wrapper width-8 gf-form-select-wrapper--has-help-icon">
+      <select class="gf-form-input" ng-model="ctrl.current.jsonData.httpMethod" ng-options="method for method in ['GET', 'POST']"></select>
+      <info-popover mode="right-absolute">
+        Specify the HTTP Method to query Prometheus. (POST is only available in Prometheus >= v2.1.0)
+      </info-popover>
+    </div>
+  </div>
 </div>
 

+ 69 - 1
public/app/plugins/datasource/prometheus/specs/datasource_specs.ts

@@ -1,5 +1,6 @@
 import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common';
 import moment from 'moment';
+import $ from 'jquery';
 import helpers from 'test/specs/helpers';
 import { PrometheusDatasource } from '../datasource';
 
@@ -10,7 +11,7 @@ describe('PrometheusDatasource', function() {
     directUrl: 'direct',
     user: 'test',
     password: 'mupp',
-    jsonData: {},
+    jsonData: { httpMethod: 'GET' },
   };
 
   beforeEach(angularMocks.module('grafana.core'));
@@ -652,3 +653,70 @@ describe('PrometheusDatasource', function() {
     });
   });
 });
+
+describe('PrometheusDatasource for POST', function() {
+  var ctx = new helpers.ServiceTestContext();
+  var instanceSettings = {
+    url: 'proxied',
+    directUrl: 'direct',
+    user: 'test',
+    password: 'mupp',
+    jsonData: { httpMethod: 'POST' },
+  };
+
+  beforeEach(angularMocks.module('grafana.core'));
+  beforeEach(angularMocks.module('grafana.services'));
+  beforeEach(ctx.providePhase(['timeSrv']));
+
+  beforeEach(
+    angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
+      ctx.$q = $q;
+      ctx.$httpBackend = $httpBackend;
+      ctx.$rootScope = $rootScope;
+      ctx.ds = $injector.instantiate(PrometheusDatasource, { instanceSettings: instanceSettings });
+      $httpBackend.when('GET', /\.html$/).respond('');
+    })
+  );
+
+  describe('When querying prometheus with one target using query editor target spec', function() {
+    var results;
+    var urlExpected = 'proxied/api/v1/query_range';
+    var dataExpected = $.param({
+      query: 'test{job="testjob"}',
+      start: 1443438675,
+      end: 1443460275,
+      step: 60,
+    });
+    var query = {
+      range: { from: moment(1443438674760), to: moment(1443460274760) },
+      targets: [{ expr: 'test{job="testjob"}', format: 'time_series' }],
+      interval: '60s',
+    };
+    var response = {
+      status: 'success',
+      data: {
+        resultType: 'matrix',
+        result: [
+          {
+            metric: { __name__: 'test', job: 'testjob' },
+            values: [[1443454528, '3846']],
+          },
+        ],
+      },
+    };
+    beforeEach(function() {
+      ctx.$httpBackend.expectPOST(urlExpected, dataExpected).respond(response);
+      ctx.ds.query(query).then(function(data) {
+        results = data;
+      });
+      ctx.$httpBackend.flush();
+    });
+    it('should generate the correct query', function() {
+      ctx.$httpBackend.verifyNoOutstandingExpectation();
+    });
+    it('should return series list', function() {
+      expect(results.data.length).to.be(1);
+      expect(results.data[0].target).to.be('test{job="testjob"}');
+    });
+  });
+});

+ 1 - 1
public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts

@@ -12,7 +12,7 @@ describe('PrometheusMetricFindQuery', function() {
     directUrl: 'direct',
     user: 'test',
     password: 'mupp',
-    jsonData: {},
+    jsonData: { httpMethod: 'GET' },
   };
 
   beforeEach(angularMocks.module('grafana.core'));