]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add XML deserializer for qos_manage delete_keys API
authorZhiteng Huang <zhithuang@ebaysf.com>
Wed, 30 Apr 2014 15:47:55 +0000 (23:47 +0800)
committerZhiteng Huang <zhithuang@ebaysf.com>
Sun, 25 May 2014 03:37:30 +0000 (11:37 +0800)
XML deserializer is missing for delete_keys API resulting XML format
request not being correctly handled by QoS Manage API extension. This
patch adds XML deserializer for the API.

Also add unittest for qos_manage XML serializer and deserilizer.

Change-Id: I7b3ac6822c52f11b08d767aa55b7107bd0333c36
Closes-bug: #1312553

cinder/api/contrib/qos_specs_manage.py
cinder/api/schemas/v1.1/qos_association.rng [new file with mode: 0644]
cinder/api/schemas/v1.1/qos_associations.rng [new file with mode: 0644]
cinder/api/schemas/v1.1/qos_spec.rng [new file with mode: 0644]
cinder/api/schemas/v1.1/qos_specs.rng [new file with mode: 0644]
cinder/tests/api/contrib/test_qos_specs_manage.py

index 1d50017190ed7e6a2e7b375a9e0b93f3af9b086c..8420c2a4c4ffa76ade308bfad3e9f850b5968d13 100644 (file)
@@ -26,6 +26,7 @@ from cinder import exception
 from cinder.openstack.common import log as logging
 from cinder.openstack.common import strutils
 from cinder import rpc
+from cinder import utils
 from cinder.volume import qos_specs
 
 
@@ -61,6 +62,26 @@ class QoSSpecsTemplate(xmlutil.TemplateBuilder):
         return xmlutil.MasterTemplate(root, 1)
 
 
+class QoSSpecsKeyDeserializer(wsgi.XMLDeserializer):
+    def _extract_keys(self, key_node):
+        keys = []
+        for key in key_node.childNodes:
+            key_name = key.tagName
+            keys.append(key_name)
+
+        return keys
+
+    def default(self, string):
+        dom = utils.safe_minidom_parse_string(string)
+        key_node = self.find_first_child_named(dom, 'keys')
+        if not key_node:
+            LOG.info(_("Unable to parse XML input."))
+            msg = _("Unable to parse XML request. "
+                    "Please provide XML in correct format.")
+            raise webob.exc.HTTPBadRequest(explanation=msg)
+        return {'body': {'keys': self._extract_keys(key_node)}}
+
+
 class AssociationsTemplate(xmlutil.TemplateBuilder):
     def construct(self):
         root = xmlutil.TemplateElement('qos_associations')
@@ -225,6 +246,7 @@ class QoSSpecsController(wsgi.Controller):
 
         return webob.Response(status_int=202)
 
+    @wsgi.deserializers(xml=QoSSpecsKeyDeserializer)
     def delete_keys(self, req, id, body):
         """Deletes specified keys in qos specs."""
         context = req.environ['cinder.context']
diff --git a/cinder/api/schemas/v1.1/qos_association.rng b/cinder/api/schemas/v1.1/qos_association.rng
new file mode 100644 (file)
index 0000000..20d975c
--- /dev/null
@@ -0,0 +1,8 @@
+<element name="associations" xmlns="http://relaxng.org/ns/structure/1.0">
+  <attribute name="name">      <text/> </attribute>
+  <attribute name="id">      <text/> </attribute>
+  <attribute name="association_type"> <text/> </attribute>
+  <zeroOrMore>
+    <externalRef href="../atom-link.rng"/>
+  </zeroOrMore>
+</element>
diff --git a/cinder/api/schemas/v1.1/qos_associations.rng b/cinder/api/schemas/v1.1/qos_associations.rng
new file mode 100644 (file)
index 0000000..3d218f8
--- /dev/null
@@ -0,0 +1,5 @@
+<element name="qos_associations" xmlns="http://relaxng.org/ns/structure/1.0">
+  <zeroOrMore>
+    <externalRef href="qos_association.rng"/>
+  </zeroOrMore>
+</element>
diff --git a/cinder/api/schemas/v1.1/qos_spec.rng b/cinder/api/schemas/v1.1/qos_spec.rng
new file mode 100644 (file)
index 0000000..c82741f
--- /dev/null
@@ -0,0 +1,16 @@
+<element name="qos_spec" xmlns="http://relaxng.org/ns/structure/1.0">
+  <attribute name="name">      <text/> </attribute>
+  <attribute name="id">      <text/> </attribute>
+  <attribute name="consumer"> <text/> </attribute>
+  <element name="specs">
+    <zeroOrMore>
+      <element>
+        <anyName/>
+        <text/>
+      </element>
+    </zeroOrMore>
+  </element>
+  <zeroOrMore>
+    <externalRef href="../atom-link.rng"/>
+  </zeroOrMore>
+</element>
diff --git a/cinder/api/schemas/v1.1/qos_specs.rng b/cinder/api/schemas/v1.1/qos_specs.rng
new file mode 100644 (file)
index 0000000..974e7de
--- /dev/null
@@ -0,0 +1,5 @@
+<element name="qos_specs" xmlns="http://relaxng.org/ns/structure/1.0">
+  <zeroOrMore>
+    <externalRef href="qos_spec.rng"/>
+  </zeroOrMore>
+</element>
index 8abf1d070d2166b2339dd552ffc8f270ddad7930..84ce76fa6a8df1fdb18e9dcb5ebff57735599883 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from lxml import etree
 from xml.dom import minidom
 
 import webob
 
 from cinder.api.contrib import qos_specs_manage
