Merge "Add XML support for flavors_client"
diff --git a/tempest/manager.py b/tempest/manager.py
index 4469301..3856eed 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -38,7 +38,7 @@
 
 NetworkClient = network_client.NetworkClient
 ImagesClient = images_client.ImagesClient
-FlavorsClient = flavors_client.FlavorsClient
+FlavorsClient = flavors_client.FlavorsClientJSON
 ServersClient = servers_client.ServersClientJSON
 LimitsClient = limits_client.LimitsClientJSON
 ExtensionsClient = extensions_client.ExtensionsClient
diff --git a/tempest/openstack.py b/tempest/openstack.py
index e5f6404..241d8bd 100644
--- a/tempest/openstack.py
+++ b/tempest/openstack.py
@@ -21,8 +21,8 @@
 from tempest import exceptions
 from tempest.services.image import service as image_service
 from tempest.services.network.json.network_client import NetworkClient
+from tempest.services.nova.json.flavors_client import FlavorsClientJSON
 from tempest.services.nova.json.images_client import ImagesClient
-from tempest.services.nova.json.flavors_client import FlavorsClient
 from tempest.services.nova.json.limits_client import LimitsClientJSON
 from tempest.services.nova.json.servers_client import ServersClientJSON
 from tempest.services.nova.json.extensions_client import ExtensionsClient
@@ -33,6 +33,7 @@
 from tempest.services.nova.json.volumes_client import VolumesClient
 from tempest.services.nova.json.console_output_client \
 import ConsoleOutputsClient
+from tempest.services.nova.xml.flavors_client import FlavorsClientXML
 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
@@ -54,6 +55,11 @@
     "xml": LimitsClientXML,
 }
 
+FLAVORS_CLIENTS = {
+    "json": FlavorsClientJSON,
+    "xml": FlavorsClientXML
+}
+
 
 class Manager(object):
 
@@ -98,10 +104,10 @@
             self.servers_client = SERVERS_CLIENTS[interface](*client_args)
             self.limits_client = LIMITS_CLIENTS[interface](*client_args)
             self.keypairs_client = KEYPAIRS_CLIENTS[interface](*client_args)
+            self.flavors_client = FLAVORS_CLIENTS[interface](*client_args)
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
-        self.flavors_client = FlavorsClient(*client_args)
         self.images_client = ImagesClient(*client_args)
         self.extensions_client = ExtensionsClient(*client_args)
         self.security_groups_client = SecurityGroupsClient(*client_args)
