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.
|
# @author: Rudrajit Tapadar, Cisco Systems, Inc.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import httplib2
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
import requests
|
||||||
|
|
||||||
from neutron.common import exceptions as n_exc
|
from neutron.common import exceptions as n_exc
|
||||||
from neutron.extensions import providernet
|
from neutron.extensions import providernet
|
||||||
|
from neutron.openstack.common import jsonutils
|
||||||
from neutron.openstack.common import log as logging
|
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_constants as c_const
|
||||||
from neutron.plugins.cisco.common import cisco_credentials_v2 as c_cred
|
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.common import cisco_exceptions as c_exc
|
||||||
from neutron.plugins.cisco.db import network_db_v2
|
from neutron.plugins.cisco.db import network_db_v2
|
||||||
from neutron.plugins.cisco.extensions import n1kv
|
from neutron.plugins.cisco.extensions import n1kv
|
||||||
from neutron import wsgi
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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.
|
# Define paths for the URI where the client connects for HTTP requests.
|
||||||
port_profiles_path = "/virtual-port-profile"
|
port_profiles_path = "/virtual-port-profile"
|
||||||
network_segment_path = "/network-segment/%s"
|
network_segment_path = "/network-segment/%s"
|
||||||
@ -152,7 +133,7 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
Fetch all policy profiles from the VSM.
|
Fetch all policy profiles from the VSM.
|
||||||
|
|
||||||
:returns: XML string
|
:returns: JSON string
|
||||||
"""
|
"""
|
||||||
return self._get(self.port_profiles_path)
|
return self._get(self.port_profiles_path)
|
||||||
|
|
||||||
@ -430,8 +411,8 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
Perform the HTTP request.
|
Perform the HTTP request.
|
||||||
|
|
||||||
The response is in either XML format or plain text. A GET method will
|
The response is in either JSON format or plain text. A GET method will
|
||||||
invoke a XML response while a PUT/POST/DELETE returns message from the
|
invoke a JSON response while a PUT/POST/DELETE returns message from the
|
||||||
VSM in plain text format.
|
VSM in plain text format.
|
||||||
Exception is raised when VSM replies with an INTERNAL SERVER ERROR HTTP
|
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
|
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 action: path to which the client makes request
|
||||||
:param body: dict for arguments which are sent as part of the request
|
:param body: dict for arguments which are sent as part of the request
|
||||||
:param headers: header for the HTTP 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
|
action = self.action_prefix + action
|
||||||
if not headers and self.hosts:
|
if not headers and self.hosts:
|
||||||
headers = self._get_auth_header(self.hosts[0])
|
headers = self._get_auth_header(self.hosts[0])
|
||||||
headers['Content-Type'] = self._set_content_type('json')
|
headers['Content-Type'] = self._set_content_type('json')
|
||||||
|
headers['Accept'] = self._set_content_type('json')
|
||||||
if body:
|
if body:
|
||||||
body = self._serialize(body)
|
body = jsonutils.dumps(body, indent=2)
|
||||||
LOG.debug(_("req: %s"), body)
|
LOG.debug(_("req: %s"), body)
|
||||||
try:
|
try:
|
||||||
resp, replybody = (httplib2.Http(timeout=self.timeout).
|
resp = requests.request(method,
|
||||||
request(action,
|
url=action,
|
||||||
method,
|
data=body,
|
||||||
body=body,
|
headers=headers,
|
||||||
headers=headers))
|
timeout=self.timeout)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise c_exc.VSMConnectionFailed(reason=e)
|
raise c_exc.VSMConnectionFailed(reason=e)
|
||||||
LOG.debug(_("status_code %s"), resp.status)
|
LOG.debug(_("status_code %s"), resp.status_code)
|
||||||
if resp.status == 200:
|
if resp.status_code == requests.codes.OK:
|
||||||
if 'application/xml' in resp['content-type']:
|
if 'application/json' in resp.headers['content-type']:
|
||||||
return self._deserialize(replybody, resp.status)
|
try:
|
||||||
elif 'text/plain' in resp['content-type']:
|
return resp.json()
|
||||||
LOG.debug(_("VSM: %s"), replybody)
|
except ValueError:
|
||||||
|
return {}
|
||||||
|
elif 'text/plain' in resp.headers['content-type']:
|
||||||
|
LOG.debug(_("VSM: %s"), resp.text)
|
||||||
else:
|
else:
|
||||||
raise c_exc.VSMError(reason=replybody)
|
raise c_exc.VSMError(reason=resp.text)
|
||||||
|
|
||||||
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'))
|
|
||||||
|
|
||||||
def _set_content_type(self, format=None):
|
def _set_content_type(self, format=None):
|
||||||
"""
|
"""
|
||||||
@ -539,7 +497,7 @@ class Client(object):
|
|||||||
"""
|
"""
|
||||||
username = c_cred.Store.get_username(host_ip)
|
username = c_cred.Store.get_username(host_ip)
|
||||||
password = c_cred.Store.get_password(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}
|
header = {"Authorization": "Basic %s" % auth}
|
||||||
return header
|
return header
|
||||||
|
|
||||||
|
@ -176,29 +176,24 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
n1kvclient = n1kv_client.Client()
|
n1kvclient = n1kv_client.Client()
|
||||||
policy_profiles = n1kvclient.list_port_profiles()
|
policy_profiles = n1kvclient.list_port_profiles()
|
||||||
vsm_profiles = {}
|
vsm_profiles = {}
|
||||||
plugin_profiles = {}
|
plugin_profiles_set = set()
|
||||||
# Fetch policy profiles from VSM
|
# Fetch policy profiles from VSM
|
||||||
if policy_profiles:
|
for profile_name in policy_profiles:
|
||||||
for profile in policy_profiles['body'][c_const.SET]:
|
profile_id = (policy_profiles
|
||||||
profile_name = (profile[c_const.PROPERTIES].
|
[profile_name][c_const.PROPERTIES][c_const.ID])
|
||||||
get(c_const.NAME, None))
|
vsm_profiles[profile_id] = profile_name
|
||||||
profile_id = (profile[c_const.PROPERTIES].
|
# Fetch policy profiles previously populated
|
||||||
get(c_const.ID, None))
|
for profile in n1kv_db_v2.get_policy_profiles():
|
||||||
if profile_id and profile_name:
|
plugin_profiles_set.add(profile.id)
|
||||||
vsm_profiles[profile_id] = profile_name
|
vsm_profiles_set = set(vsm_profiles)
|
||||||
# Fetch policy profiles previously populated
|
# Update database if the profile sets differ.
|
||||||
for profile in n1kv_db_v2.get_policy_profiles():
|
if vsm_profiles_set ^ plugin_profiles_set:
|
||||||
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:
|
|
||||||
# Add profiles in database if new profiles were created in VSM
|
# Add profiles in database if new profiles were created in VSM
|
||||||
for pid in vsm_profiles_set - plugin_profiles_set:
|
for pid in vsm_profiles_set - plugin_profiles_set:
|
||||||
self._add_policy_profile(vsm_profiles[pid], pid)
|
self._add_policy_profile(vsm_profiles[pid], pid)
|
||||||
# Delete profiles from database if profiles were deleted in VSM
|
# Delete profiles from database if profiles were deleted in VSM
|
||||||
for pid in plugin_profiles_set - vsm_profiles_set:
|
for pid in plugin_profiles_set - vsm_profiles_set:
|
||||||
self._delete_policy_profile(pid)
|
self._delete_policy_profile(pid)
|
||||||
self._remove_all_fake_policy_profiles()
|
self._remove_all_fake_policy_profiles()
|
||||||
except (cisco_exceptions.VSMError,
|
except (cisco_exceptions.VSMError,
|
||||||
cisco_exceptions.VSMConnectionFailed):
|
cisco_exceptions.VSMConnectionFailed):
|
||||||
|
@ -51,9 +51,8 @@ class TestClient(n1kv_client.Client):
|
|||||||
return _validate_resource(action, body)
|
return _validate_resource(action, body)
|
||||||
elif method == 'GET':
|
elif method == 'GET':
|
||||||
if 'virtual-port-profile' in action:
|
if 'virtual-port-profile' in action:
|
||||||
profiles = _policy_profile_generator_xml(
|
return _policy_profile_generator(
|
||||||
self._get_total_profiles())
|
self._get_total_profiles())
|
||||||
return self._deserialize(profiles, 200)
|
|
||||||
else:
|
else:
|
||||||
raise c_exc.VSMError(reason='VSM:Internal Server Error')
|
raise c_exc.VSMError(reason='VSM:Internal Server Error')
|
||||||
|
|
||||||
@ -82,6 +81,21 @@ def _validate_resource(action, body=None):
|
|||||||
return
|
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):
|
def _policy_profile_generator_xml(total_profiles):
|
||||||
"""
|
"""
|
||||||
Generate policy profile response in XML format.
|
Generate policy profile response in XML format.
|
||||||
|
@ -50,20 +50,18 @@ VLAN_MAX = 110
|
|||||||
class FakeResponse(object):
|
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
|
Initialize it with the status code, header and buffer contents you wish to
|
||||||
you wish to return.
|
return.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, status, response_text, content_type):
|
def __init__(self, status, response_text, headers):
|
||||||
self.buffer = response_text
|
self.buffer = response_text
|
||||||
self.status = status
|
self.status_code = status
|
||||||
|
self.headers = headers
|
||||||
|
|
||||||
def __getitem__(cls, val):
|
def json(self, *args, **kwargs):
|
||||||
return "application/xml"
|
|
||||||
|
|
||||||
def read(self, *args, **kwargs):
|
|
||||||
return self.buffer
|
return self.buffer
|
||||||
|
|
||||||
|
|
||||||
@ -154,47 +152,28 @@ class N1kvPluginTestCase(test_plugin.NeutronDbPluginV2TestCase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.DEFAULT_RESP_BODY:
|
if not self.DEFAULT_RESP_BODY:
|
||||||
self.DEFAULT_RESP_BODY = (
|
self.DEFAULT_RESP_BODY = {
|
||||||
"""<?xml version="1.0" encoding="utf-8"?>
|
"icehouse-pp": {"properties": {"name": "icehouse-pp",
|
||||||
<set name="events_set">
|
"id": "some-uuid-1"}},
|
||||||
<instance name="1" url="/api/hyper-v/events/1">
|
"havana_pp": {"properties": {"name": "havana_pp",
|
||||||
<properties>
|
"id": "some-uuid-2"}},
|
||||||
<cmd>configure terminal ; port-profile type vethernet grizzlyPP
|
"dhcp_pp": {"properties": {"name": "dhcp_pp",
|
||||||
(SUCCESS)
|
"id": "some-uuid-3"}},
|
||||||
</cmd>
|
}
|
||||||
<id>42227269-e348-72ed-bdb7-7ce91cd1423c</id>
|
# Creating a mock HTTP connection object for requests lib. The N1KV
|
||||||
<time>1369223611</time>
|
# client interacts with the VSM via HTTP. Since we don't have a VSM
|
||||||
<name>grizzlyPP</name>
|
# running in the unit tests, we need to 'fake' it by patching the HTTP
|
||||||
</properties>
|
# library itself. We install a patch for a fake HTTP connection class.
|
||||||
</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.
|
|
||||||
# Using __name__ to avoid having to enter the full module path.
|
# 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()
|
FakeHttpConnection = http_patcher.start()
|
||||||
# Now define the return values for a few functions that may be called
|
# Now define the return values for a few functions that may be called
|
||||||
# on any instance of the fake HTTP connection class.
|
# on any instance of the fake HTTP connection class.
|
||||||
instance = FakeHttpConnection.return_value
|
self.resp_headers = {"content-type": "application/json"}
|
||||||
instance.getresponse.return_value = (FakeResponse(
|
FakeHttpConnection.return_value = (FakeResponse(
|
||||||
self.DEFAULT_RESP_CODE,
|
self.DEFAULT_RESP_CODE,
|
||||||
self.DEFAULT_RESP_BODY,
|
self.DEFAULT_RESP_BODY,
|
||||||
'application/xml'))
|
self.resp_headers))
|
||||||
instance.request.return_value = (instance.getresponse.return_value,
|
|
||||||
self.DEFAULT_RESP_BODY)
|
|
||||||
|
|
||||||
# Patch some internal functions in a few other parts of the system.
|
# 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
|
# 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):
|
def setUp(self):
|
||||||
super(TestN1kvSubnets, self).setUp()
|
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):
|
class TestN1kvL3Test(test_l3_plugin.L3NatExtensionTestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user