Add XML support to the volumes client.

Change-Id: Ie73b1a9cee293271cd7d5493a7c6e0dcb30cabe8
Signed-off-by: Matthew Treinish <treinish@linux.vnet.ibm.com>
diff --git a/tempest/manager.py b/tempest/manager.py
index 228c3b9..b13226d 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -48,7 +48,7 @@
 SecurityGroupsClient = security_groups_client.SecurityGroupsClient
 FloatingIPsClient = floating_ips_client.FloatingIPsClient
 KeyPairsClient = keypairs_client.KeyPairsClientJSON
-VolumesExtensionsClient = volumes_extensions_client.VolumesExtensionsClient
+VolumesExtensionsClient = volumes_extensions_client.VolumesExtensionsClientJSON
 VolumesClient = volumes_client.VolumesClient
 ConsoleOutputsClient = console_output_client.ConsoleOutputsClient
 
diff --git a/tempest/openstack.py b/tempest/openstack.py
index 27ae6c0..204265e 100644
--- a/tempest/openstack.py
+++ b/tempest/openstack.py
@@ -23,7 +23,6 @@
 from tempest.services.network.json.network_client import NetworkClient
 from tempest.services.nova.json.extensions_client import ExtensionsClientJSON
 from tempest.services.nova.json.flavors_client import FlavorsClientJSON
-from tempest.services.volume.json.volumes_client import VolumesClient
 from tempest.services.nova.json.images_client import ImagesClient
 from tempest.services.nova.json.limits_client import LimitsClientJSON
 from tempest.services.nova.json.servers_client import ServersClientJSON
@@ -32,7 +31,7 @@
 from tempest.services.nova.json.floating_ips_client import FloatingIPsClient
 from tempest.services.nova.json.keypairs_client import KeyPairsClientJSON
 from tempest.services.nova.json.volumes_extensions_client \
-import VolumesExtensionsClient
+import VolumesExtensionsClientJSON
 from tempest.services.nova.json.console_output_client \
 import ConsoleOutputsClient
 from tempest.services.nova.xml.extensions_client import ExtensionsClientXML
@@ -40,6 +39,9 @@
 from tempest.services.nova.xml.keypairs_client import KeyPairsClientXML
 from tempest.services.nova.xml.limits_client import LimitsClientXML
 from tempest.services.nova.xml.servers_client import ServersClientXML
+from tempest.services.nova.xml.volumes_extensions_client \
+import VolumesExtensionsClientXML
+from tempest.services.volume.json.volumes_client import VolumesClient
 
 LOG = logging.getLogger(__name__)
 
@@ -68,6 +70,11 @@
     "xml": ExtensionsClientXML
 }
 
+VOLUMES_EXTENSIONS_CLIENTS = {
+    "json": VolumesExtensionsClientJSON,
+    "xml": VolumesExtensionsClientXML,
+}
+
 
 class Manager(object):
 
@@ -116,6 +123,8 @@
             self.flavors_client = FLAVORS_CLIENTS[interface](*client_args)
             self.extensions_client = \
                     EXTENSIONS_CLIENTS[interface](*client_args)
+            self.volumes_extensions_client = \
+                    VOLUMES_EXTENSIONS_CLIENTS[interface](*client_args)
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
@@ -124,7 +133,6 @@
         self.floating_ips_client = FloatingIPsClient(*client_args)
         self.console_outputs_client = ConsoleOutputsClient(*client_args)
         self.network_client = NetworkClient(*client_args)
-        self.volumes_extensions_client = VolumesExtensionsClient(*client_args)
         self.volumes_client = VolumesClient(*client_args)
 
 
diff --git a/tempest/services/nova/json/volumes_extensions_client.py b/tempest/services/nova/json/volumes_extensions_client.py
index 9b3590b..ed71f71 100644
--- a/tempest/services/nova/json/volumes_extensions_client.py
+++ b/tempest/services/nova/json/volumes_extensions_client.py
@@ -4,10 +4,10 @@
 import time
 
 
-class VolumesExtensionsClient(RestClient):
+class VolumesExtensionsClientJSON(RestClient):
 
     def __init__(self, config, username, password, auth_url, tenant_name=None):
