Browse Source

feat(datasource): cancel in flight data source requests, refeatoring #5321

Torkel Ödegaard 9 năm trước cách đây
mục cha
commit
81e9aa4de4

+ 2 - 0
pkg/api/dataproxy.go

@@ -63,6 +63,8 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
 			req.Header.Add("Authorization", dsAuth)
 		}
 
+		time.Sleep(time.Second * 5)
+
 		// clear cookie headers
 		req.Header.Del("Cookie")
 		req.Header.Del("Set-Cookie")

+ 0 - 175
public/app/core/services/backend_srv.js

@@ -1,175 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  '../core_module',
-  'app/core/config',
-],
-function (angular, _, coreModule, config) {
-  'use strict';
-
-  coreModule.default.service('backendSrv', function($http, alertSrv, $timeout, $q) {
-    var self = this;
-
-    this.get = function(url, params) {
-      return this.request({ method: 'GET', url: url, params: params });
-    };
-
-    this.delete = function(url) {
-      return this.request({ method: 'DELETE', url: url });
-    };
-
-    this.post = function(url, data) {
-      return this.request({ method: 'POST', url: url, data: data });
-    };
-
-    this.patch = function(url, data) {
-      return this.request({ method: 'PATCH', url: url, data: data });
-    };
-
-    this.put = function(url, data) {
-      return this.request({ method: 'PUT', url: url, data: data });
-    };
-
-    this._handleError = function(err) {
-      return function() {
-        if (err.isHandled) {
-          return;
-        }
-
-        var data = err.data || { message: 'Unexpected error' };
-        if (_.isString(data)) {
-          data = { message: data };
-        }
-
-        if (err.status === 422) {
-          alertSrv.set("Validation failed", data.message, "warning", 4000);
-          throw data;
-        }
-
-        data.severity = 'error';
-
-        if (err.status < 500) {
-          data.severity = "warning";
-        }
-
-        if (data.message) {
-          alertSrv.set("Problem!", data.message, data.severity, 10000);
-        }
-
-        throw data;
-      };
-    };
-
-    this.request = function(options) {
-      options.retry = options.retry || 0;
-      var requestIsLocal = options.url.indexOf('/') === 0;
-      var firstAttempt = options.retry === 0;
-
-      if (requestIsLocal && !options.hasSubUrl) {
-        options.url = config.appSubUrl + options.url;
-        options.hasSubUrl = true;
-      }
-
-      return $http(options).then(function(results) {
-        if (options.method !== 'GET') {
-          if (results && results.data.message) {
-            alertSrv.set(results.data.message, '', 'success', 3000);
-          }
-        }
-        return results.data;
-      }, function(err) {
-        // handle unauthorized
-        if (err.status === 401 && firstAttempt) {
-          return self.loginPing().then(function() {
-            options.retry = 1;
-            return self.request(options);
-          });
-        }
-
-        $timeout(self._handleError(err), 50);
-        throw err;
-      });
-    };
-
-    var datasourceInFlightRequests = {};
-    var HTTP_REQUEST_ABORTED = -1;
-    this.datasourceRequest = function(options) {
-      options.retry = options.retry || 0;
-
-      // A requestID is provided by the datasource as a unique identifier for a
-      // particular query. If the requestID exists, the promise it is keyed to
-      // is canceled, canceling the previous datasource request if it is still
-      // in-flight.
-      var canceler;
-      if (options.requestID) {
-        if (canceler = datasourceInFlightRequests[options.requestID]) {
-          canceler.resolve();
-        }
-        canceler = $q.defer();
-        options.timeout = canceler.promise;
-        datasourceInFlightRequests[options.requestID] = canceler;
-      }
-
-      var requestIsLocal = options.url.indexOf('/') === 0;
-      var firstAttempt = options.retry === 0;
-
-      if (requestIsLocal && options.headers && options.headers.Authorization) {
-        options.headers['X-DS-Authorization'] = options.headers.Authorization;
-        delete options.headers.Authorization;
-      }
-
-      return $http(options).then(null, function(err) {
-        if (err.status === HTTP_REQUEST_ABORTED) {
-          // TODO: Hitting refresh before the original request returns cancels
-          // the "loading" animation on the panes, but it should continue to be
-          // visible.
-          err.statusText = "request aborted";
-          return err;
-        }
-
-        // handle unauthorized for backend requests
-        if (requestIsLocal && firstAttempt && err.status === 401) {
-          return self.loginPing().then(function() {
-            options.retry = 1;
-            if (canceler) {
-              canceler.resolve();
-            }
-            return self.datasourceRequest(options);
-          });
-        }
-
-        //populate error obj on Internal Error
-        if (_.isString(err.data) && err.status === 500) {
-          err.data = {
-            error: err.statusText
-          };
-        }
-
-        // for Prometheus
-        if (!err.data.message && _.isString(err.data.error)) {
-          err.data.message = err.data.error;
-        }
-
-        throw err;
-      });
-    };
-
-    this.loginPing = function() {
-      return this.request({url: '/api/login/ping', method: 'GET', retry: 1 });
-    };
-
-    this.search = function(query) {
-      return this.get('/api/search', query);
-    };
-
-    this.getDashboard = function(type, slug) {
-      return this.get('/api/dashboards/' + type + '/' + slug);
-    };
-
-    this.saveDashboard = function(dash, options) {
-      options = (options || {});
-      return this.post('/api/dashboards/db/', {dashboard: dash, overwrite: options.overwrite === true});
-    };
-
-  });
-});

