| |
@@ -17,6 +17,8 @@
|
| |
# Author: Josef Skladanka <jskladan@redhat.com>
|
| |
|
| |
import requests
|
| |
+ from requests.adapters import HTTPAdapter
|
| |
+ from requests.packages.urllib3.util.retry import Retry
|
| |
import json
|
| |
import inspect
|
| |
import simplejson
|
| |
@@ -62,18 +64,10 @@
|
| |
return repr(self.message)
|
| |
|
| |
|
| |
- class ResultsDBapi(object):
|
| |
-
|
| |
- def __init__(self, api_url, auth_token=None):
|
| |
- # remove trailing slash(es), so we don't generate
|
| |
- # urls with a double slash which breaks werkzeug
|
| |
- # https://github.com/mitsuhiko/werkzeug/issues/491
|
| |
- self.url = api_url.rstrip('/')
|
| |
- self.auth_token = auth_token
|
| |
-
|
| |
- def __raise_on_error(self, r):
|
| |
+ class _ResultsDBSession(requests.Session):
|
| |
+ def __resultsdb_raise_on_error(self, r):
|
| |
if r.ok:
|
| |
- return
|
| |
+ return r
|
| |
|
| |
try:
|
| |
logger.warn('Received HTTP failure status code %s for request: %s',
|
| |
@@ -81,11 +75,48 @@
|
| |
raise ResultsDBapiException(
|
| |
'%s (HTTP %s)' % (r.json()['message'], r.status_code), r)
|
| |
except simplejson.JSONDecodeError as e:
|
| |
- logger.debug('Received invalid JSON data: %s\n%s', e, r.text)
|
| |
+ logger.debug('Could not parse JSON data: %s\n%s', e, r.text)
|
| |
raise ResultsDBapiException(
|
| |
- 'Invalid JSON (HTTP %s): %s' % (r.status_code, e), r)
|
| |
+ '(HTTP %s)' % r.status_code, r)
|
| |
except KeyError:
|
| |
- raise ResultsDBapiException('HTTP %s Error' % r.status_code, r)
|
| |
+ logger.debug('JSON data in unexpected format: %s\n%s', e, r.text)
|
| |
+ raise ResultsDBapiException('Unexpected JSON data (HTTP %s): %s' % (r.status_code, r.json()), r)
|
| |
+
|
| |
+ raise ResultsDBapiException('Unknown Error', r)
|
| |
+
|
| |
+ def get(self, *args, **kwargs):
|
| |
+ try:
|
| |
+ r = super(_ResultsDBSession, self).get(*args, **kwargs)
|
| |
+ except requests.exceptions.RetryError as e:
|
| |
+ raise ResultsDBapiException('Maximum number of retries exceeded: %s' % e.message, None)
|
| |
+ return self.__resultsdb_raise_on_error(r)
|
| |
+
|
| |
+ def post(self, *args, **kwargs):
|
| |
+ try:
|
| |
+ r = super(_ResultsDBSession, self).post(*args, **kwargs)
|
| |
+ except requests.exceptions.RetryError as e:
|
| |
+ raise ResultsDBapiException('Maximum number of retries exceeded: %s' % e.message, None)
|
| |
+ return self.__resultsdb_raise_on_error(r)
|
| |
+
|
| |
+ class ResultsDBapi(object):
|
| |
+
|
| |
+ def __init__(self, api_url, auth_token=None, max_retries=3, backoff_factor=1.0):
|
| |
+ # remove trailing slash(es), so we don't generate
|
| |
+ # urls with a double slash which breaks werkzeug
|
| |
+ # https://github.com/mitsuhiko/werkzeug/issues/491
|
| |
+ self.url = api_url.rstrip('/')
|
| |
+ self.auth_token = auth_token
|
| |
+
|
| |
+ self._retry = Retry(
|
| |
+ total=max_retries,
|
| |
+ backoff_factor=backoff_factor,
|
| |
+ status_forcelist=(500, 502, 503, 504),
|
| |
+ method_whitelist=False # Enables POST retries
|
| |
+ )
|
| |
+ self._adapter = HTTPAdapter(max_retries=self._retry)
|
| |
+ self.session = _ResultsDBSession()
|
| |
+ self.session.mount("http://", self._adapter)
|
| |
+ self.session.mount("https://", self._adapter)
|
| |
|
| |
def __prepare_params(self, params_all):
|
| |
params = {}
|
| |
@@ -117,8 +148,7 @@
|
| |
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
| |
data = _fparams()
|
| |
data['_auth_token'] = self.auth_token
|
| |
- r = requests.post(url, data=json.dumps(data), headers=headers)
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.post(url, data=json.dumps(data), headers=headers)
|
| |
|
| |
return r.json()
|
| |
|
| |
@@ -128,28 +158,27 @@
|
| |
data['ref_url'] = ref_url
|
| |
if description is not _KEEP:
|
| |
data['description'] = description
|
| |
- if data:
|
| |
- data['uuid'] = uuid
|
| |
+ if not data:
|
| |
+ return self.get_group(uuid)
|
| |
+
|
| |
+ data['uuid'] = uuid
|
| |
data['_auth_token'] = self.auth_token
|
| |
|
| |
url = "%s/groups" % self.url
|
| |
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
| |
- r = requests.post(url, data=json.dumps(data), headers=headers)
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.post(url, data=json.dumps(data), headers=headers)
|
| |
|
| |
return r.json()
|
| |
|
| |
def get_group(self, uuid):
|
| |
url = "%s/groups/%s" % (self.url, uuid)
|
| |
- r = requests.get(url)
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.get(url)
|
| |
|
| |
return r.json()
|
| |
|
| |
def get_groups(self, page=None, limit=None, description=None, description_like=None, uuid=None):
|
| |
url = "%s/groups" % self.url
|
| |
- r = requests.get(url, params=self.__prepare_params(_fparams()))
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.get(url, params=self.__prepare_params(_fparams()))
|
| |
|
| |
return r.json()
|
| |
|
| |
@@ -158,22 +187,19 @@
|
| |
data = _fparams(expand_kwargs=False)
|
| |
data['_auth_token'] = self.auth_token
|
| |
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
| |
- r = requests.post(url, data=json.dumps(data), headers=headers)
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.post(url, data=json.dumps(data), headers=headers)
|
| |
|
| |
return r.json()
|
| |
|
| |
def get_result(self, id):
|
| |
url = "%s/results/%s" % (self.url, id)
|
| |
- r = requests.get(url)
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.get(url)
|
| |
|
| |
return r.json()
|
| |
|
| |
def get_results(self, page=None, limit=None, since=None, outcome=None, groups=None, testcases=None, testcases_like=None, raw_params=None, **kwargs):
|
| |
url = "%s/results" % self.url
|
| |
- r = requests.get(url, params=self.__prepare_params(_fparams()))
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.get(url, params=self.__prepare_params(_fparams()))
|
| |
|
| |
return r.json()
|
| |
|
| |
@@ -182,8 +208,7 @@
|
| |
data = _fparams()
|
| |
data['_auth_token'] = self.auth_token
|
| |
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
| |
- r = requests.post(url, data=json.dumps(data), headers=headers)
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.post(url, data=json.dumps(data), headers=headers)
|
| |
|
| |
return r.json()
|
| |
|
| |
@@ -191,27 +216,26 @@
|
| |
data = {}
|
| |
if ref_url is not _KEEP:
|
| |
data['ref_url'] = ref_url
|
| |
- if data:
|
| |
- data['name'] = name
|
| |
+ if not data:
|
| |
+ return self.get_testcase(name)
|
| |
+
|
| |
+ data['name'] = name
|
| |
data['_auth_token'] = self.auth_token
|
| |
|
| |
url = "%s/testcases" % self.url
|
| |
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
| |
- r = requests.post(url, data=json.dumps(data), headers=headers)
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.post(url, data=json.dumps(data), headers=headers)
|
| |
|
| |
return r.json()
|
| |
|
| |
def get_testcase(self, name):
|
| |
url = "%s/testcases/%s" % (self.url, name)
|
| |
- r = requests.get(url)
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.get(url)
|
| |
|
| |
return r.json()
|
| |
|
| |
def get_testcases(self, page=None, limit=None, name=None, name_like=None):
|
| |
url = "%s/testcases" % self.url
|
| |
- r = requests.get(url, params=self.__prepare_params(_fparams()))
|
| |
- self.__raise_on_error(r)
|
| |
+ r = self.session.get(url, params=self.__prepare_params(_fparams()))
|
| |
|
| |
return r.json()
|
| |
Adds
urllib3
-style retries for HTTP 500 errors on any request. ResultsDB app does not return HTTP 500 on its own, anywhere in the code, but some weird network/db/apache/proxy error causes these randomly.This might remedy the situation a bit.