@@ -132,11 +138,12 @@
     managed client objects
     """
 
-    def __init__(self):
+    def __init__(self, interface='json'):
         conf = config.TempestConfig()
         super(AdminManager, self).__init__(conf.compute_admin.username,
                                            conf.compute_admin.password,
-                                           conf.compute_admin.tenant_name)
+                                           conf.compute_admin.tenant_name,
+                                           interface=interface)
 
 
 class ServiceManager(object):
diff --git a/tempest/services/nova/json/flavors_client.py b/tempest/services/nova/json/flavors_client.py
index bd77484..5f7d918 100644
--- a/tempest/services/nova/json/flavors_client.py
+++ b/tempest/services/nova/json/flavors_client.py
@@ -2,11 +2,11 @@
 import json
 
 
-class FlavorsClient(RestClient):
+class FlavorsClientJSON(RestClient):
 
     def __init__(self, config, username, password, auth_url, tenant_name=None):
-        super(FlavorsClient, self).__init__(config, username, password,
-                                            auth_url, tenant_name)
+        super(FlavorsClientJSON, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
         self.service = self.config.compute.catalog_type
 
     def list_flavors(self, params=None):
diff --git a/tempest/services/nova/xml/flavors_client.py b/tempest/services/nova/xml/flavors_client.py
new file mode 100644
index 0000000..1ea6c5a
--- /dev/null
+++ b/tempest/services/nova/xml/flavors_client.py
@@ -0,0 +1,92 @@
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.nova.xml.common import Document
+from tempest.services.nova.xml.common import Element
+from tempest.services.nova.xml.common import xml_to_json
+from tempest.services.nova.xml.common import XMLNS_11
+
+
+XMLNS_OS_FLV_EXT_DATA = \
+        "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1"
+
+
+class FlavorsClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(FlavorsClientXML, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def _format_flavor(self, f):
+        flavor = {'links': []}
+        for k, v in f.items():
+            if k == 'link':
+                flavor['links'].append(v)
+                continue
+
+            if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA:
+                k = 'OS-FLV-EXT-DATA:ephemeral'
+
+            try:
+                v = int(v)
+            except ValueError:
+                try:
+                    v = float(v)
+                except ValueError:
+                    pass
+
+            flavor[k] = v
+
+        return flavor
+
+    def _parse_array(self, node):
+        return [self._format_flavor(xml_to_json(x)) for x in node]
+
+    def _list_flavors(self, url, params):
+        if params != None:
+            url += "?%s" % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        flavors = self._parse_array(etree.fromstring(body))
+        return resp, flavors
+
+    def list_flavors(self, params=None):
+        url = 'flavors'
+        return self._list_flavors(url, params)
+
+    def list_flavors_with_detail(self, params=None):
+        url = 'flavors/detail'
+        return self._list_flavors(url, params)
+
+    def get_flavor_details(self, flavor_id):
+        resp, body = self.get("flavors/%s" % str(flavor_id), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        flavor = self._format_flavor(body)
+        return resp, flavor
+
+    def create_flavor(self, name, ram, vcpus, disk, ephemeral, flavor_id,
+                    swap, rxtx):
+        """Creates a new flavor or instance type"""
+        flavor = Element("flavor",
+                         xmlns=XMLNS_11,
+                         ram=ram,
+                         vcpus=vcpus,
+                         disk=disk,
+                         id=flavor_id,
+                         swap=swap,
+                         rxtx_factor=rxtx,
+                         name=name)
+        flavor.add_attr('xmlns:OS-FLV-EXT-DATA', XMLNS_OS_FLV_EXT_DATA)
+        flavor.add_attr('OS-FLV-EXT-DATA:ephemeral', ephemeral)
+
+        resp, body = self.post('flavors', str(Document(flavor)), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        flavor = self._format_flavor(body)
+        return resp, flavor
+
+    def delete_flavor(self, flavor_id):
+        """Deletes the given flavor"""
+        return self.delete("flavors/%s" % str(flavor_id), self.headers)
diff --git a/tempest/tests/compute/admin/test_flavors.py b/tempest/tests/compute/admin/test_flavors.py
index eac33f1..be77411 100644
--- a/tempest/tests/compute/admin/test_flavors.py
+++ b/tempest/tests/compute/admin/test_flavors.py
@@ -19,24 +19,22 @@
 from nose.plugins.attrib import attr
 import unittest2 as unittest
 
-from tempest import exceptions
-from tempest.tests.compute.base import BaseComputeAdminTest
+from tempest.tests.compute import base
 from tempest.tests import compute
 
 
-class FlavorsAdminTest(BaseComputeAdminTest):
+class FlavorsAdminTestBase(object):
 
     """
     Tests Flavors API Create and Delete that require admin privileges
     """
 
-    @classmethod
+    @staticmethod
     def setUpClass(cls):
         if not compute.FLAVOR_EXTRA_DATA_ENABLED:
             msg = "FlavorExtraData extension not enabled."
             raise nose.SkipTest(msg)
 
-        super(FlavorsAdminTest, cls).setUpClass()
         cls.client = cls.os.flavors_client
         cls.flavor_name = 'test_flavor'
         cls.ram = 512
@@ -133,3 +131,21 @@
             if flavor['name'] == self.flavor_name:
                 flag = False
         self.assertTrue(flag)
+
+
+class FlavorsAdminTestXML(base.BaseComputeAdminTestXML,
+                          FlavorsAdminTestBase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsAdminTestXML, cls).setUpClass()
+        FlavorsAdminTestBase.setUpClass(cls)
+
+
+class FlavorsAdminTestJSON(base.BaseComputeAdminTestJSON,
+                            FlavorsAdminTestBase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsAdminTestJSON, cls).setUpClass()
+        FlavorsAdminTestBase.setUpClass(cls)
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 8b084ff..5e6eb7d 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -21,15 +21,17 @@
 import unittest2 as unittest
 
 from tempest import config
-from tempest import exceptions
 from tempest import openstack
 from tempest.common.utils.data_utils import rand_name
 from tempest.services.identity.json.admin_client import AdminClient
 
+__all__ = ['BaseComputeTest', 'BaseComputeTestJSON', 'BaseComputeTestXML',
+           'BaseComputeAdminTestJSON', 'BaseComputeAdminTestXML']
+
 LOG = logging.getLogger(__name__)
 
 
-class _BaseComputeTest(unittest.TestCase):
+class BaseComputeTest(unittest.TestCase):
 
     """Base test case class for all Compute API tests"""
 
@@ -200,7 +202,7 @@
             time.sleep(self.build_interval)
 
 
-class BaseComputeTestJSON(_BaseComputeTest):
+class BaseComputeTestJSON(BaseComputeTest):
     @classmethod
     def setUpClass(cls):
         cls._interface = "json"
@@ -210,7 +212,7 @@
 BaseComputeTest = BaseComputeTestJSON
 
 
-class BaseComputeTestXML(_BaseComputeTest):
+class BaseComputeTestXML(BaseComputeTest):
     @classmethod
     def setUpClass(cls):
         cls._interface = "xml"
@@ -233,4 +235,18 @@
                    "in configuration.")
             raise nose.SkipTest(msg)
 
-        cls.os = openstack.AdminManager()
+        cls.os = openstack.AdminManager(interface=cls._interface)
+
+
+class BaseComputeAdminTestJSON(BaseComputeAdminTest):
+    @classmethod
+    def setUpClass(cls):
+        cls._interface = "json"
+        super(BaseComputeAdminTestJSON, cls).setUpClass()
+
+
+class BaseComputeAdminTestXML(BaseComputeAdminTest):
+    @classmethod
+    def setUpClass(cls):
+        cls._interface = "xml"
+        super(BaseComputeAdminTestXML, cls).setUpClass()
diff --git a/tempest/tests/compute/test_flavors.py b/tempest/tests/compute/test_flavors.py
index dea8ac3..ac5aac5 100644
--- a/tempest/tests/compute/test_flavors.py
+++ b/tempest/tests/compute/test_flavors.py
@@ -18,15 +18,10 @@
 from nose.plugins.attrib import attr
 
 from tempest import exceptions
-from tempest.tests.compute.base import BaseComputeTest
+from tempest.tests.compute import base
 
 
-class FlavorsTest(BaseComputeTest):
-
-    @classmethod
-    def setUpClass(cls):
-        super(FlavorsTest, cls).setUpClass()
-        cls.client = cls.flavors_client
+class FlavorsTestBase(object):
 
     @attr(type='smoke')
     def test_list_flavors(self):
@@ -141,3 +136,21 @@
         """Ensure 404 returned for non-existant flavor ID"""
         self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
                         9999)
+
+
+class FlavorsTestXML(base.BaseComputeTestXML,
+                     FlavorsTestBase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsTestXML, cls).setUpClass()
+        cls.client = cls.flavors_client
+
+
+class FlavorsTestJSON(base.BaseComputeTestJSON,
+                      FlavorsTestBase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsTestJSON, cls).setUpClass()
+        cls.client = cls.flavors_client