-        super(VolumesExtensionsClient, self).__init__(config, username,
+        super(VolumesExtensionsClientJSON, self).__init__(config, username,
                                                       password, auth_url,
                                                       tenant_name)
         self.service = self.config.compute.catalog_type
diff --git a/tempest/services/nova/xml/volumes_extensions_client.py b/tempest/services/nova/xml/volumes_extensions_client.py
new file mode 100644
index 0000000..fffea44
--- /dev/null
+++ b/tempest/services/nova/xml/volumes_extensions_client.py
@@ -0,0 +1,146 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 IBM
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+
+import time
+from lxml import etree
+
+from tempest import exceptions
+from tempest.common.rest_client import RestClientXML
+from tempest.services.nova.xml.common import xml_to_json
+from tempest.services.nova.xml.common import XMLNS_11
+from tempest.services.nova.xml.common import Element
+from tempest.services.nova.xml.common import Text
+from tempest.services.nova.xml.common import Document
+
+
+class VolumesExtensionsClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(VolumesExtensionsClientXML, self).__init__(config,
+                                                         username, password,
+                                                         auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+        self.build_interval = self.config.compute.build_interval
+        self.build_timeout = self.config.compute.build_timeout
+
+    def _parse_volume(self, body):
+        vol = dict((attr, body.get(attr)) for attr in body.keys())
+
+        for child in body.getchildren():
+            tag = child.tag
+            if tag.startswith("{"):
+                ns, tag = tag.split("}", 1)
+            if tag == 'metadata':
+                vol['metadata'] = dict((meta.get('key'),
+                                        meta.text) for meta in list(child))
+            else:
+                vol[tag] = xml_to_json(child)
+        return vol
+
+    def list_volumes(self, params=None):
+        """List all the volumes created"""
+        url = 'os-volumes'
+
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        body = etree.fromstring(body)
+        volumes = []
+        if body is not None:
+            volumes += [self._parse_volume(vol) for vol in list(body)]
+        return resp, volumes
+
+    def list_volumes_with_detail(self, params=None):
+        """List all the details of volumes"""
+        url = 'os-volumes/detail'
+
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        body = etree.fromstring(body)
+        volumes = []
+        if body is not None:
+            volumes += [self._parse_volume(vol) for vol in list(body)]
+        return resp, volumes
+
+    def get_volume(self, volume_id):
+        """Returns the details of a single volume"""
+        url = "os-volumes/%s" % str(volume_id)
+        resp, body = self.get(url, self.headers)
+        body = etree.fromstring(body)
+        return resp, self._parse_volume(body)
+
+    def create_volume(self, size, display_name=None, metadata=None):
+        """Creates a new Volume.
+
+        :param size: Size of volume in GB. (Required)
+        :param display_name: Optional Volume Name.
+        :param metadata: An optional dictionary of values for metadata.
+        """
+        volume = Element("volume",
+                         xmlns=XMLNS_11,
+                         size=size)
+        if display_name:
+            volume.add_attr('display_name', display_name)
+
+        if metadata:
+            _metadata = Element('metadata')
+            volume.append(_metadata)
+            for key, value in metadata.items():
+                meta = Element('meta')
+                meta.add_attr('key', key)
+                meta.append(Text(value))
+                _metadata.append(meta)
+
+        resp, body = self.post('os-volumes', str(Document(volume)),
+                               self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def delete_volume(self, volume_id):
+        """Deletes the Specified Volume"""
+        return self.delete("os-volumes/%s" % str(volume_id))
+
+    def wait_for_volume_status(self, volume_id, status):
+        """Waits for a Volume to reach a given status"""
+        resp, body = self.get_volume(volume_id)
+        volume_name = body['displayName']
+        volume_status = body['status']
+        start = int(time.time())
+
+        while volume_status != status:
+            time.sleep(self.build_interval)
+            resp, body = self.get_volume(volume_id)
+            volume_status = body['status']
+            if volume_status == 'error':
+                raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
+
+            if int(time.time()) - start >= self.build_timeout:
+                message = 'Volume %s failed to reach %s status within '\
+                          'the required time (%s s).' % (volume_name, status,
+                                                         self.build_timeout)
+                raise exceptions.TimeoutException(message)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.get_volume(id)
+        except exceptions.NotFound:
+            return True
+        return False
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 2617fbb..ee3bf9e 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -34,7 +34,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class BaseComputeTest(unittest.TestCase):
+class BaseCompTest(unittest.TestCase):
 
     """Base test case class for all Compute API tests"""
 
@@ -206,7 +206,7 @@
             time.sleep(self.build_interval)
 
 
-class BaseComputeTestJSON(BaseComputeTest):
+class BaseComputeTestJSON(BaseCompTest):
     @classmethod
     def setUpClass(cls):
         cls._interface = "json"
@@ -216,7 +216,7 @@
 BaseComputeTest = BaseComputeTestJSON
 
 
-class BaseComputeTestXML(BaseComputeTest):
+class BaseComputeTestXML(BaseCompTest):
     @classmethod
     def setUpClass(cls):
         cls._interface = "xml"