+ 177 - 0
public/app/core/services/backend_srv.ts

@@ -0,0 +1,177 @@
+///<reference path="../../headers/common.d.ts" />
+
+import angular from 'angular';
+import _ from 'lodash';
+import config from 'app/core/config';
+import coreModule from 'app/core/core_module';
+
+export class BackendSrv {
+  inFlightRequests = {};
+  HTTP_REQUEST_CANCELLED = -1;
+
+    /** @ngInject */
+  constructor(private $http, private alertSrv, private $rootScope, private $q, private $timeout) {
+  }
+
+  get(url, params?) {
+    return this.request({ method: 'GET', url: url, params: params });
+  }
+
+  delete(url) {
+    return this.request({ method: 'DELETE', url: url });
+  }
+
+  post(url, data) {
+    return this.request({ method: 'POST', url: url, data: data });
+  };
+
+  patch(url, data) {
+    return this.request({ method: 'PATCH', url: url, data: data });
+  }
+
+  put(url, data) {
+    return this.request({ method: 'PUT', url: url, data: data });
+  }
+
+  requestErrorHandler(err) {
+    if (err.isHandled) {
+      return;
+    }
+
+    var data = err.data || { message: 'Unexpected error' };
+    if (_.isString(data)) {
+      data = { message: data };
+    }
+
+    if (err.status === 422) {
+      this.alertSrv.set("Validation failed", data.message, "warning", 4000);
+      throw data;
+    }
+
+    data.severity = 'error';
+
+    if (err.status < 500) {
+      data.severity = "warning";
+    }
+
+    if (data.message) {
+      this.alertSrv.set("Problem!", data.message, data.severity, 10000);
+    }
+
+    throw data;
+  }
+
+  request(options) {
+    options.retry = options.retry || 0;
+    var requestIsLocal = options.url.indexOf('/') === 0;
+    var firstAttempt = options.retry === 0;
+
+    if (requestIsLocal && !options.hasSubUrl) {
+      options.url = config.appSubUrl + options.url;
+      options.hasSubUrl = true;
+    }
+
+    return this.$http(options).then(results => {
+      if (options.method !== 'GET') {
+        if (results && results.data.message) {
+          this.alertSrv.set(results.data.message, '', 'success', 3000);
+        }
+      }
+      return results.data;
+    }, err => {
+      // handle unauthorized
+      if (err.status === 401 && firstAttempt) {
+        return this.loginPing().then(() => {
+          options.retry = 1;
+          return this.request(options);
+        });
+      }
+
+      this.$timeout(this.requestErrorHandler.bind(this), 50);
+      throw err;
+    });
+  };
+
+  datasourceRequest(options) {
+    options.retry = options.retry || 0;
+
+    // A requestID is provided by the datasource as a unique identifier for a
+    // particular query. If the requestID exists, the promise it is keyed to
+    // is canceled, canceling the previous datasource request if it is still
+    // in-flight.
+    var canceler;
+    if (options.requestId) {
+      canceler = this.inFlightRequests[options.requestId];
+      if (canceler) {
+        canceler.resolve();
+      }
+      // create new canceler
+      canceler = this.$q.defer();
+      options.timeout = canceler.promise;
+      this.inFlightRequests[options.requestId] = canceler;
+    }
+
+    var requestIsLocal = options.url.indexOf('/') === 0;
+    var firstAttempt = options.retry === 0;
+
+    if (requestIsLocal && options.headers && options.headers.Authorization) {
+      options.headers['X-DS-Authorization'] = options.headers.Authorization;
+      delete options.headers.Authorization;
+    }
+
+    return this.$http(options).catch(err => {
+      if (err.status === this.HTTP_REQUEST_CANCELLED) {
+        throw {err, cancelled: true};
+      }
+
+      // handle unauthorized for backend requests
+      if (requestIsLocal && firstAttempt && err.status === 401) {
+        return this.loginPing().then(() => {
+          options.retry = 1;
+          if (canceler) {
+            canceler.resolve();
+          }
+          return this.datasourceRequest(options);
+        });
+      }
+
+      //populate error obj on Internal Error
+      if (_.isString(err.data) && err.status === 500) {
+        err.data = {
+          error: err.statusText
+        };
+      }
+
+      // for Prometheus
+      if (!err.data.message && _.isString(err.data.error)) {
+        err.data.message = err.data.error;
+      }
+
+      throw err;
+    }).finally(() => {
+      // clean up
+      if (options.requestId) {
+        delete this.inFlightRequests[options.requestId];
+      }
+    });
+  };
+
+  loginPing() {
+    return this.request({url: '/api/login/ping', method: 'GET', retry: 1 });
+  }
+
+  search(query) {
+    return this.get('/api/search', query);
+  }
+
+  getDashboard(type, slug) {
+    return this.get('/api/dashboards/' + type + '/' + slug);
+  }
+
+  saveDashboard(dash, options) {
+    options = (options || {});
+    return this.post('/api/dashboards/db/', {dashboard: dash, overwrite: options.overwrite === true});
+  }
+}
+
+coreModule.service('backendSrv', BackendSrv);

