diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py index 348666d8ea..9263396c7a 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 QuotasAdminTestJSON(base.BaseV2ComputeAdminTest): 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 7c70aecbb2..be6538d556 100644 --- a/tempest/api/compute/base.py +++ b/tempest/api/compute/base.py @@ -67,6 +67,8 @@ class BaseComputeTest(tempest.test.BaseTestCase): 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 dc85e76e0f..eeff3ce40b 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 62a7556332..ecf70cf754 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 0000000000..3464fb4275 --- /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 7532bf237f..790e78c15a 100644 --- a/tempest/clients.py +++ b/tempest/clients.py @@ -50,6 +50,7 @@ from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON 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 @@ from tempest.services.compute.xml.interfaces_client import \ 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 @@ class Manager(manager.Manager): 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 @@ class Manager(manager.Manager): 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 7e828d8eed..14b71003c1 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 @@ class QuotasClientJSON(rest_client.RestClient): 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 5502fcceac..7f87248c59 100644 --- a/tempest/services/compute/xml/quotas_client.py +++ b/tempest/services/compute/xml/quotas_client.py @@ -22,6 +22,19 @@ from tempest import config 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 @@ class QuotasClientXML(rest_client.RestClient): 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 @@ class QuotasClientXML(rest_client.RestClient): 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 @@ class QuotasClientXML(rest_client.RestClient): 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 @@ class QuotasClientXML(rest_client.RestClient): 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