]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Replace HTTPSConnection in NEC plugin
authorDaniel Gollub <d.gollub@telekom.de>
Sun, 2 Mar 2014 08:33:38 +0000 (09:33 +0100)
committerAkihiro Motoki <motoki@da.jp.nec.com>
Thu, 3 Apr 2014 14:28:19 +0000 (23:28 +0900)
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

etc/neutron/plugins/nec/nec.ini
neutron/plugins/nec/common/config.py
neutron/plugins/nec/common/ofc_client.py
neutron/plugins/nec/drivers/pfc.py
neutron/tests/unit/nec/test_ofc_client.py
neutron/tests/unit/nec/test_pfc_driver.py

index b2cb3dbfa687e92d2b77df0ac8f604362762ccb1..aa4171da78499c90a3483ccf977ee73a2776d225 100644 (file)
@@ -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.
index 21c5e18246e8d4aa44d0817927d75152430f7051..80cfd0200ba582aa3f1006a9ebecd677a6060dc6 100644 (file)
@@ -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 "
index 957f24483a89fd4b640185821d36fd2aa714ab16..d51738c7ac3a6809bc58d364077f4b3b4401115b 100644 (file)
 #    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)
index deae833547b88a8856edf1ac72f8a61ae1e35209..85921b712a01871dbfe6e96385b79a9bf7786a98 100644 (file)
@@ -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):
index 68668d4d4841b4844de0475d292e05f44797d3aa..09a214a1f0fd5aa62776db5c152562feeecf4297 100644 (file)
 # @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
-
-        conn = mock.Mock()
-        conn.getresponse.return_value = res
+        req = mock.Mock(return_value=(FakeResponse(status, resbody)))
 
-        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)
index f5b27036d5ce74c8af463535564b6c9d8a1d6f90..c198800c4725ee663d0cf89e31a0f5e66a25748a 100644 (file)
@@ -39,6 +39,7 @@ class TestConfig(object):
     use_ssl = False
     key_file = None
     cert_file = None
+    insecure_ssl = False
 
 
 def _ofc(id):