+ 8 - 6
public/app/features/panel/metrics_panel_ctrl.ts

@@ -12,7 +12,7 @@ import * as dateMath from 'app/core/utils/datemath';
 import {Subject} from 'vendor/npm/rxjs/Subject';
 
 class MetricsPanelCtrl extends PanelCtrl {
-  error: boolean;
+  error: any;
   loading: boolean;
   datasource: any;
   datasourceName: any;
@@ -86,8 +86,14 @@ class MetricsPanelCtrl extends PanelCtrl {
     .then(this.issueQueries.bind(this))
     .then(this.handleQueryResult.bind(this))
     .catch(err => {
+      // if cancelled  keep loading set to true
+      if (err.cancelled) {
+        console.log('Panel request cancelled', err);
+        return;
+      }
+
       this.loading = false;
-      this.error = err.message || "Timeseries data request error";
+      this.error = err.message || "Request Error";
       this.inspector = {error: err};
       this.events.emit('data-error', err);
       console.log('Panel data error:', err);
@@ -182,10 +188,6 @@ class MetricsPanelCtrl extends PanelCtrl {
       cacheTimeout: this.panel.cacheTimeout
     };
 
-    metricsQuery.targets.forEach(function(target) {
-      target.exprID = target.refId + metricsQuery.panelId;
-    });
-
     return datasource.query(metricsQuery);
   }
 

+ 11 - 18
public/app/plugins/datasource/graphite/datasource.ts

@@ -30,13 +30,17 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
       return $q.when({data: []});
     }
 
-    var httpOptions: any = {method: this.render_method, url: '/render'};
+    var httpOptions: any = {
+      method: 'POST',
+      url: '/render',
+      data: params.join('&'),
+      headers: {
+        'Content-Type': 'application/x-www-form-urlencoded'
+      },
+    };
 
-    if (httpOptions.method === 'GET') {
-      httpOptions.url = httpOptions.url + '?' + params.join('&');
-    } else {
-      httpOptions.data = params.join('&');
-      httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
+    if (options.panelId) {
+      httpOptions.requestId = 'panel' + options.panelId;
     }
 
     return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs);
@@ -177,17 +181,6 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
     });
   };
 
