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 {
}
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")
}
}
......
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 '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;
}
_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 };
}
});
}
......
import { PrometheusDatasource } from './datasource';
import { PrometheusQueryCtrl } from './query_ctrl';
class PrometheusConfigCtrl {
static templateUrl = 'partials/config.html';
}
import { PrometheusConfigCtrl } from './config_ctrl';
class PrometheusAnnotationsQueryCtrl {
static templateUrl = 'partials/annotations.editor.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>
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"}');
});
});
});
......@@ -12,7 +12,7 @@ describe('PrometheusMetricFindQuery', function() {
directUrl: 'direct',
user: 'test',
password: 'mupp',
jsonData: {},
jsonData: { httpMethod: 'GET' },
};
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