Commit 4edc49bf by Carl Bergquist Committed by GitHub

Merge pull request #9859 from mtanda/prometheus_post

(prometheus) support POST for query and query_range
parents f1fc1672 4e1501b1
...@@ -189,8 +189,14 @@ func (proxy *DataSourceProxy) validateRequest() error { ...@@ -189,8 +189,14 @@ func (proxy *DataSourceProxy) validateRequest() error {
} }
if proxy.ds.Type == m.DS_PROMETHEUS { if proxy.ds.Type == m.DS_PROMETHEUS {
if proxy.ctx.Req.Request.Method != http.MethodGet || !strings.HasPrefix(proxy.proxyPath, "api/") { if proxy.ctx.Req.Request.Method == "DELETE" {
return errors.New("GET is only allowed on proxied Prometheus datasource") 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")
} }
} }
......
export class PrometheusConfigCtrl {
static templateUrl = 'public/app/plugins/datasource/prometheus/partials/config.html';
current: any;
/** @ngInject */
constructor($scope) {
this.current.jsonData.httpMethod = this.current.jsonData.httpMethod || 'GET';
}
}
import _ from 'lodash'; import _ from 'lodash';
import $ from 'jquery';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import * as dateMath from 'app/core/utils/datemath'; import * as dateMath from 'app/core/utils/datemath';
import PrometheusMetricFindQuery from './metric_find_query'; import PrometheusMetricFindQuery from './metric_find_query';
...@@ -20,6 +21,7 @@ export class PrometheusDatasource { ...@@ -20,6 +21,7 @@ export class PrometheusDatasource {
withCredentials: any; withCredentials: any;
metricsNameCache: any; metricsNameCache: any;
interval: string; interval: string;
httpMethod: string;
/** @ngInject */ /** @ngInject */
constructor(instanceSettings, private $q, private backendSrv, private templateSrv, private timeSrv) { constructor(instanceSettings, private $q, private backendSrv, private templateSrv, private timeSrv) {
...@@ -32,14 +34,33 @@ export class PrometheusDatasource { ...@@ -32,14 +34,33 @@ export class PrometheusDatasource {
this.basicAuth = instanceSettings.basicAuth; this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = instanceSettings.withCredentials; this.withCredentials = instanceSettings.withCredentials;
this.interval = instanceSettings.jsonData.timeInterval || '15s'; this.interval = instanceSettings.jsonData.timeInterval || '15s';
this.httpMethod = instanceSettings.jsonData.httpMethod;
} }
_request(method, url, requestId?) { _request(method, url, data?, requestId?) {
var options: any = { var options: any = {
url: this.url + url, url: this.url + url,
method: method, method: method,
requestId: requestId, 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) { if (this.basicAuth || this.withCredentials) {
options.withCredentials = true; options.withCredentials = true;
...@@ -173,21 +194,23 @@ export class PrometheusDatasource { ...@@ -173,21 +194,23 @@ export class PrometheusDatasource {
throw { message: 'Invalid time range' }; throw { message: 'Invalid time range' };
} }
var url = var url = '/api/v1/query_range';
'/api/v1/query_range?query=' + var data = {
encodeURIComponent(query.expr) + query: query.expr,
'&start=' + start: start,
start + end: end,
'&end=' + step: query.step,
end + };
'&step=' + return this._request(this.httpMethod, url, data, query.requestId);
query.step;
return this._request('GET', url, query.requestId);
} }
performInstantQuery(query, time) { performInstantQuery(query, time) {
var url = '/api/v1/query?query=' + encodeURIComponent(query.expr) + '&time=' + time; var url = '/api/v1/query';
return this._request('GET', url, query.requestId); var data = {
query: query.expr,
time: time,
};
return this._request(this.httpMethod, url, data, query.requestId);
} }
performSuggestQuery(query, cache = false) { performSuggestQuery(query, cache = false) {
...@@ -279,8 +302,13 @@ export class PrometheusDatasource { ...@@ -279,8 +302,13 @@ export class PrometheusDatasource {
} }
testDatasource() { testDatasource() {
return this.metricFindQuery('metrics(.*)').then(function() { let now = new Date().getTime();
return { status: 'success', message: 'Data source is working' }; 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 };
}
}); });
} }
......
import { PrometheusDatasource } from './datasource'; import { PrometheusDatasource } from './datasource';
import { PrometheusQueryCtrl } from './query_ctrl'; import { PrometheusQueryCtrl } from './query_ctrl';
import { PrometheusConfigCtrl } from './config_ctrl';
class PrometheusConfigCtrl {
static templateUrl = 'partials/config.html';
}
class PrometheusAnnotationsQueryCtrl { class PrometheusAnnotationsQueryCtrl {
static templateUrl = 'partials/annotations.editor.html'; static templateUrl = 'partials/annotations.editor.html';
......
...@@ -4,13 +4,23 @@ ...@@ -4,13 +4,23 @@
<div class="gf-form-group"> <div class="gf-form-group">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label">Scrape interval</span> <span class="gf-form-label width-8">Scrape interval</span>
<input type="text" class="gf-form-input width-6" ng-model="ctrl.current.jsonData.timeInterval" spellcheck='false' placeholder="15s"></input> <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"> <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. the Prometheus step query parameter.
</info-popover> </info-popover>
</div> </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> </div>
import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common'; import { describe, beforeEach, it, expect, angularMocks } from 'test/lib/common';
import moment from 'moment'; import moment from 'moment';
import $ from 'jquery';
import helpers from 'test/specs/helpers'; import helpers from 'test/specs/helpers';
import { PrometheusDatasource } from '../datasource'; import { PrometheusDatasource } from '../datasource';
...@@ -10,7 +11,7 @@ describe('PrometheusDatasource', function() { ...@@ -10,7 +11,7 @@ describe('PrometheusDatasource', function() {
directUrl: 'direct', directUrl: 'direct',
user: 'test', user: 'test',
password: 'mupp', password: 'mupp',
jsonData: {}, jsonData: { httpMethod: 'GET' },
}; };
beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.core'));
...@@ -652,3 +653,70 @@ describe('PrometheusDatasource', function() { ...@@ -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"}');
});
});
});
...@@ -12,7 +12,7 @@ describe('PrometheusMetricFindQuery', function() { ...@@ -12,7 +12,7 @@ describe('PrometheusMetricFindQuery', function() {
directUrl: 'direct', directUrl: 'direct',
user: 'test', user: 'test',
password: 'mupp', password: 'mupp',
jsonData: {}, jsonData: { httpMethod: 'GET' },
}; };
beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.core'));
......
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