mirror of https://github.com/grafana/grafana.git
				
				
				
			Merge pull request #9859 from mtanda/prometheus_post
(prometheus) support POST for query and query_range
This commit is contained in:
		
						commit
						4edc49bfab
					
				|  | @ -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") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| 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'; | ||||
|   } | ||||
| } | ||||
|  | @ -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; | ||||
|   } | ||||
| 
 | ||||
|   _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 }; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,9 +1,6 @@ | |||
| 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 | ||||
|                 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> | ||||
| 
 | ||||
|  |  | |||
|  | @ -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"}'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ describe('PrometheusMetricFindQuery', function() { | |||
|     directUrl: 'direct', | ||||
|     user: 'test', | ||||
|     password: 'mupp', | ||||
|     jsonData: {}, | ||||
|     jsonData: { httpMethod: 'GET' }, | ||||
|   }; | ||||
| 
 | ||||
|   beforeEach(angularMocks.module('grafana.core')); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue