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
This commit is contained in:
Matt Riedemann 2014-06-16 13:23:51 -07:00
parent ffe30e0729
commit 848805f21b
8 changed files with 187 additions and 15 deletions

View File

@ -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'

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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']

View File

@ -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)

View File

@ -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']

View File

@ -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