Merge "Replace HTTPSConnection in NEC plugin"
This commit is contained in:
commit
972fecc2ed
@ -45,6 +45,9 @@ firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewal
|
||||
# Certificate file
|
||||
# cert_file =
|
||||
|
||||
# Disable SSL certificate verification
|
||||
# insecure_ssl = false
|
||||
|
||||
# Maximum attempts per OFC API request. NEC plugin retries
|
||||
# API request to OFC when OFC returns ServiceUnavailable (503).
|
||||
# The value must be greater than 0.
|
||||
|
@ -51,6 +51,8 @@ ofc_opts = [
|
||||
help=_("Key file")),
|
||||
cfg.StrOpt('cert_file', default=None,
|
||||
help=_("Certificate file")),
|
||||
cfg.BoolOpt('insecure_ssl', default=False,
|
||||
help=_("Disable SSL certificate verification")),
|
||||
cfg.IntOpt('api_max_attempts', default=3,
|
||||
help=_("Maximum attempts per OFC API request."
|
||||
"NEC plugin retries API request to OFC "
|
||||
|
@ -15,11 +15,11 @@
|
||||
# under the License.
|
||||
# @author: Ryota MIBU
|
||||
|
||||
import httplib
|
||||
import json
|
||||
import socket
|
||||
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
|
||||
@ -33,7 +33,7 @@ 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):
|
||||
key_file=None, cert_file=None, insecure_ssl=False):
|
||||
"""Creates a new client to some OFC.
|
||||
|
||||
:param host: The host where service resides
|
||||
@ -41,35 +41,40 @@ class OFCClient(object):
|
||||
: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 get_connection(self):
|
||||
"""Returns the proper connection."""
|
||||
if self.use_ssl:
|
||||
connection_type = httplib.HTTPSConnection
|
||||
else:
|
||||
connection_type = httplib.HTTPConnection
|
||||
|
||||
# Open connection and send request, handling SSL certs
|
||||
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)
|
||||
if self.use_ssl and len(certs):
|
||||
conn = connection_type(self.host, self.port, **certs)
|
||||
else:
|
||||
conn = connection_type(self.host, self.port)
|
||||
return conn
|
||||
|
||||
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 "
|
||||
@ -79,13 +84,10 @@ class OFCClient(object):
|
||||
if type(body) is dict:
|
||||
body = json.dumps(body)
|
||||
try:
|
||||
conn = self.get_connection()
|
||||
headers = {"Content-Type": "application/json"}
|
||||
conn.request(method, action, body, headers)
|
||||
res = conn.getresponse()
|
||||
data = res.read()
|
||||
res = self._get_response(method, action, body)
|
||||
data = res.text
|
||||
LOG.debug(_("OFC returns [%(status)s:%(data)s]"),
|
||||
{'status': res.status,
|
||||
{'status': res.status_code,
|
||||
'data': data})
|
||||
|
||||
# Try to decode JSON data if possible.
|
||||
@ -94,33 +96,33 @@ class OFCClient(object):
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if res.status in (httplib.OK,
|
||||
httplib.CREATED,
|
||||
httplib.ACCEPTED,
|
||||
httplib.NO_CONTENT):
|
||||
if res.status_code in (requests.codes.OK,
|
||||
requests.codes.CREATED,
|
||||
requests.codes.ACCEPTED,
|
||||
requests.codes.NO_CONTENT):
|
||||
return data
|
||||
elif res.status == httplib.SERVICE_UNAVAILABLE:
|
||||
retry_after = res.getheader('retry-after')
|
||||
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 == httplib.NOT_FOUND:
|
||||
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, 'detail': data})
|
||||
{'status': res.status_code, 'detail': data})
|
||||
params = {'reason': _("Operation on OFC failed"),
|
||||
'status': res.status}
|
||||
'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 (socket.error, IOError) as e:
|
||||
except requests.exceptions.RequestException as e:
|
||||
reason = _("Failed to connect OFC : %s") % e
|
||||
LOG.error(reason)
|
||||
raise nexc.OFCException(reason=reason)
|
||||
|
@ -57,7 +57,8 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
|
||||
port=conf_ofc.port,
|
||||
use_ssl=conf_ofc.use_ssl,
|
||||
key_file=conf_ofc.key_file,
|
||||
cert_file=conf_ofc.cert_file)
|
||||
cert_file=conf_ofc.cert_file,
|
||||
insecure_ssl=conf_ofc.insecure_ssl)
|
||||
|
||||
@classmethod
|
||||
def filter_supported(cls):
|
||||
|
@ -17,10 +17,10 @@
|
||||
# @author: Akihiro Motoki
|
||||
|
||||
import json
|
||||
import socket
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
import requests
|
||||
|
||||
from neutron.plugins.nec.common import config
|
||||
from neutron.plugins.nec.common import exceptions as nexc
|
||||
@ -28,19 +28,25 @@ from neutron.plugins.nec.common import ofc_client
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class FakeResponse(requests.Response):
|
||||
def __init__(self, status_code=None, text=None, headers=None):
|
||||
self._text = text
|
||||
self.status_code = status_code
|
||||
if headers is not None:
|
||||
self.headers = headers
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._text
|
||||
|
||||
|
||||
class OFCClientTest(base.BaseTestCase):
|
||||
|
||||
def _test_do_request(self, status, resbody, expected_data, exctype=None,
|
||||
exc_checks=None, path_prefix=None):
|
||||
res = mock.Mock()
|
||||
res.status = status
|
||||
res.read.return_value = resbody
|
||||
req = mock.Mock(return_value=(FakeResponse(status, resbody)))
|
||||
|
||||
conn = mock.Mock()
|
||||
conn.getresponse.return_value = res
|
||||
|
||||
with mock.patch.object(ofc_client.OFCClient, 'get_connection',
|
||||
return_value=conn):
|
||||
with mock.patch.object(requests, 'request', req):
|
||||
client = ofc_client.OFCClient()
|
||||
path = '/somewhere'
|
||||
realpath = path_prefix + path if path_prefix else path
|
||||
@ -56,11 +62,9 @@ class OFCClientTest(base.BaseTestCase):
|
||||
self.assertEqual(response, expected_data)
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
expected = [
|
||||
mock.call.request('GET', realpath, '{}', headers),
|
||||
mock.call.getresponse(),
|
||||
]
|
||||
conn.assert_has_calls(expected)
|
||||
req.assert_called_with('GET', 'http://127.0.0.1:8888' + realpath,
|
||||
verify=True, cert={}, data='{}',
|
||||
headers=headers)
|
||||
|
||||
def test_do_request_200_json_value(self):
|
||||
self._test_do_request(200, json.dumps([1, 2, 3]), [1, 2, 3])
|
||||
@ -108,13 +112,12 @@ class OFCClientTest(base.BaseTestCase):
|
||||
exc_checks)
|
||||
|
||||
def test_do_request_socket_error(self):
|
||||
conn = mock.Mock()
|
||||
conn.request.side_effect = socket.error
|
||||
|
||||
data = _("An OFC exception has occurred: Failed to connect OFC : ")
|
||||
|
||||
with mock.patch.object(ofc_client.OFCClient, 'get_connection',
|
||||
return_value=conn):
|
||||
req = mock.Mock()
|
||||
req.side_effect = requests.exceptions.RequestException
|
||||
|
||||
with mock.patch.object(requests, 'request', req):
|
||||
client = ofc_client.OFCClient()
|
||||
|
||||
e = self.assertRaises(nexc.OFCException, client.do_request,
|
||||
@ -124,10 +127,9 @@ class OFCClientTest(base.BaseTestCase):
|
||||
self.assertIsNone(getattr(e, k))
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
expected = [
|
||||
mock.call.request('GET', '/somewhere', '{}', headers),
|
||||
]
|
||||
conn.assert_has_calls(expected)
|
||||
req.assert_called_with('GET', 'http://127.0.0.1:8888/somewhere',
|
||||
verify=True, cert={}, data='{}',
|
||||
headers=headers)
|
||||
|
||||
def test_do_request_retry_fail_after_one_attempts(self):
|
||||
self._test_do_request_retry_after(1, api_max_attempts=1)
|
||||
@ -148,24 +150,17 @@ class OFCClientTest(base.BaseTestCase):
|
||||
cfg.CONF.set_override('api_max_attempts', api_max_attempts,
|
||||
group='OFC')
|
||||
|
||||
res_unavail = mock.Mock()
|
||||
res_unavail.status = 503
|
||||
res_unavail.read.return_value = None
|
||||
res_unavail.getheader.return_value = '10'
|
||||
res_unavail = FakeResponse(503, headers={'retry-after': '10'})
|
||||
res_ok = FakeResponse(200)
|
||||
|
||||
res_ok = mock.Mock()
|
||||
res_ok.status = 200
|
||||
res_ok.read.return_value = None
|
||||
|
||||
conn = mock.Mock()
|
||||
req = mock.Mock()
|
||||
if succeed_final:
|
||||
side_effect = [res_unavail] * (exp_request_count - 1) + [res_ok]
|
||||
req.side_effect = ([res_unavail] * (exp_request_count - 1)
|
||||
+ [res_ok])
|
||||
else:
|
||||
side_effect = [res_unavail] * exp_request_count
|
||||
conn.getresponse.side_effect = side_effect
|
||||
req.side_effect = [res_unavail] * exp_request_count
|
||||
|
||||
with mock.patch.object(ofc_client.OFCClient, 'get_connection',
|
||||
return_value=conn):
|
||||
with mock.patch.object(requests, 'request', req):
|
||||
with mock.patch('time.sleep') as sleep:
|
||||
client = ofc_client.OFCClient()
|
||||
if succeed_final:
|
||||
@ -176,11 +171,10 @@ class OFCClientTest(base.BaseTestCase):
|
||||
client.do_request,
|
||||
'GET', '/somewhere')
|
||||
self.assertEqual('10', e.retry_after)
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
expected = [
|
||||
mock.call.request('GET', '/somewhere', None, headers),
|
||||
mock.call.getresponse(),
|
||||
] * exp_request_count
|
||||
conn.assert_has_calls(expected)
|
||||
self.assertEqual(exp_request_count, conn.request.call_count)
|
||||
req.assert_called_with('GET', 'http://127.0.0.1:8888/somewhere',
|
||||
verify=True, cert={}, data=None,
|
||||
headers=headers)
|
||||
self.assertEqual(exp_request_count, req.call_count)
|
||||
self.assertEqual(exp_request_count - 1, sleep.call_count)
|
||||
|
@ -39,6 +39,7 @@ class TestConfig(object):
|
||||
use_ssl = False
|
||||
key_file = None
|
||||
cert_file = None
|
||||
insecure_ssl = False
|
||||
|
||||
|
||||
def _ofc(id):
|
||||
|
Loading…
Reference in New Issue
Block a user