]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Replace XML with JSON for N1kv REST calls
authorAbhishek Raut <abhraut@cisco.com>
Wed, 28 May 2014 19:03:06 +0000 (12:03 -0700)
committerAbhishek Raut <abhraut@cisco.com>
Sat, 7 Jun 2014 04:13:14 +0000 (21:13 -0700)
Currently the cisco n1kv plugin handles XML responses from VSM.
This change will replace the httplib2 with requests library and handle
JSON responses from the VSM.

Implements: blueprint cisco-n1kv-json-support

Change-Id: Icd32a6a2ab815ccd24ad86371e927c132e204413

neutron/plugins/cisco/n1kv/n1kv_client.py
neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py
neutron/tests/unit/cisco/n1kv/fake_client.py
neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py

index 79abf715e9f087d3fd936e1999db8e012d929c67..7389eadfb31a867b1f162562aca9b4a51d4427ff 100644 (file)
 # @author: Rudrajit Tapadar, Cisco Systems, Inc.
 
 import base64
-import httplib2
 import netaddr
+import requests
 
 from neutron.common import exceptions as n_exc
 from neutron.extensions import providernet
+from neutron.openstack.common import jsonutils
 from neutron.openstack.common import log as logging
 from neutron.plugins.cisco.common import cisco_constants as c_const
 from neutron.plugins.cisco.common import cisco_credentials_v2 as c_cred
 from neutron.plugins.cisco.common import cisco_exceptions as c_exc
 from neutron.plugins.cisco.db import network_db_v2
 from neutron.plugins.cisco.extensions import n1kv
-from neutron import wsgi
 
 LOG = logging.getLogger(__name__)
 
@@ -105,25 +105,6 @@ class Client(object):
 
     """
 
-    # Metadata for deserializing xml
-    _serialization_metadata = {
-        "application/xml": {
-            "attributes": {
-                "network": ["id", "name"],
-                "port": ["id", "mac_address"],
-                "subnet": ["id", "prefix"]
-            },
-        },
-        "plurals": {
-            "networks": "network",
-            "ports": "port",
-            "set": "instance",
-            "subnets": "subnet",
-            "mappings": "mapping",
-            "segments": "segment"
-        }
-    }
-
     # Define paths for the URI where the client connects for HTTP requests.
     port_profiles_path = "/virtual-port-profile"
     network_segment_path = "/network-segment/%s"
@@ -152,7 +133,7 @@ class Client(object):
         """
         Fetch all policy profiles from the VSM.
 
-        :returns: XML string
+        :returns: JSON string
         """
         return self._get(self.port_profiles_path)
 
@@ -430,8 +411,8 @@ class Client(object):
         """
         Perform the HTTP request.
 
-        The response is in either XML format or plain text. A GET method will
-        invoke a XML response while a PUT/POST/DELETE returns message from the
+        The response is in either JSON format or plain text. A GET method will
+        invoke a JSON response while a PUT/POST/DELETE returns message from the
         VSM in plain text format.
         Exception is raised when VSM replies with an INTERNAL SERVER ERROR HTTP
         status code (500) i.e. an error has occurred on the VSM or SERVICE
@@ -441,58 +422,35 @@ class Client(object):
         :param action: path to which the client makes request
         :param body: dict for arguments which are sent as part of the request
         :param headers: header for the HTTP request
-        :returns: XML or plain text in HTTP response
+        :returns: JSON or plain text in HTTP response
         """
         action = self.action_prefix + action
         if not headers and self.hosts:
             headers = self._get_auth_header(self.hosts[0])
         headers['Content-Type'] = self._set_content_type('json')
+        headers['Accept'] = self._set_content_type('json')
         if body:
-            body = self._serialize(body)
+            body = jsonutils.dumps(body, indent=2)
             LOG.debug(_("req: %s"), body)
         try:
-            resp, replybody = (httplib2.Http(timeout=self.timeout).
-                               request(action,
-                                       method,
-                                       body=body,
-                                       headers=headers))
+            resp = requests.request(method,
+                                    url=action,
+                                    data=body,
+                                    headers=headers,
+                                    timeout=self.timeout)
         except Exception as e:
             raise c_exc.VSMConnectionFailed(reason=e)
-        LOG.debug(_("status_code %s"), resp.status)
-        if resp.status == 200:
-            if 'application/xml' in resp['content-type']:
-                return self._deserialize(replybody, resp.status)
-            elif 'text/plain' in resp['content-type']:
-                LOG.debug(_("VSM: %s"), replybody)
-        else:
-            raise c_exc.VSMError(reason=replybody)
-
-    def _serialize(self, data):
-        """
-        Serialize a dictionary with a single key into either xml or json.
-
-        :param data: data in the form of dict
-        """
-        if data is None:
-            return None
-        elif type(data) is dict:
-            return wsgi.Serializer().serialize(data, self._set_content_type())
+        LOG.debug(_("status_code %s"), resp.status_code)
+        if resp.status_code == requests.codes.OK:
+            if 'application/json' in resp.headers['content-type']:
+                try:
+                    return resp.json()
+                except ValueError:
+                    return {}
+            elif 'text/plain' in resp.headers['content-type']:
+                LOG.debug(_("VSM: %s"), resp.text)
         else:
-            raise Exception(_("Unable to serialize object of type = '%s'") %
-                            type(data))
-
-    def _deserialize(self, data, status_code):
-        """
-        Deserialize an XML string into a dictionary.
-
-        :param data: XML string from the HTTP response
-        :param status_code: integer status code from the HTTP response
-        :return: data in the form of dict
-        """
-        if status_code == 204:
-            return data
-        return wsgi.Serializer(self._serialization_metadata).deserialize(
-            data, self._set_content_type('xml'))
+            raise c_exc.VSMError(reason=resp.text)
 
     def _set_content_type(self, format=None):
         """
