def create_project(self, metadata, display_name=None, description=None):
""" Creating a project requires using the project_request endpoint. """
# TODO: handle admin-level project creation
w, stream = self._create_stream(None)
try:
proj_req = openshift_models.V1ProjectRequest(metadata=metadata, display_name=display_name, description=description)
openshift_apis.OapiApi(self.api_client).create_project_request(proj_req)
except ApiException as exc:
msg = json.loads(exc.body).get('message', exc.reason) if exc.body.startswith('{') else exc.body
raise OpenShiftException(msg, status=exc.status)
except MaxRetryError as ex:
raise OpenShiftException(str(ex.reason))
self._read_stream(w, stream, metadata.name)
return self._wait_for_response(metadata.name, None, 'create')
python类MaxRetryError()的实例源码
def get_object(self, name=None, namespace=None):
k8s_obj = None
method_name = 'list' if self.kind.endswith('list') else 'read'
try:
get_method = self.lookup_method(method_name, namespace)
if name is None and namespace is None:
k8s_obj = get_method()
elif name and namespace is None:
k8s_obj = get_method(name)
elif namespace and not name:
k8s_obj = get_method(namespace)
else:
k8s_obj = get_method(name, namespace)
except ApiException as exc:
if exc.status != 404:
if self.base_model_name == 'Project'and exc.status == 403:
pass
else:
msg = json.loads(exc.body).get('message', exc.reason) if exc.body.startswith('{') else exc.body
raise self.get_exception_class()(msg, status=exc.status)
except MaxRetryError as ex:
raise self.get_exception_class()(str(ex.reason))
return k8s_obj
def get_resource(self, resource, namespace="all"):
ret, resources = None, list()
try:
ret, namespaced_resource = self._call_api_client(resource)
except ApiException as ae:
self.logger.warning("resource autocomplete disabled, encountered "
"ApiException", exc_info=1)
except (NewConnectionError, MaxRetryError, ConnectTimeoutError):
self.logger.warning("unable to connect to k8 cluster", exc_info=1)
if ret:
for i in ret.items:
if namespace == "all" or not namespaced_resource:
resources.append((i.metadata.name, i.metadata.namespace))
elif namespace == i.metadata.namespace:
resources.append((i.metadata.name, i.metadata.namespace))
return resources
def on_get(self, req, resp):
token = req.get_param('token', True)
data = {}
for key in self.data_keys:
data[key] = req.get_param(key, True)
if not self.validate_token(token, data):
raise falcon.HTTPForbidden('Invalid token for these given values', '')
endpoint = self.config['iris']['hook']['gmail_one_click']
try:
result = self.iclient.post(endpoint, data)
except MaxRetryError:
logger.exception('Hitting iris-api failed for gmail oneclick')
else:
if result.status == 204:
resp.status = falcon.HTTP_204
return
else:
logger.error('Unexpected status code from api %s for gmail oneclick', result.status)
raise falcon.HTTPInternalServerError('Internal Server Error', 'Invalid response from API')
def on_post(self, req, resp):
"""
Accept twilio SMS webhook and forward to iris API
"""
try:
path = self.config['iris']['hook']['twilio_messages']
re = self.iclient.post(path, req.context['body'], raw=True)
except MaxRetryError as e:
logger.error(e.reason)
self.return_twixml_message('Connection error to web hook.', resp)
return
if re.status is not 200:
self.return_twixml_message(
'Got status code: %d, content: %s' % (re.status,
re.data[0:100]), resp)
return
else:
body = process_api_response(re.data)
self.return_twixml_message(body, resp)
return
def on_post(self, req, resp):
"""
Accept twilio POST that has message delivery status, and pass it
to iris-api
"""
try:
re = self.iclient.post(self.endpoint, req.context['body'], raw=True)
except MaxRetryError:
logger.exception('Failed posting data to iris-api')
raise falcon.HTTPInternalServerError('Internal Server Error', 'API call failed')
if re.status is not 204:
logger.error('Invalid response from API for delivery status update: %s', re.status)
raise falcon.HTTPBadRequest('Likely bad params passed', 'Invalid response from API')
resp.status = falcon.HTTP_204
def create_object(self, namespace=None, k8s_obj=None, body=None):
"""
Send a POST request to the API. Pass either k8s_obj or body.
:param namespace: namespace value or None
:param k8s_obj: optional k8s object model
:param body: optional JSON dict
:return: new object returned from the API
"""
self.logger.debug('Starting create object')
w, stream = self._create_stream(namespace)
return_obj = None
name = None
if k8s_obj:
name = k8s_obj.metadata.name
elif body:
name = body.get('metadata', {}).get('name', None)
try:
create_method = self.lookup_method('create', namespace)
if namespace:
if k8s_obj:
create_method(namespace, k8s_obj)
else:
create_method(namespace, body=body)
else:
if k8s_obj:
create_method(k8s_obj)
else:
create_method(body=body)
except ApiException as exc:
msg = json.loads(exc.body).get('message', exc.reason) if exc.body.startswith('{') else exc.body
raise self.get_exception_class()(msg, status=exc.status)
except MaxRetryError as ex:
raise self.get_exception_class()(str(ex.reason))
if stream is not None:
return_obj = self._read_stream(w, stream, name)
if not return_obj or self.kind in ('project', 'namespace'):
return_obj = self._wait_for_response(name, namespace, 'create')
return self.fix_serialization(return_obj)
def delete_object(self, name, namespace):
self.logger.debug('Starting delete object {0} {1} {2}'.format(self.kind, name, namespace))
delete_method = self.lookup_method('delete', namespace)
if not namespace:
try:
if 'body' in inspect.getargspec(delete_method).args:
status_obj = delete_method(name, body=V1DeleteOptions(propagation_policy='Foreground'))
else:
status_obj = delete_method(name)
except ApiException as exc:
msg = json.loads(exc.body).get('message', exc.reason)
raise self.get_exception_class()(msg, status=exc.status)
except MaxRetryError as ex:
raise self.get_exception_class()(str(ex.reason))
else:
try:
if 'body' in inspect.getargspec(delete_method).args:
status_obj = delete_method(name, namespace, body=V1DeleteOptions(propagation_policy='Foreground'))
else:
status_obj = delete_method(name, namespace)
except ApiException as exc:
msg = json.loads(exc.body).get('message', exc.reason) if exc.body.startswith('{') else exc.body
raise self.get_exception_class()(msg, status=exc.status)
except MaxRetryError as ex:
raise self.get_exception_class()(str(ex.reason))
if status_obj is None or status_obj.status == 'Failure':
msg = 'Failed to delete {}'.format(name)
if namespace is not None:
msg += ' in namespace {}'.format(namespace)
msg += ' status: {}'.format(status_obj)
raise self.get_exception_class()(msg)
self._wait_for_response(name, namespace, 'delete')
def test_timeout():
transport = Transport(urlparse.urlparse('http://localhost'))
responses.add('POST', '/', status=202,
body=MaxRetryError(None, None, reason=TimeoutError()))
with pytest.raises(TransportException) as exc_info:
transport.send('x', {}, timeout=5)
assert 'timeout' in str(exc_info.value)
def on_post(self, req, resp):
"""
Accept twilio gather callbacks and forward to iris API
"""
message_id = req.get_param('message_id')
# If we weren't given a message_id, this is an OOB message and there isn't
# anything to say, so hang up.
if not message_id:
self.return_twixml_call('Thank you', resp)
return
if not message_id.isdigit() and not uuid4hex.match(message_id):
raise falcon.HTTPBadRequest('Bad message id', 'message id must be int/hex')
try:
path = self.config['iris']['hook']['twilio_calls']
re = self.iclient.post(path, req.context['body'], raw=True, params={
'message_id': message_id
})
except MaxRetryError as e:
logger.error(e.reason)
self.return_twixml_call('Connection error to web hook.', resp)
return
if re.status is not 200:
self.return_twixml_call(
'Got status code: %d, content: %s' % (re.status,
re.data[0:100]), resp)
return
else:
body = process_api_response(re.data)
self.return_twixml_call(body, resp)
return
def on_post(self, req, resp):
"""
Accept slack's message from interactive buttons
"""
try:
form_post = falcon.uri.parse_query_string(req.context['body'])
payload = ujson.loads(form_post['payload'])
if not self.valid_token(payload['token']):
logger.error('Invalid token sent in the request.')
raise falcon.HTTPUnauthorized('Access denied',
'Not a valid auth token')
try:
msg_id = int(payload['callback_id'])
except KeyError as e:
logger.error('callback_id not found in the json payload.')
raise falcon.HTTPBadRequest('Bad Request', 'Callback id not found')
except ValueError as e:
logger.error('Callback ID not an integer: %s', payload['callback_id'])
raise falcon.HTTPBadRequest('Bad Request', 'Callback id must be int')
data = {'msg_id': msg_id,
'source': payload['user']['name'],
'content': payload['actions'][0]['name']}
endpoint = self.config['iris']['hook']['slack']
try:
result = self.iclient.post(endpoint, data)
except MaxRetryError as e:
logger.error(e.reason)
return
if result.status == 400:
raise falcon.HTTPBadRequest('Bad Request', '')
elif result.status is not 200:
raise falcon.HTTPInternalServerError('Internal Server Error', 'Unknown response from the api')
else:
content = process_api_response(result.data)
self.return_slack_message(resp, content)
return
except Exception:
logger.exception('Unable to read payload from slack. Our post body: %s', req.context['body'])
raise falcon.HTTPBadRequest('Bad Request', 'Unable to read the payload from slack')
def exec(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, body=None):
""" Execute a method against steemd RPC.
Warnings:
This command will auto-retry in case of node failure, as well as handle
node fail-over, unless we are broadcasting a transaction.
In latter case, the exception is **re-raised**.
"""
body = body or HttpClient.json_rpc_body(name, *args, api=api)
response = None
try:
response = self.request(body=body)
except (MaxRetryError,
ConnectionResetError,
ReadTimeoutError,
RemoteDisconnected,
ProtocolError) as e:
# if we broadcasted a transaction, always raise
# this is to prevent potential for double spend scenario
if api == 'network_broadcast_api':
raise e
# try switching nodes before giving up
if _ret_cnt > 2:
time.sleep(_ret_cnt) # we should wait only a short period before trying the next node, but still slowly increase backoff
elif _ret_cnt > 10:
raise e
self.next_node()
logging.debug('Switched node to %s due to exception: %s' %
(self.hostname, e.__class__.__name__))
return self.exec(name, *args,
return_with_args=return_with_args,
_ret_cnt=_ret_cnt + 1)
except Exception as e:
if self.re_raise:
raise e
else:
extra = dict(err=e, request=self.request)
logger.info('Request error', extra=extra)
return self._return(
response=response,
args=args,
return_with_args=return_with_args)
else:
if response.status not in tuple(
[*response.REDIRECT_STATUSES, 200]):
logger.info('non 200 response:%s', response.status)
return self._return(
response=response,
args=args,
return_with_args=return_with_args)
def replace_object(self, name, namespace, k8s_obj=None, body=None):
""" Replace an existing object. Pass in a model object or request dict().
Will first lookup the existing object to get the resource version and
update the request.
"""
self.logger.debug('Starting replace object')
existing_obj = self.get_object(name, namespace)
if not existing_obj:
msg = "Error: Replacing object. Unable to find {}".format(name)
msg += " in namespace {}".format(namespace) if namespace else ""
raise self.get_exception_class()(msg)
if k8s_obj:
k8s_obj.status = self.properties['status']['class']()
self.__remove_creation_timestamps(k8s_obj)
k8s_obj.metadata.resource_version = existing_obj.metadata.resource_version
elif body:
body['metadata']['resourceVersion'] = existing_obj.metadata.resource_version
w, stream = self._create_stream(namespace)
return_obj = None
try:
replace_method = self.lookup_method('replace', namespace)
if k8s_obj:
if namespace is None:
replace_method(name, k8s_obj)
else:
replace_method(name, namespace, k8s_obj)
else:
if namespace is None:
replace_method(name, body=body)
else:
replace_method(name, namespace, body=body)
except ApiException as exc:
msg = json.loads(exc.body).get('message', exc.reason) if exc.body.startswith('{') else exc.body
raise self.get_exception_class()(msg, status=exc.status)
except MaxRetryError as ex:
raise self.get_exception_class()(str(ex.reason))
if stream is not None:
return_obj = self._read_stream(w, stream, name)
if not return_obj or self.kind in ('project', 'namespace'):
return_obj = self._wait_for_response(name, namespace, 'replace')
return self.fix_serialization(return_obj)
def exec(self, name, *args, api=None, return_with_args=None, _ret_cnt=0, kwargs=None):
""" Execute a method against steemd RPC.
Warnings:
This command will auto-retry in case of node failure, as well as handle
node fail-over, unless we are broadcasting a transaction.
In latter case, the exception is **re-raised**.
"""
body = HttpClient.json_rpc_body(name, *args, api=api, kwargs=kwargs)
response = None
try:
response = self.request(body=body)
except (MaxRetryError,
ConnectionResetError,
ReadTimeoutError,
RemoteDisconnected,
ProtocolError) as e:
# if we broadcasted a transaction, always raise
# this is to prevent potential for double spend scenario
if api == 'network_broadcast_api':
raise e
# try switching nodes before giving up
if _ret_cnt > 2:
time.sleep(_ret_cnt) # we should wait only a short period before trying the next node, but still slowly increase backoff
if _ret_cnt > 10:
raise e
self.next_node()
logging.debug('Switched node to %s due to exception: %s' %
(self.hostname, e.__class__.__name__))
return self.exec(name, *args,
return_with_args=return_with_args,
_ret_cnt=_ret_cnt + 1)
except Exception as e:
if self.re_raise:
raise e
else:
extra = dict(err=e, request=self.request)
logger.info('Request error', extra=extra)
return self._return(
response=response,
args=args,
return_with_args=return_with_args)
else:
if response.status not in tuple(
[*response.REDIRECT_STATUSES, 200]):
logger.info('non 200 response:%s', response.status)
return self._return(
response=response,
args=args,
return_with_args=return_with_args)
def send(self, data, headers, timeout=None):
response = None
# ensure headers are byte strings
headers = {k.encode('ascii') if isinstance(k, compat.text_type) else k:
v.encode('ascii') if isinstance(v, compat.text_type) else v
for k, v in headers.items()}
if compat.PY2 and isinstance(self._url, compat.text_type):
url = self._url.encode('utf-8')
else:
url = self._url
try:
try:
response = self.http.urlopen(
'POST', url, body=data, headers=headers, timeout=timeout, preload_content=False
)
logger.info('Sent request, url=%s size=%.2fkb status=%s', url, len(data) / 1024.0, response.status)
except Exception as e:
print_trace = True
if isinstance(e, MaxRetryError) and isinstance(e.reason, TimeoutError):
message = (
"Connection to APM Server timed out "
"(url: %s, timeout: %s seconds)" % (self._url, timeout)
)
print_trace = False
else:
message = 'Unable to reach APM Server: %s (url: %s)' % (
e, self._url
)
raise TransportException(message, data, print_trace=print_trace)
body = response.read()
if response.status >= 400:
if response.status == 429: # rate-limited
message = 'Temporarily rate limited: '
print_trace = False
else:
message = 'HTTP %s: ' % response.status
print_trace = True
message += body.decode('utf8')
raise TransportException(message, data, print_trace=print_trace)
return response.getheader('Location')
finally:
if response:
response.close()
def request(self, url):
"""
Client request HTTP
:param str url: request uri
:return: urllib3.HTTPResponse
"""
if self._HTTP_DBG_LEVEL <= self.__debug.level:
self.__debug.debug_request(self._headers, url, self.__cfg.method)
try:
if self.__cfg.DEFAULT_SCAN == self.__cfg.scan:
response = self.__pool.request(self.__cfg.method,
helper.parse_url(url).path,
headers=self._headers,
retries=self.__cfg.retries,
assert_same_host=True,
redirect=False)
self.cookies_middleware(is_accept=self.__cfg.accept_cookies, response=response)
else:
response = PoolManager().request(self.__cfg.method, url,
headers=self._headers,
retries=self.__cfg.retries,
assert_same_host=False,
redirect=False)
return response
except MaxRetryError:
if self.__cfg.DEFAULT_SCAN == self.__cfg.scan:
self.__tpl.warning(key='max_retry_error', url=helper.parse_url(url).path)
pass
except HostChangedError as error:
self.__tpl.warning(key='host_changed_error', details=error)
pass
except ReadTimeoutError:
self.__tpl.warning(key='read_timeout_error', url=url)
pass
except ConnectTimeoutError:
self.__tpl.warning(key='connection_timeout_error', url=url)
pass
def exec(self, name, *args, api=None, return_with_args=None, _ret_cnt=0):
""" Execute a method against steemd RPC.
Warnings:
This command will auto-retry in case of node failure, as well as handle
node fail-over, unless we are broadcasting a transaction.
In latter case, the exception is **re-raised**.
"""
# rotate nodes to distribute the load
if self.round_robin:
self.next_node()
body = HttpClient.json_rpc_body(name, *args, api=api)
response = None
try:
response = self.request(body=body)
except (MaxRetryError,
ConnectionResetError,
ReadTimeoutError,
RemoteDisconnected,
ProtocolError) as e:
# try switching nodes before giving up
if _ret_cnt > 2:
time.sleep(5 * _ret_cnt)
elif _ret_cnt >= 10:
raise e
self.next_node()
logging.debug('Switched node to %s due to exception: %s' %
(self.hostname, e.__class__.__name__))
return self.exec(name, *args,
return_with_args=return_with_args,
_ret_cnt=_ret_cnt + 1)
except Exception as e:
if self.re_raise:
raise e
else:
extra = dict(err=e, request=self.request)
logger.info('Request error', extra=extra)
return self._return(
response=response,
args=args,
return_with_args=return_with_args)
else:
if response.status not in tuple(
[*response.REDIRECT_STATUSES, 200]):
logger.info('non 200 response:%s', response.status)
return self._return(
response=response,
args=args,
return_with_args=return_with_args)