Add test for compute API os-quota-class-sets
This tests showing and updating the 'default' quota class, which is
really all this API is used for. It was removed in Icehouse in part to
not knowing it was used even though python-novaclient and Horizon were
using it. The removal was reverted and this change is to enforce what
the API is used for.
The extension only lives in the Nova v2 API, it was removed in the v3
API in Icehouse.
This change adds new quota client and test classes for the new API.
An external lock fixture is used in all compute API quota tests to
avoid introducing race bugs with the existing tenant-specific
os-quota-sets tests.
Closes-Bug: #1325727
Change-Id: Ib0cde08dfaa0f6a5e180d247864fb59d76eca903
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 348666d..9263396 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -13,14 +13,26 @@
# License for the specific language governing permissions and limitations
# under the License.
+import six
+from testtools import matchers
+
from tempest.api.compute import base
+from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest.openstack.common import log as logging
from tempest import test
+LOG = logging.getLogger(__name__)
+
class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest):
force_tenant_isolation = True
+ def setUp(self):
+ # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
+ self.useFixture(fixtures.LockFixture('compute_quotas'))
+ super(QuotasAdminTestJSON, self).setUp()
+
@classmethod
def setUpClass(cls):
super(QuotasAdminTestJSON, cls).setUpClass()
@@ -134,3 +146,49 @@
class QuotasAdminTestXML(QuotasAdminTestJSON):
_interface = 'xml'
+
+
+class QuotaClassesAdminTestJSON(base.BaseV2ComputeAdminTest):
+ """Tests the os-quota-class-sets API to update default quotas.
+ """
+
+ def setUp(self):
+ # All test cases in this class need to externally lock on doing
+ # anything with default quota values.
+ self.useFixture(fixtures.LockFixture('compute_quotas'))
+ super(QuotaClassesAdminTestJSON, self).setUp()
+
+ @classmethod
+ def setUpClass(cls):
+ super(QuotaClassesAdminTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.quota_classes_client
+
+ def _restore_default_quotas(self, original_defaults):
+ LOG.debug("restoring quota class defaults")
+ resp, body = self.adm_client.update_quota_class_set(
+ 'default', **original_defaults)
+ self.assertEqual(200, resp.status)
+
+ @test.attr(type='gate')
+ def test_update_default_quotas(self):
+ LOG.debug("get the current 'default' quota class values")
+ resp, body = self.adm_client.get_quota_class_set('default')
+ self.assertEqual(200, resp.status)
+ self.assertIn('id', body)
+ self.assertEqual('default', body.pop('id'))
+ # restore the defaults when the test is done
+ self.addCleanup(self._restore_default_quotas, body.copy())
+ # increment all of the values for updating the default quota class
+ for quota, default in six.iteritems(body):
+ body[quota] = default + 1
+ LOG.debug("update limits for the default quota class set")
+ resp, update_body = self.adm_client.update_quota_class_set('default',
+ **body)
+ self.assertEqual(200, resp.status)
+ LOG.debug("assert that the response has all of the changed values")
+ self.assertThat(update_body.items(),
+ matchers.ContainsAll(body.items()))
+
+
+class QuotaClassesAdminTestXML(QuotaClassesAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 7c70aec..be6538d 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -67,6 +67,8 @@
cls.keypairs_client = cls.os.keypairs_client
cls.security_groups_client = cls.os.security_groups_client
cls.quotas_client = cls.os.quotas_client
+ # NOTE(mriedem): os-quota-class-sets is v2 API only
+ cls.quota_classes_client = cls.os.quota_classes_client
cls.limits_client = cls.os.limits_client
cls.volumes_extensions_client = cls.os.volumes_extensions_client
cls.volumes_client = cls.os.volumes_client
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index dc85e76..eeff3ce 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -14,11 +14,17 @@
# under the License.
from tempest.api.compute import base
+from tempest.common import tempest_fixtures as fixtures
from tempest import test
class QuotasTestJSON(base.BaseV2ComputeTest):
+ def setUp(self):
+ # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
+ self.useFixture(fixtures.LockFixture('compute_quotas'))
+ super(QuotasTestJSON, self).setUp()
+
@classmethod
def setUpClass(cls):
super(QuotasTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/v3/test_quotas.py b/tempest/api/compute/v3/test_quotas.py
index 62a7556..ecf70cf 100644
--- a/tempest/api/compute/v3/test_quotas.py
+++ b/tempest/api/compute/v3/test_quotas.py
@@ -14,11 +14,17 @@
# under the License.
from tempest.api.compute import base
+from tempest.common import tempest_fixtures as fixtures
from tempest import test
class QuotasV3Test(base.BaseV3ComputeTest):
+ def setUp(self):
+ # NOTE(mriedem): Avoid conflicts with os-quota-class-sets tests.
+ self.useFixture(fixtures.LockFixture('compute_quotas'))
+ super(QuotasV3Test, self).setUp()
+
@classmethod
def setUpClass(cls):
super(QuotasV3Test, cls).setUpClass()
diff --git a/tempest/api_schema/compute/v2/quota_classes.py b/tempest/api_schema/compute/v2/quota_classes.py
new file mode 100644
index 0000000..3464fb4
--- /dev/null
+++ b/tempest/api_schema/compute/v2/quota_classes.py
@@ -0,0 +1,31 @@
+# Copyright 2014 IBM Corporation.
+# 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 copy
+
+from tempest.api_schema.compute.v2 import quotas
+
+# NOTE(mriedem): os-quota-class-sets responses are the same as os-quota-sets
+# except for the key in the response body is quota_class_set instead of
+# quota_set, so update this copy of the schema from os-quota-sets.
+quota_set = copy.deepcopy(quotas.quota_set)
+quota_set['response_body']['properties']['quota_class_set'] = (
+ quota_set['response_body']['properties'].pop('quota_set'))
+quota_set['response_body']['required'] = ['quota_class_set']
+
+quota_set_update = copy.deepcopy(quotas.quota_set_update)
+quota_set_update['response_body']['properties']['quota_class_set'] = (
+ quota_set_update['response_body']['properties'].pop('quota_set'))
+quota_set_update['response_body']['required'] = ['quota_class_set']
diff --git a/tempest/clients.py b/tempest/clients.py
index 7532bf2..790e78c 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -50,6 +50,7 @@
from tempest.services.compute.json.limits_client import LimitsClientJSON
from tempest.services.compute.json.migrations_client import \
MigrationsClientJSON
+from tempest.services.compute.json.quotas_client import QuotaClassesClientJSON
from tempest.services.compute.json.quotas_client import QuotasClientJSON
from tempest.services.compute.json.security_groups_client import \
SecurityGroupsClientJSON
@@ -105,6 +106,7 @@
InterfacesClientXML
from tempest.services.compute.xml.keypairs_client import KeyPairsClientXML
from tempest.services.compute.xml.limits_client import LimitsClientXML
+from tempest.services.compute.xml.quotas_client import QuotaClassesClientXML
from tempest.services.compute.xml.quotas_client import QuotasClientXML
from tempest.services.compute.xml.security_groups_client \
import SecurityGroupsClientXML
@@ -220,6 +222,8 @@
self.images_client = ImagesClientXML(self.auth_provider)
self.keypairs_client = KeyPairsClientXML(self.auth_provider)
self.quotas_client = QuotasClientXML(self.auth_provider)
+ self.quota_classes_client = QuotaClassesClientXML(
+ self.auth_provider)
self.flavors_client = FlavorsClientXML(self.auth_provider)
self.extensions_client = ExtensionsClientXML(self.auth_provider)
self.volumes_extensions_client = VolumesExtensionsClientXML(
@@ -288,6 +292,8 @@
self.keypairs_v3_client = KeyPairsV3ClientJSON(
self.auth_provider)
self.quotas_client = QuotasClientJSON(self.auth_provider)
+ self.quota_classes_client = QuotaClassesClientJSON(
+ self.auth_provider)
self.quotas_v3_client = QuotasV3ClientJSON(self.auth_provider)
self.flavors_client = FlavorsClientJSON(self.auth_provider)
self.flavors_v3_client = FlavorsV3ClientJSON(self.auth_provider)
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 7e828d8..14b7100 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v2 import quota_classes as classes_schema
from tempest.api_schema.compute.v2 import quotas as schema
from tempest.common import rest_client
from tempest import config
@@ -118,3 +119,32 @@
resp, body = self.delete('os-quota-sets/%s' % str(tenant_id))
self.validate_response(schema.delete_quota, resp, body)
return resp, body
+
+
+class QuotaClassesClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(QuotaClassesClientJSON, self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_type
+
+ def get_quota_class_set(self, quota_class_id):
+ """List the quota class set for a quota class."""
+
+ url = 'os-quota-class-sets/%s' % str(quota_class_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.validate_response(classes_schema.quota_set, resp, body)
+ return resp, body['quota_class_set']
+
+ def update_quota_class_set(self, quota_class_id, **kwargs):
+ """
+ Updates the quota class's limits for one or more resources.
+ """
+ post_body = json.dumps({'quota_class_set': kwargs})
+
+ resp, body = self.put('os-quota-class-sets/%s' % str(quota_class_id),
+ post_body)
+
+ body = json.loads(body)
+ self.validate_response(classes_schema.quota_set_update, resp, body)
+ return resp, body['quota_class_set']
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
index 5502fcc..7f87248 100644
--- a/tempest/services/compute/xml/quotas_client.py
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -22,6 +22,19 @@
CONF = config.CONF
+def format_quota(q):
+ quota = {}
+ for k, v in q.items():
+ try:
+ v = int(v)
+ except ValueError:
+ pass
+
+ quota[k] = v
+
+ return quota
+
+
class QuotasClientXML(rest_client.RestClient):
TYPE = "xml"
@@ -29,18 +42,6 @@
super(QuotasClientXML, self).__init__(auth_provider)
self.service = CONF.compute.catalog_type
- def _format_quota(self, q):
- quota = {}
- for k, v in q.items():
- try:
- v = int(v)
- except ValueError:
- pass
-
- quota[k] = v
-
- return quota
-
def get_quota_set(self, tenant_id, user_id=None):
"""List the quota set for a tenant."""
@@ -49,7 +50,7 @@
url += '?user_id=%s' % str(user_id)
resp, body = self.get(url)
body = xml_utils.xml_to_json(etree.fromstring(body))
- body = self._format_quota(body)
+ body = format_quota(body)
return resp, body
def get_default_quota_set(self, tenant_id):
@@ -58,7 +59,7 @@
url = 'os-quota-sets/%s/defaults' % str(tenant_id)
resp, body = self.get(url)
body = xml_utils.xml_to_json(etree.fromstring(body))
- body = self._format_quota(body)
+ body = format_quota(body)
return resp, body
def update_quota_set(self, tenant_id, user_id=None,
@@ -124,9 +125,41 @@
str(xml_utils.Document(post_body)))
body = xml_utils.xml_to_json(etree.fromstring(body))
- body = self._format_quota(body)
+ body = format_quota(body)
return resp, body
def delete_quota_set(self, tenant_id):
"""Delete the tenant's quota set."""
return self.delete('os-quota-sets/%s' % str(tenant_id))
+
+
+class QuotaClassesClientXML(rest_client.RestClient):
+ TYPE = "xml"
+
+ def __init__(self, auth_provider):
+ super(QuotaClassesClientXML, self).__init__(auth_provider)
+ self.service = CONF.compute.catalog_type
+
+ def get_quota_class_set(self, quota_class_id):
+ """List the quota class set for a quota class."""
+
+ url = 'os-quota-class-sets/%s' % str(quota_class_id)
+ resp, body = self.get(url)
+ body = xml_utils.xml_to_json(etree.fromstring(body))
+ body = format_quota(body)
+ return resp, body
+
+ def update_quota_class_set(self, quota_class_id, **kwargs):
+ """
+ Updates the quota class's limits for one or more resources.
+ """
+ post_body = xml_utils.Element("quota_class_set",
+ xmlns=xml_utils.XMLNS_11,
+ **kwargs)
+
+ resp, body = self.put('os-quota-class-sets/%s' % str(quota_class_id),
+ str(xml_utils.Document(post_body)))
+
+ body = xml_utils.xml_to_json(etree.fromstring(body))
+ body = format_quota(body)
+ return resp, body