
jsonutils provides multiple benefits in comparison to pure stdlib json (like using simplejson on Python 2.6). Similar patch was already merged before [1], but since it lacked hacking rule to enforce jsonutils usage, new occurrences of stdlib json module usage were introduced. This patch switches all the code to using jsonutils and adds a hacking rule to enforce the rule. The hacking rule requires that jsonutils module does not mimic as 'json' thru using import renames, so the code was updated not to rename the module when doing import. The hacking rule was shamelessly copied from the corresponding nova review [2]. [1]: https://review.openstack.org/#/c/99760/ [2]: https://review.openstack.org/111296/ Change-Id: Ie7a5bb76445e15cde9fbf9ff3d2101a014637b37
157 lines
6.3 KiB
Python
157 lines
6.3 KiB
Python
# Copyright 2012 NEC Corporation. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
# @author: Ryota MIBU
|
|
|
|
import time
|
|
|
|
import requests
|
|
|
|
from neutron.openstack.common import excutils
|
|
from neutron.openstack.common import jsonutils
|
|
from neutron.openstack.common import log as logging
|
|
from neutron.plugins.nec.common import config
|
|
from neutron.plugins.nec.common import exceptions as nexc
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class OFCClient(object):
|
|
"""A HTTP/HTTPS client for OFC Drivers."""
|
|
|
|
def __init__(self, host="127.0.0.1", port=8888, use_ssl=False,
|
|
key_file=None, cert_file=None, insecure_ssl=False):
|
|
"""Creates a new client to some OFC.
|
|
|
|
:param host: The host where service resides
|
|
:param port: The port where service resides
|
|
:param use_ssl: True to use SSL, False to use HTTP
|
|
:param key_file: The SSL key file to use if use_ssl is true
|
|
:param cert_file: The SSL cert file to use if use_ssl is true
|
|
:param insecure_ssl: Don't verify SSL certificate
|
|
"""
|
|
self.host = host
|
|
self.port = port
|
|
self.use_ssl = use_ssl
|
|
self.key_file = key_file
|
|
self.cert_file = cert_file
|
|
self.insecure_ssl = insecure_ssl
|
|
self.connection = None
|
|
|
|
def _format_error_message(self, status, detail):
|
|
detail = ' ' + detail if detail else ''
|
|
return (_("Operation on OFC failed: %(status)s%(msg)s") %
|
|
{'status': status, 'msg': detail})
|
|
|
|
def _get_response(self, method, action, body=None):
|
|
headers = {"Content-Type": "application/json"}
|
|
protocol = "http"
|
|
certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
|
|
certs = dict((x, certs[x]) for x in certs if certs[x] is not None)
|
|
verify = True
|
|
|
|
if self.use_ssl:
|
|
protocol = "https"
|
|
if self.insecure_ssl:
|
|
verify = False
|
|
|
|
url = "%s://%s:%d%s" % (protocol, self.host, int(self.port),
|
|
action)
|
|
|
|
res = requests.request(method, url, data=body, headers=headers,
|
|
cert=certs, verify=verify)
|
|
return res
|
|
|
|
def do_single_request(self, method, action, body=None):
|
|
action = config.OFC.path_prefix + action
|
|
LOG.debug(_("Client request: %(host)s:%(port)s "
|
|
"%(method)s %(action)s [%(body)s]"),
|
|
{'host': self.host, 'port': self.port,
|
|
'method': method, 'action': action, 'body': body})
|
|
if type(body) is dict:
|
|
body = jsonutils.dumps(body)
|
|
try:
|
|
res = self._get_response(method, action, body)
|
|
data = res.text
|
|
LOG.debug(_("OFC returns [%(status)s:%(data)s]"),
|
|
{'status': res.status_code,
|
|
'data': data})
|
|
|
|
# Try to decode JSON data if possible.
|
|
try:
|
|
data = jsonutils.loads(data)
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
if res.status_code in (requests.codes.OK,
|
|
requests.codes.CREATED,
|
|
requests.codes.ACCEPTED,
|
|
requests.codes.NO_CONTENT):
|
|
return data
|
|
elif res.status_code == requests.codes.SERVICE_UNAVAILABLE:
|
|
retry_after = res.headers.get('retry-after')
|
|
LOG.warning(_("OFC returns ServiceUnavailable "
|
|
"(retry-after=%s)"), retry_after)
|
|
raise nexc.OFCServiceUnavailable(retry_after=retry_after)
|
|
elif res.status_code == requests.codes.NOT_FOUND:
|
|
LOG.info(_("Specified resource %s does not exist on OFC "),
|
|
action)
|
|
raise nexc.OFCResourceNotFound(resource=action)
|
|
else:
|
|
LOG.warning(_("Operation on OFC failed: "
|
|
"status=%(status)s, detail=%(detail)s"),
|
|
{'status': res.status_code, 'detail': data})
|
|
params = {'reason': _("Operation on OFC failed"),
|
|
'status': res.status_code}
|
|
if isinstance(data, dict):
|
|
params['err_code'] = data.get('err_code')
|
|
params['err_msg'] = data.get('err_msg')
|
|
else:
|
|
params['err_msg'] = data
|
|
raise nexc.OFCException(**params)
|
|
except requests.exceptions.RequestException as e:
|
|
reason = _("Failed to connect OFC : %s") % e
|
|
LOG.error(reason)
|
|
raise nexc.OFCException(reason=reason)
|
|
|
|
def do_request(self, method, action, body=None):
|
|
max_attempts = config.OFC.api_max_attempts
|
|
for i in range(max_attempts, 0, -1):
|
|
try:
|
|
return self.do_single_request(method, action, body)
|
|
except nexc.OFCServiceUnavailable as e:
|
|
with excutils.save_and_reraise_exception() as ctxt:
|
|
try:
|
|
wait_time = int(e.retry_after)
|
|
except (ValueError, TypeError):
|
|
wait_time = None
|
|
if i > 1 and wait_time:
|
|
LOG.info(_("Waiting for %s seconds due to "
|
|
"OFC Service_Unavailable."), wait_time)
|
|
time.sleep(wait_time)
|
|
ctxt.reraise = False
|
|
continue
|
|
|
|
def get(self, action):
|
|
return self.do_request("GET", action)
|
|
|
|
def post(self, action, body=None):
|
|
return self.do_request("POST", action, body=body)
|
|
|
|
def put(self, action, body=None):
|
|
return self.do_request("PUT", action, body=body)
|
|
|
|
def delete(self, action):
|
|
return self.do_request("DELETE", action)
|