@@ -539,7 +497,7 @@ class Client(object):
         """
         username = c_cred.Store.get_username(host_ip)
         password = c_cred.Store.get_password(host_ip)
-        auth = base64.encodestring("%s:%s" % (username, password))
+        auth = base64.encodestring("%s:%s" % (username, password)).rstrip()
         header = {"Authorization": "Basic %s" % auth}
         return header
 
index 1bc24722466656c9c5061bb1ccbbf4b3c8ff2fe8..168ba8b10b3b4520e82600ee89fc3bf4f315a0f9 100644 (file)
@@ -176,29 +176,24 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
             n1kvclient = n1kv_client.Client()
             policy_profiles = n1kvclient.list_port_profiles()
             vsm_profiles = {}
-            plugin_profiles = {}
+            plugin_profiles_set = set()
             # Fetch policy profiles from VSM
-            if policy_profiles:
-                for profile in policy_profiles['body'][c_const.SET]:
-                    profile_name = (profile[c_const.PROPERTIES].
-                                    get(c_const.NAME, None))
-                    profile_id = (profile[c_const.PROPERTIES].
-                                  get(c_const.ID, None))
-                    if profile_id and profile_name:
-                        vsm_profiles[profile_id] = profile_name
-                # Fetch policy profiles previously populated
-                for profile in n1kv_db_v2.get_policy_profiles():
-                    plugin_profiles[profile.id] = profile.name
-                vsm_profiles_set = set(vsm_profiles)
-                plugin_profiles_set = set(plugin_profiles)
-                # Update database if the profile sets differ.
-                if vsm_profiles_set ^ plugin_profiles_set:
+            for profile_name in policy_profiles:
+                profile_id = (policy_profiles
+                              [profile_name][c_const.PROPERTIES][c_const.ID])
+                vsm_profiles[profile_id] = profile_name
+            # Fetch policy profiles previously populated
+            for profile in n1kv_db_v2.get_policy_profiles():
+                plugin_profiles_set.add(profile.id)
+            vsm_profiles_set = set(vsm_profiles)
+            # Update database if the profile sets differ.
+            if vsm_profiles_set ^ plugin_profiles_set:
                 # Add profiles in database if new profiles were created in VSM
-                    for pid in vsm_profiles_set - plugin_profiles_set:
-                        self._add_policy_profile(vsm_profiles[pid], pid)
+                for pid in vsm_profiles_set - plugin_profiles_set:
+                    self._add_policy_profile(vsm_profiles[pid], pid)
                 # Delete profiles from database if profiles were deleted in VSM
-                    for pid in plugin_profiles_set - vsm_profiles_set:
-                        self._delete_policy_profile(pid)
+                for pid in plugin_profiles_set - vsm_profiles_set:
+                    self._delete_policy_profile(pid)
             self._remove_all_fake_policy_profiles()
         except (cisco_exceptions.VSMError,
                 cisco_exceptions.VSMConnectionFailed):
index 6348bbfd042e368a3cedc7fbc1e374b563e07bfe..2d1f0780e15e6274032a181ee654210cf3d66d14 100755 (executable)
@@ -51,9 +51,8 @@ class TestClient(n1kv_client.Client):
             return _validate_resource(action, body)
         elif method == 'GET':
             if 'virtual-port-profile' in action:
-                profiles = _policy_profile_generator_xml(
+                return _policy_profile_generator(
                     self._get_total_profiles())
-                return self._deserialize(profiles, 200)
             else:
                 raise c_exc.VSMError(reason='VSM:Internal Server Error')
 
@@ -82,6 +81,21 @@ def _validate_resource(action, body=None):
         return
 
 
+def _policy_profile_generator(total_profiles):
+    """
+    Generate policy profile response and return a dictionary.
+
+    :param total_profiles: integer representing total number of profiles to
+                           return
+    """
+    profiles = {}
+    for num in range(1, total_profiles + 1):
+        name = "pp-%s" % num
+        profile_id = "00000000-0000-0000-0000-00000000000%s" % num
+        profiles[name] = {"properties": {"name": name, "id": profile_id}}
+    return profiles
+
+
 def _policy_profile_generator_xml(total_profiles):
     """
     Generate policy profile response in XML format.