-  this.listDashboards = function(query) {
-    return this.doGraphiteRequest({ method: 'GET',  url: '/dashboard/find/', params: {query: query || ''} })
-    .then(function(results) {
-      return results.data.dashboards;
-    });
-  };
-
-  this.loadDashboard = function(dashName) {
-    return this.doGraphiteRequest({method: 'GET', url: '/dashboard/load/' + encodeURIComponent(dashName) });
-  };
-
   this.doGraphiteRequest = function(options) {
     if (this.basicAuth || this.withCredentials) {
       options.withCredentials = true;
@@ -198,7 +191,7 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
     }
 
     options.url = this.url + options.url;
-    options.inspect = { type: 'graphite' };
+    options.inspect = {type: 'graphite'};
 
     return backendSrv.datasourceRequest(options);
   };

+ 7 - 9
public/app/plugins/datasource/prometheus/datasource.ts

@@ -21,11 +21,11 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
   this.withCredentials = instanceSettings.withCredentials;
   this.lastErrors = {};
 
-  this._request = function(method, url, requestID) {
+  this._request = function(method, url, requestId) {
     var options: any = {
       url: this.url + url,
       method: method,
-      requestID: requestID,
+      requestId: requestId,
     };
 
     if (this.basicAuth || this.withCredentials) {
@@ -77,7 +77,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
 
       var query: any = {};
       query.expr = templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr);
-      query.requestID = target.exprID;
+      query.requestId = target.expr;
 
       var interval = target.interval || options.interval;
       var intervalFactor = target.intervalFactor || 1;
@@ -103,14 +103,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
       return this.performTimeSeriesQuery(query, start, end);
     }, this));
 
-    return $q.all(allQueryPromise)
-    .then(function(allResponse) {
+    return $q.all(allQueryPromise).then(function(allResponse) {
       var result = [];
 
       _.each(allResponse, function(response, index) {
-        if (response.status === HTTP_REQUEST_ABORTED) {
-          return;
-        }
         if (response.status === 'error') {
           self.lastErrors.query = response.error;
           throw response.error;
@@ -128,7 +124,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
 
   this.performTimeSeriesQuery = function(query, start, end) {
     var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step;
-    return this._request('GET', url, query.requestID);
+    return this._request('GET', url, query.requestId);
   };
 
   this.performSuggestQuery = function(query) {
@@ -175,9 +171,11 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
       expr: interpolated,
       step: '60s'
     };
+
     var start = getPrometheusTime(options.range.from, false);
     var end = getPrometheusTime(options.range.to, true);
     var self = this;
+
     return this.performTimeSeriesQuery(query, start, end).then(function(results) {
       var eventList = [];
       tagKeys = tagKeys.split(',');