Assert quota related API behavior when noop

Adding tests so its clear what happens with the noop driver when using
the quota APIs.

To make the unit tests work, we had to make the caching of the quota
driver slightly more dynamic. We verify the current config matches
the currently cached driver, and reload the driver if there is a miss-match.
It also preserves the ability of some unit tests to pass in a fake
quota driver.

We also test the current unified limits driver, as it is currently
identical in behaviour to the noop driver.
As things evolve the tests will diverge, but will show the common
approach to what is returned from the API in both cases.

blueprint unified-limits-nova

Change-Id: If3c58d6cbf0a0aee62766c7142beab165c1fb9a4
This commit is contained in:
John Garbutt
2020-03-10 12:31:26 +00:00
committed by melanie witt
parent 4fbe94a98c
commit 6acefc6b10
6 changed files with 414 additions and 4 deletions
+2
View File
@@ -181,6 +181,8 @@ Possible values:
'on-demand.'),
('nova.quota.NoopQuotaDriver', 'Ignores quota and treats all '
'resources as unlimited.'),
('nova.quota.UnifiedLimitsDriver', 'Do not use. Still being '
'developed.')
],
help="""
Provides abstraction for quota checks. Users can configure a specific
+14 -4
View File
@@ -882,13 +882,23 @@ class QuotaEngine(object):
}
# NOTE(mriedem): quota_driver is ever only supplied in tests with a
# fake driver.
self.__driver = quota_driver
self.__driver_override = quota_driver
self.__driver = None
self.__driver_name = None
@property
def _driver(self):
if self.__driver:
return self.__driver
self.__driver = importutils.import_object(CONF.quota.driver)
if self.__driver_override:
return self.__driver_override
# NOTE(johngarbutt) to allow unit tests to change the driver by
# simply overriding config, double check if we have the correct
# driver cached before we return the currently cached driver
driver_name_in_config = CONF.quota.driver
if self.__driver_name != driver_name_in_config:
self.__driver = importutils.import_object(driver_name_in_config)
self.__driver_name = driver_name_in_config
return self.__driver
def get_defaults(self, context):
@@ -477,3 +477,75 @@ class LimitsControllerTestV275(BaseLimitTestSuite):
self.assertRaises(
exception.ValidationError,
self.controller.index, req=req)
class NoopLimitsControllerTest(test.NoDBTestCase):
quota_driver = "nova.quota.NoopQuotaDriver"
def setUp(self):
super(NoopLimitsControllerTest, self).setUp()
self.flags(driver=self.quota_driver, group="quota")
self.controller = limits_v21.LimitsController()
# remove policy checks
patcher = self.mock_can = mock.patch('nova.context.RequestContext.can')
self.mock_can = patcher.start()
self.addCleanup(patcher.stop)
def test_index_v21(self):
req = fakes.HTTPRequest.blank("/")
response = self.controller.index(req)
expected_response = {
"limits": {
"rate": [],
"absolute": {
'maxImageMeta': -1,
'maxPersonality': -1,
'maxPersonalitySize': -1,
'maxSecurityGroupRules': -1,
'maxSecurityGroups': -1,
'maxServerGroupMembers': -1,
'maxServerGroups': -1,
'maxServerMeta': -1,
'maxTotalCores': -1,
'maxTotalFloatingIps': -1,
'maxTotalInstances': -1,
'maxTotalKeypairs': -1,
'maxTotalRAMSize': -1,
'totalCoresUsed': -1,
'totalFloatingIpsUsed': -1,
'totalInstancesUsed': -1,
'totalRAMUsed': -1,
'totalSecurityGroupsUsed': -1,
'totalServerGroupsUsed': -1,
},
},
}
self.assertEqual(expected_response, response)
def test_index_v275(self):
req = fakes.HTTPRequest.blank("/?tenant_id=faketenant",
version='2.75')
response = self.controller.index(req)
expected_response = {
"limits": {
"rate": [],
"absolute": {
'maxServerGroupMembers': -1,
'maxServerGroups': -1,
'maxServerMeta': -1,
'maxTotalCores': -1,
'maxTotalInstances': -1,
'maxTotalKeypairs': -1,
'maxTotalRAMSize': -1,
'totalCoresUsed': -1,
'totalInstancesUsed': -1,
'totalRAMUsed': -1,
'totalServerGroupsUsed': -1,
},
},
}
self.assertEqual(expected_response, response)
class UnifiedLimitsControllerTest(NoopLimitsControllerTest):
quota_driver = "nova.quota.UnifiedLimitsDriver"
@@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import mock
import webob
from nova.api.openstack.compute import quota_classes \
as quota_classes_v21
from nova import exception
from nova import objects
from nova import test
from nova.tests.unit.api.openstack import fakes
@@ -156,3 +158,107 @@ class QuotaClassSetsTestV257(QuotaClassSetsTestV250):
for resource in quota_classes_v21.FILTERED_QUOTAS_2_57:
self.quota_resources.pop(resource, None)
self.filtered_quotas.extend(quota_classes_v21.FILTERED_QUOTAS_2_57)
class NoopQuotaClassesTest(test.NoDBTestCase):
quota_driver = "nova.quota.NoopQuotaDriver"
def setUp(self):
super(NoopQuotaClassesTest, self).setUp()
self.flags(driver=self.quota_driver, group="quota")
self.controller = quota_classes_v21.QuotaClassSetsController()
def test_show_v21(self):
req = fakes.HTTPRequest.blank("")
response = self.controller.show(req, "test_class")
expected_response = {
'quota_class_set': {
'id': 'test_class',
'cores': -1,
'fixed_ips': -1,
'floating_ips': -1,
'injected_file_content_bytes': -1,
'injected_file_path_bytes': -1,
'injected_files': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'security_group_rules': -1,
'security_groups': -1
}
}
self.assertEqual(expected_response, response)
def test_show_v257(self):
req = fakes.HTTPRequest.blank("", version='2.57')
response = self.controller.show(req, "default")
expected_response = {
'quota_class_set': {
'id': 'default',
'cores': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'server_group_members': -1,
'server_groups': -1,
}
}
self.assertEqual(expected_response, response)
def test_update_v21_still_rejects_badrequests(self):
req = fakes.HTTPRequest.blank("")
body = {'quota_class_set': {'instances': 50, 'cores': 50,
'ram': 51200, 'unsupported': 12}}
self.assertRaises(exception.ValidationError, self.controller.update,
req, 'test_class', body=body)
@mock.patch.object(objects.Quotas, "update_class")
def test_update_v21(self, mock_update):
req = fakes.HTTPRequest.blank("")
body = {'quota_class_set': {'ram': 51200}}
response = self.controller.update(req, 'default', body=body)
expected_response = {
'quota_class_set': {
'cores': -1,
'fixed_ips': -1,
'floating_ips': -1,
'injected_file_content_bytes': -1,
'injected_file_path_bytes': -1,
'injected_files': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'security_group_rules': -1,
'security_groups': -1
}
}
self.assertEqual(expected_response, response)
mock_update.assert_called_once_with(req.environ['nova.context'],
"default", "ram", 51200)
@mock.patch.object(objects.Quotas, "update_class")
def test_update_v257(self, mock_update):
req = fakes.HTTPRequest.blank("", version='2.57')
body = {'quota_class_set': {'ram': 51200}}
response = self.controller.update(req, 'default', body=body)
expected_response = {
'quota_class_set': {
'cores': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'server_group_members': -1,
'server_groups': -1,
}
}
self.assertEqual(expected_response, response)
mock_update.assert_called_once_with(req.environ['nova.context'],
"default", "ram", 51200)
class UnifiedLimitsQuotaClassesTest(NoopQuotaClassesTest):
quota_driver = "nova.quota.UnifiedLimitsDriver"
@@ -15,11 +15,13 @@
# under the License.
import mock
from oslo_utils.fixture import uuidsentinel as uuids
import webob
from nova.api.openstack.compute import quota_sets as quotas_v21
from nova.db import constants as db_const
from nova import exception
from nova import objects
from nova import quota
from nova import test
from nova.tests.unit.api.openstack import fakes
@@ -660,3 +662,208 @@ class QuotaSetsTestV275(QuotaSetsTestV257):
query_string=query_string)
self.assertRaises(exception.ValidationError, self.controller.delete,
req, 1234)
class NoopQuotaSetsTest(test.NoDBTestCase):
quota_driver = "nova.quota.NoopQuotaDriver"
def setUp(self):
super(NoopQuotaSetsTest, self).setUp()
self.flags(driver=self.quota_driver, group="quota")
self.controller = quotas_v21.QuotaSetsController()
self.stub_out('nova.api.openstack.identity.verify_project_id',
lambda ctx, project_id: True)
def test_show_v21(self):
req = fakes.HTTPRequest.blank("")
response = self.controller.show(req, uuids.project_id)
expected_response = {
'quota_set': {
'id': uuids.project_id,
'cores': -1,
'fixed_ips': -1,
'floating_ips': -1,
'injected_file_content_bytes': -1,
'injected_file_path_bytes': -1,
'injected_files': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'security_group_rules': -1,
'security_groups': -1,
'server_group_members': -1,
'server_groups': -1,
}
}
self.assertEqual(expected_response, response)
def test_show_v257(self):
req = fakes.HTTPRequest.blank("", version='2.57')
response = self.controller.show(req, uuids.project_id)
expected_response = {
'quota_set': {
'id': uuids.project_id,
'cores': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'server_group_members': -1,
'server_groups': -1}}
self.assertEqual(expected_response, response)
def test_detail_v21(self):
req = fakes.HTTPRequest.blank("")
response = self.controller.detail(req, uuids.project_id)
expected_detail = {'in_use': -1, 'limit': -1, 'reserved': -1}
expected_response = {
'quota_set': {
'id': uuids.project_id,
'cores': expected_detail,
'fixed_ips': expected_detail,
'floating_ips': expected_detail,
'injected_file_content_bytes': expected_detail,
'injected_file_path_bytes': expected_detail,
'injected_files': expected_detail,
'instances': expected_detail,
'key_pairs': expected_detail,
'metadata_items': expected_detail,
'ram': expected_detail,
'security_group_rules': expected_detail,
'security_groups': expected_detail,
'server_group_members': expected_detail,
'server_groups': expected_detail,
}
}
self.assertEqual(expected_response, response)
def test_detail_v21_user(self):
req = fakes.HTTPRequest.blank("?user_id=42")
response = self.controller.detail(req, uuids.project_id)
expected_detail = {'in_use': -1, 'limit': -1, 'reserved': -1}
expected_response = {
'quota_set': {
'id': uuids.project_id,
'cores': expected_detail,
'fixed_ips': expected_detail,
'floating_ips': expected_detail,
'injected_file_content_bytes': expected_detail,
'injected_file_path_bytes': expected_detail,
'injected_files': expected_detail,
'instances': expected_detail,
'key_pairs': expected_detail,
'metadata_items': expected_detail,
'ram': expected_detail,
'security_group_rules': expected_detail,
'security_groups': expected_detail,
'server_group_members': expected_detail,
'server_groups': expected_detail,
}
}
self.assertEqual(expected_response, response)
def test_update_still_rejects_badrequests(self):
req = fakes.HTTPRequest.blank("")
body = {'quota_set': {'instances': 50, 'cores': 50,
'ram': 51200, 'unsupported': 12}}
self.assertRaises(exception.ValidationError, self.controller.update,
req, uuids.project_id, body=body)
@mock.patch.object(objects.Quotas, "create_limit")
def test_update_v21(self, mock_create):
req = fakes.HTTPRequest.blank("")
body = {'quota_set': {'server_groups': 2}}
response = self.controller.update(req, uuids.project_id, body=body)
expected_response = {
'quota_set': {
'cores': -1,
'fixed_ips': -1,
'floating_ips': -1,
'injected_file_content_bytes': -1,
'injected_file_path_bytes': -1,
'injected_files': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'security_group_rules': -1,
'security_groups': -1,
'server_group_members': -1,
'server_groups': -1,
}
}
self.assertEqual(expected_response, response)
mock_create.assert_called_once_with(req.environ['nova.context'],
uuids.project_id, "server_groups",
2, user_id=None)
@mock.patch.object(objects.Quotas, "create_limit")
def test_update_v21_user(self, mock_create):
req = fakes.HTTPRequest.blank("?user_id=42")
body = {'quota_set': {'key_pairs': 52}}
response = self.controller.update(req, uuids.project_id, body=body)
expected_response = {
'quota_set': {
'cores': -1,
'fixed_ips': -1,
'floating_ips': -1,
'injected_file_content_bytes': -1,
'injected_file_path_bytes': -1,
'injected_files': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'security_group_rules': -1,
'security_groups': -1,
'server_group_members': -1,
'server_groups': -1,
}
}
self.assertEqual(expected_response, response)
mock_create.assert_called_once_with(req.environ['nova.context'],
uuids.project_id, "key_pairs", 52,
user_id="42")
def test_defaults_v21(self):
req = fakes.HTTPRequest.blank("")
response = self.controller.defaults(req, uuids.project_id)
expected_response = {
'quota_set': {
'id': uuids.project_id,
'cores': -1,
'fixed_ips': -1,
'floating_ips': -1,
'injected_file_content_bytes': -1,
'injected_file_path_bytes': -1,
'injected_files': -1,
'instances': -1,
'key_pairs': -1,
'metadata_items': -1,
'ram': -1,
'security_group_rules': -1,
'security_groups': -1,
'server_group_members': -1,
'server_groups': -1,
}
}
self.assertEqual(expected_response, response)
@mock.patch('nova.objects.Quotas.destroy_all_by_project')
def test_quotas_delete(self, mock_destroy_all_by_project):
req = fakes.HTTPRequest.blank("")
self.controller.delete(req, "1234")
mock_destroy_all_by_project.assert_called_once_with(
req.environ['nova.context'], "1234")
@mock.patch('nova.objects.Quotas.destroy_all_by_project_and_user')
def test_user_quotas_delete(self, mock_destroy_all_by_user):
req = fakes.HTTPRequest.blank("?user_id=42")
self.controller.delete(req, "1234")
mock_destroy_all_by_user.assert_called_once_with(
req.environ['nova.context'], "1234", "42")
class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest):
quota_driver = "nova.quota.UnifiedLimitsDriver"
+13
View File
@@ -340,6 +340,19 @@ class QuotaEngineTestCase(test.TestCase):
quota_obj = quota.QuotaEngine(quota_driver=FakeDriver)
self.assertEqual(quota_obj._driver, FakeDriver)
def test_init_with_flag_set(self):
quota_obj = quota.QuotaEngine()
self.assertIsInstance(quota_obj._driver, quota.DbQuotaDriver)
self.flags(group="quota", driver="nova.quota.NoopQuotaDriver")
self.assertIsInstance(quota_obj._driver, quota.NoopQuotaDriver)
self.flags(group="quota", driver="nova.quota.UnifiedLimitsDriver")
self.assertIsInstance(quota_obj._driver, quota.UnifiedLimitsDriver)
self.flags(group="quota", driver="nova.quota.DbQuotaDriver")
self.assertIsInstance(quota_obj._driver, quota.DbQuotaDriver)
def _get_quota_engine(self, driver, resources=None):
resources = resources or [
quota.AbsoluteResource('test_resource4'),