diff --git a/quantum/common/constants.py b/quantum/common/constants.py index 1e2c70a2bcb..3e27fdf1450 100644 --- a/quantum/common/constants.py +++ b/quantum/common/constants.py @@ -42,6 +42,9 @@ XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0' XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance" XSI_ATTR = "xsi:nil" XSI_NIL_ATTR = "xmlns:xsi" +ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" +ATOM_XMLNS = "xmlns:atom" +ATOM_LINK_NOTATION = "{%s}link" % ATOM_NAMESPACE TYPE_XMLNS = "xmlns:quantum" TYPE_ATTR = "quantum:type" VIRTUAL_ROOT_KEY = "_v_root" diff --git a/quantum/tests/unit/db/loadbalancer/test_db_loadbalancer.py b/quantum/tests/unit/db/loadbalancer/test_db_loadbalancer.py index 26d44ba925b..dad18902cd3 100644 --- a/quantum/tests/unit/db/loadbalancer/test_db_loadbalancer.py +++ b/quantum/tests/unit/db/loadbalancer/test_db_loadbalancer.py @@ -287,8 +287,6 @@ class LoadBalancerPluginDbTestCase(testlib_api.WebTestCase): def _test_list_with_pagination(self, collection, items, sort, limit, expected_page_num, query_params=''): - if self.fmt == 'xml': - self.skipTest("Skip xml test for pagination") query_str = query_params + '&' if query_params else '' query_str = query_str + ("limit=%s&sort_key=%s&" "sort_dir=%s") % (limit, sort[0], sort[1]) @@ -317,8 +315,6 @@ class LoadBalancerPluginDbTestCase(testlib_api.WebTestCase): def _test_list_with_pagination_reverse(self, collection, items, sort, limit, expected_page_num, query_params=''): - if self.fmt == 'xml': - self.skipTest("Skip xml test for pagination") resources = '%ss' % collection collection = collection.replace('-', '_') api = self._api_for_resource(resources) diff --git a/quantum/tests/unit/test_db_plugin.py b/quantum/tests/unit/test_db_plugin.py index d78213f5e6e..3ead154c115 100644 --- a/quantum/tests/unit/test_db_plugin.py +++ b/quantum/tests/unit/test_db_plugin.py @@ -555,8 +555,6 @@ class QuantumDbPluginV2TestCase(testlib_api.WebTestCase): def _test_list_with_pagination(self, collection, items, sort, limit, expected_page_num, query_params='', verify_key='id'): - if self.fmt == 'xml': - self.skipTest("Skip xml test for pagination") query_str = query_params + '&' if query_params else '' query_str = query_str + ("limit=%s&sort_key=%s&" "sort_dir=%s") % (limit, sort[0], sort[1]) @@ -586,8 +584,6 @@ class QuantumDbPluginV2TestCase(testlib_api.WebTestCase): def _test_list_with_pagination_reverse(self, collection, items, sort, limit, expected_page_num, query_params=''): - if self.fmt == 'xml': - self.skipTest("Skip xml test for pagination") resources = '%ss' % collection collection = collection.replace('-', '_') api = self._api_for_resource(resources) diff --git a/quantum/wsgi.py b/quantum/wsgi.py index 8e846d06fae..a78c88ffe04 100644 --- a/quantum/wsgi.py +++ b/quantum/wsgi.py @@ -255,21 +255,33 @@ class XMLDictSerializer(DictSerializer): self.xmlns = xmlns def default(self, data): - # We expect data to contain a single key which is the XML root or - # non root + """ + :param data: expect data to contain a single key as XML root, or + contain another '*_links' key as atom links. Other + case will use 'VIRTUAL_ROOT_KEY' as XML root. + """ try: - key_len = data and len(data.keys()) or 0 - if (key_len == 1): - root_key = data.keys()[0] - root_value = data[root_key] - else: + links = None + has_atom = False + if data is None: root_key = constants.VIRTUAL_ROOT_KEY - root_value = data + root_value = None + else: + link_keys = [k for k in data.iterkeys() or [] + if k.endswith('_links')] + if link_keys: + links = data.pop(link_keys[0], None) + has_atom = True + root_key = (len(data) == 1 and + data.keys()[0] or constants.VIRTUAL_ROOT_KEY) + root_value = data.get(root_key, data) doc = etree.Element("_temp_root") used_prefixes = [] self._to_xml_node(doc, self.metadata, root_key, root_value, used_prefixes) - return self.to_xml_string(list(doc)[0], used_prefixes) + if links: + self._create_link_nodes(list(doc)[0], links) + return self.to_xml_string(list(doc)[0], used_prefixes, has_atom) except AttributeError as e: LOG.exception(str(e)) return '' @@ -292,7 +304,7 @@ class XMLDictSerializer(DictSerializer): node.set('xmlns', self.xmlns) node.set(constants.TYPE_XMLNS, self.xmlns) if has_atom: - node.set('xmlns:atom', "http://www.w3.org/2005/Atom") + node.set(constants.ATOM_XMLNS, constants.ATOM_NAMESPACE) node.set(constants.XSI_NIL_ATTR, constants.XSI_NAMESPACE) ext_ns = self.metadata.get(constants.EXT_NS, {}) for prefix in used_prefixes: @@ -359,6 +371,12 @@ class XMLDictSerializer(DictSerializer): result.text = str(data) return result + def _create_link_nodes(self, xml_doc, links): + for link in links: + link_node = etree.SubElement(xml_doc, 'atom:link') + link_node.set('rel', link['rel']) + link_node.set('href', link['href']) + class ResponseHeaderSerializer(ActionDispatcher): """Default response headers serialization""" @@ -462,18 +480,35 @@ class XMLDeserializer(TextDeserializer): else: return tag + def _get_links(self, root_tag, node): + link_nodes = node.findall(constants.ATOM_LINK_NOTATION) + root_tag = self._get_key(node.tag) + link_key = "%s_links" % root_tag + link_list = [] + for link in link_nodes: + link_list.append({'rel': link.get('rel'), + 'href': link.get('href')}) + # Remove link node in order to avoid link node process as + # an item in _from_xml_node + node.remove(link) + return link_list and {link_key: link_list} or {} + def _from_xml(self, datastring): if datastring is None: return None plurals = set(self.metadata.get('plurals', {})) try: node = etree.fromstring(datastring) - result = self._from_xml_node(node, plurals) root_tag = self._get_key(node.tag) + # Deserialize link node was needed by unit test for verifying + # the request's response + links = self._get_links(root_tag, node) + result = self._from_xml_node(node, plurals) + # root_tag = constants.VIRTUAL_ROOT_KEY and links is not None + # is not possible because of the way data are serialized. if root_tag == constants.VIRTUAL_ROOT_KEY: return result - else: - return {root_tag: result} + return dict({root_tag: result}, **links) except Exception as e: parseError = False # Python2.7