index ad45b0910cae1e8f6d35615b64396bf8a9dec33b..b1833436ef5c809200f8d7856d8e5d9aee55e8d9 100644 (file)
@@ -50,20 +50,18 @@ VLAN_MAX = 110
 class FakeResponse(object):
 
     """
-    This object is returned by mocked httplib instead of a normal response.
+    This object is returned by mocked requests lib instead of normal response.
 
-    Initialize it with the status code, content type and buffer contents
-    you wish to return.
+    Initialize it with the status code, header and buffer contents you wish to
+    return.
 
     """
-    def __init__(self, status, response_text, content_type):
+    def __init__(self, status, response_text, headers):
         self.buffer = response_text
-        self.status = status
+        self.status_code = status
+        self.headers = headers
 
-    def __getitem__(cls, val):
-        return "application/xml"
-
-    def read(self, *args, **kwargs):
+    def json(self, *args, **kwargs):
         return self.buffer
 
 
@@ -154,47 +152,28 @@ class N1kvPluginTestCase(test_plugin.NeutronDbPluginV2TestCase):
 
         """
         if not self.DEFAULT_RESP_BODY:
-            self.DEFAULT_RESP_BODY = (
-                """<?xml version="1.0" encoding="utf-8"?>
-                <set name="events_set">
-                <instance name="1" url="/api/hyper-v/events/1">
-                <properties>
-                <cmd>configure terminal ; port-profile type vethernet grizzlyPP
-                    (SUCCESS)
-                </cmd>
-                <id>42227269-e348-72ed-bdb7-7ce91cd1423c</id>
-                <time>1369223611</time>
-                <name>grizzlyPP</name>
-                </properties>
-                </instance>
-                <instance name="2" url="/api/hyper-v/events/2">
-                <properties>
-                <cmd>configure terminal ; port-profile type vethernet havanaPP
-                    (SUCCESS)
-                </cmd>
-                <id>3fc83608-ae36-70e7-9d22-dec745623d06</id>
-                <time>1369223661</time>
-                <name>havanaPP</name>
-                </properties>
-                </instance>
-                </set>
-                """)
-        # Creating a mock HTTP connection object for httplib. The N1KV client
-        # interacts with the VSM via HTTP. Since we don't have a VSM running
-        # in the unit tests, we need to 'fake' it by patching the HTTP library
-        # itself. We install a patch for a fake HTTP connection class.
+            self.DEFAULT_RESP_BODY = {
+                "icehouse-pp": {"properties": {"name": "icehouse-pp",
+                                               "id": "some-uuid-1"}},
+                "havana_pp": {"properties": {"name": "havana_pp",
+                                             "id": "some-uuid-2"}},
+                "dhcp_pp": {"properties": {"name": "dhcp_pp",
+                                           "id": "some-uuid-3"}},
+            }
+        # Creating a mock HTTP connection object for requests lib. The N1KV
+        # client interacts with the VSM via HTTP. Since we don't have a VSM
+        # running in the unit tests, we need to 'fake' it by patching the HTTP
+        # library itself. We install a patch for a fake HTTP connection class.
         # Using __name__ to avoid having to enter the full module path.
-        http_patcher = mock.patch(n1kv_client.httplib2.__name__ + ".Http")
+        http_patcher = mock.patch(n1kv_client.requests.__name__ + ".request")
         FakeHttpConnection = http_patcher.start()
         # Now define the return values for a few functions that may be called
         # on any instance of the fake HTTP connection class.
-        instance = FakeHttpConnection.return_value
-        instance.getresponse.return_value = (FakeResponse(
-                                             self.DEFAULT_RESP_CODE,
-                                             self.DEFAULT_RESP_BODY,
-                                             'application/xml'))
-        instance.request.return_value = (instance.getresponse.return_value,
-                                         self.DEFAULT_RESP_BODY)
+        self.resp_headers = {"content-type": "application/json"}
+        FakeHttpConnection.return_value = (FakeResponse(
+                                           self.DEFAULT_RESP_CODE,
+                                           self.DEFAULT_RESP_BODY,
+                                           self.resp_headers))
 
         # Patch some internal functions in a few other parts of the system.
         # These help us move along, without having to mock up even more systems
@@ -612,9 +591,6 @@ class TestN1kvSubnets(test_plugin.TestSubnetsV2,
     def setUp(self):
         super(TestN1kvSubnets, self).setUp()
 
-        # Create some of the database entries that we require.
-        self._make_test_policy_profile(name='dhcp_pp')
-
 
 class TestN1kvL3Test(test_l3_plugin.L3NatExtensionTestCase):