![Daniel Gollub](/assets/img/avatar_default.png)
Replace HTTPSConnection in NEC plugin PFC driver with Requests. SSL Verification is from now on enabled by default. This changes the default behaviour and is the primary intention of this change: verify SSL certificates. This might break existing configuration/setups where the SSL certificate used by the NEC PFC driver would not pass the verification. SecurityImpact DocImpact Partial-Bug: 1188189 Change-Id: I1e5fdc9c2ed5b812aa6509d1639bd499acc5c337
159 lines
6.3 KiB
Python
159 lines
6.3 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
#
|
|
# 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 json
|
|
import time
|
|
|
|
import requests
|
|
|
|
from neutron.openstack.common import excutils
|
|
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 = json.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 = json.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)
|