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