Merge "Replace XML with JSON for N1kv REST calls"
This commit is contained in:
commit
b5d6c5e106
@ -18,18 +18,18 @@
|
||||
# @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)
|
||||
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 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())
|
||||
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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user