+from cinder.api import xmlutil
 from cinder import exception
 from cinder import test
 from cinder.tests.api import fakes
@@ -598,3 +600,108 @@ class QoSSpecManageApiTest(test.TestCase):
             '/v2/fake/qos-specs/222/disassociate_all')
         self.assertRaises(webob.exc.HTTPInternalServerError,
                           self.controller.disassociate_all, req, '222')
+
+
+class TestQoSSpecsTemplate(test.TestCase):
+    def setUp(self):
+        super(TestQoSSpecsTemplate, self).setUp()
+        self.serializer = qos_specs_manage.QoSSpecsTemplate()
+
+    def test_qos_specs_serializer(self):
+        fixture = {
+            "qos_specs": [
+                {
+                    "specs": {
+                        "key1": "v1",
+                        "key2": "v2",
+                    },
+                    "consumer": "back-end",
+                    "name": "qos-2",
+                    "id": "61e7b72f-ef15-46d9-b00e-b80f699999d0"
+                },
+                {
+                    "specs": {"total_iops_sec": "200"},
+                    "consumer": "front-end",
+                    "name": "qos-1",
+                    "id": "e44bba5e-b629-4b96-9aa3-0404753a619b"
+                }
+            ]
+        }
+
+        output = self.serializer.serialize(fixture)
+        root = etree.XML(output)
+        xmlutil.validate_schema(root, 'qos_specs')
+        qos_elems = root.findall("qos_spec")
+        self.assertEqual(len(qos_elems), 2)
+        for i, qos_elem in enumerate(qos_elems):
+            qos_dict = fixture['qos_specs'][i]
+
+            # check qos_spec attributes
+            for key in ['name', 'id', 'consumer']:
+                self.assertEqual(qos_elem.get(key), str(qos_dict[key]))
+
+            # check specs
+            specs = qos_elem.find("specs")
+            new_dict = {}
+            for element in specs.iter(tag=etree.Element):
+                # skip root element for specs
+                if element.tag == "specs":
+                    continue
+                new_dict.update({element.tag: element.text})
+
+            self.assertDictMatch(new_dict, qos_dict['specs'])
+
+
+class TestAssociationsTemplate(test.TestCase):
+    def setUp(self):
+        super(TestAssociationsTemplate, self).setUp()
+        self.serializer = qos_specs_manage.AssociationsTemplate()
+
+    def test_qos_associations_serializer(self):
+        fixture = {
+            "qos_associations": [
+                {
+                    "association_type": "volume_type",
+                    "name": "type-4",
+                    "id": "14d54d29-51a4-4046-9f6f-cf9800323563"
+                },
+                {
+                    "association_type": "volume_type",
+                    "name": "type-2",
+                    "id": "3689ce83-308d-4ba1-8faf-7f1be04a282b"}
+            ]
+        }
+
+        output = self.serializer.serialize(fixture)
+        root = etree.XML(output)
+        xmlutil.validate_schema(root, 'qos_associations')
+        association_elems = root.findall("associations")
+        self.assertEqual(len(association_elems), 2)
+        for i, association_elem in enumerate(association_elems):
+            association_dict = fixture['qos_associations'][i]
+
+            # check qos_spec attributes
+            for key in ['name', 'id', 'association_type']:
+                self.assertEqual(association_elem.get(key),
+                                 str(association_dict[key]))
+
+
+class TestQoSSpecsKeyDeserializer(test.TestCase):
+    def setUp(self):
+        super(TestQoSSpecsKeyDeserializer, self).setUp()
+        self.deserializer = qos_specs_manage.QoSSpecsKeyDeserializer()
+
+    def test_keys(self):
+        self_request = """
+<keys><xyz /><abc /></keys>"""
+        request = self.deserializer.deserialize(self_request)
+        expected = {
+            "keys": ["xyz", "abc"]
+        }
+        self.assertEqual(request['body'], expected)
+
+    def test_bad_format(self):
+        self_request = """
+<qos_specs><keys><xyz /><abc /></keys></qos_specs>"""
+        self.assertRaises(webob.exc.HTTPBadRequest,
+                          self.deserializer.deserialize, self_request)