From 22bc8419716b2ef7a41f8710e1c13fdc52d48bc1 Mon Sep 17 00:00:00 2001 From: John Garbutt Date: Tue, 17 Mar 2020 17:42:10 +0000 Subject: [PATCH] Update quota apis with keystone limits and usage This makes use of the keystone APIs to get limits from Keystone when showing the user the limits on their project. Note we also change the default in_use amount from -1, which is what the no op driver originally used, to 0, which matches what the db driver typically returns for deprecated quota values, like floating ip limits. This seems a more sane value to respond with, given we don't count the usage for those values. blueprint unified-limits-nova Change-Id: I933dc135a364b14ddadc8eee67b42d8e1278a9ae --- nova/quota.py | 59 +++++++----- .../unit/api/openstack/compute/test_limits.py | 41 +++++---- .../openstack/compute/test_quota_classes.py | 41 +++++---- .../unit/api/openstack/compute/test_quotas.py | 90 ++++++++++++------- nova/tests/unit/test_quota.py | 83 +++++++++-------- 5 files changed, 193 insertions(+), 121 deletions(-) diff --git a/nova/quota.py b/nova/quota.py index b8fa078259..9390e3e2f2 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -29,6 +29,7 @@ from nova.db.api import models as api_models from nova.db.main import api as main_db_api from nova import exception from nova.limit import local as local_limit +from nova.limit import placement as placement_limit from nova import objects from nova.scheduler.client import report from nova import utils @@ -806,17 +807,17 @@ class UnifiedLimitsDriver(NoopQuotaDriver): def get_defaults(self, context, resources): local_limits = local_limit.get_legacy_default_limits() - # TODO(melwitt): This is temporary when we are in a state where cores, - # ram, and instances quota limits are not known/enforced with unified - # limits yet. This will occur in later patches and when it does, we - # will change the default to 0 to signal to operators that they need to - # register a limit for a resource before that resource will be - # allocated. - # Default to unlimited, as per no-op for everything that isn't - # a local limit + # Note we get 0 if there is no registered limit, + # to mirror oslo_limit behaviour when there is no registered limit + placement_limits = placement_limit.get_legacy_default_limits() quotas = {} for resource in resources.values(): - quotas[resource.name] = local_limits.get(resource.name, -1) + if resource.name in placement_limits: + quotas[resource.name] = placement_limits[resource.name] + else: + # return -1 for things like security_group_rules + # that are neither a keystone limit or a local limit + quotas[resource.name] = local_limits.get(resource.name, -1) return quotas @@ -829,21 +830,39 @@ class UnifiedLimitsDriver(NoopQuotaDriver): if remains: raise NotImplementedError("remains") - local_limits = self.get_class_quotas(context, resources, quota_class) - local_in_use = {} + local_limits = local_limit.get_legacy_default_limits() + # keystone limits always returns core, ram and instances + # if nothing set in keystone, we get back 0, i.e. don't allow + placement_limits = placement_limit.get_legacy_project_limits( + project_id) + + project_quotas = {} + for resource in resources.values(): + if resource.name in placement_limits: + limit = placement_limits[resource.name] + else: + # return -1 for things like security_group_rules + # that are neither a keystone limit or a local limit + limit = local_limits.get(resource.name, -1) + project_quotas[resource.name] = {"limit": limit} + if usages: local_in_use = local_limit.get_in_use(context, project_id) + p_in_use = placement_limit.get_legacy_counts(context, project_id) - quotas = {} - # As we only apply limits to resources we know about, - # we return unlimited (-1) for all other resources - for resource in resources.values(): - quota = {"limit": local_limits.get(resource.name, -1)} - if usages: - quota["in_use"] = local_in_use.get(resource.name, -1) - quotas[resource.name] = quota + for resource in resources.values(): + # default to 0 for resources that are deprecated, + # i.e. not in keystone or local limits, such that we + # are API compatible with what was returned with + # the db driver, even though noop driver returned -1 + usage_count = 0 + if resource.name in local_in_use: + usage_count = local_in_use[resource.name] + if resource.name in p_in_use: + usage_count = p_in_use[resource.name] + project_quotas[resource.name]["in_use"] = usage_count - return quotas + return project_quotas def get_user_quotas(self, context, resources, project_id, user_id, quota_class=None, usages=True): diff --git a/nova/tests/unit/api/openstack/compute/test_limits.py b/nova/tests/unit/api/openstack/compute/test_limits.py index 9cbdcd792b..b8b01a09dd 100644 --- a/nova/tests/unit/api/openstack/compute/test_limits.py +++ b/nova/tests/unit/api/openstack/compute/test_limits.py @@ -31,6 +31,7 @@ from nova.api.openstack import wsgi import nova.context from nova import exception from nova.limit import local as local_limit +from nova.limit import placement as placement_limit from nova import objects from nova.policies import limits as l_policies from nova import quota @@ -564,8 +565,12 @@ class UnifiedLimitsControllerTest(NoopLimitsControllerTest): local_limit.SERVER_GROUP_MEMBERS: 10} self.useFixture(limit_fixture.LimitFixture(reglimits, {})) + @mock.patch.object(placement_limit, "get_legacy_counts") + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.InstanceGroupList, "get_counts") - def test_index_v21(self, mock_count): + def test_index_v21(self, mock_count, mock_proj, mock_kcount): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} + mock_kcount.return_value = {"instances": 4, "cores": 5, "ram": 6} mock_count.return_value = {'project': {'server_groups': 9}} req = fakes.HTTPRequest.blank("/") response = self.controller.index(req) @@ -581,24 +586,28 @@ class UnifiedLimitsControllerTest(NoopLimitsControllerTest): 'maxServerGroupMembers': 10, 'maxServerGroups': 12, 'maxServerMeta': 128, - 'maxTotalCores': -1, + 'maxTotalCores': 2, 'maxTotalFloatingIps': -1, - 'maxTotalInstances': -1, + 'maxTotalInstances': 1, 'maxTotalKeypairs': 100, - 'maxTotalRAMSize': -1, - 'totalCoresUsed': -1, - 'totalFloatingIpsUsed': -1, - 'totalInstancesUsed': -1, - 'totalRAMUsed': -1, - 'totalSecurityGroupsUsed': -1, + 'maxTotalRAMSize': 3, + 'totalCoresUsed': 5, + 'totalFloatingIpsUsed': 0, + 'totalInstancesUsed': 4, + 'totalRAMUsed': 6, + 'totalSecurityGroupsUsed': 0, 'totalServerGroupsUsed': 9, }, }, } self.assertEqual(expected_response, response) + @mock.patch.object(placement_limit, "get_legacy_counts") + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.InstanceGroupList, "get_counts") - def test_index_v275(self, mock_count): + def test_index_v275(self, mock_count, mock_proj, mock_kcount): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} + mock_kcount.return_value = {"instances": 4, "cores": 5, "ram": 6} mock_count.return_value = {'project': {'server_groups': 9}} req = fakes.HTTPRequest.blank("/?tenant_id=faketenant", version='2.75') @@ -610,13 +619,13 @@ class UnifiedLimitsControllerTest(NoopLimitsControllerTest): 'maxServerGroupMembers': 10, 'maxServerGroups': 12, 'maxServerMeta': 128, - 'maxTotalCores': -1, - 'maxTotalInstances': -1, + 'maxTotalCores': 2, + 'maxTotalInstances': 1, 'maxTotalKeypairs': 100, - 'maxTotalRAMSize': -1, - 'totalCoresUsed': -1, - 'totalInstancesUsed': -1, - 'totalRAMUsed': -1, + 'maxTotalRAMSize': 3, + 'totalCoresUsed': 5, + 'totalInstancesUsed': 4, + 'totalRAMUsed': 6, 'totalServerGroupsUsed': 9, }, }, diff --git a/nova/tests/unit/api/openstack/compute/test_quota_classes.py b/nova/tests/unit/api/openstack/compute/test_quota_classes.py index 9a334480d6..d501412803 100644 --- a/nova/tests/unit/api/openstack/compute/test_quota_classes.py +++ b/nova/tests/unit/api/openstack/compute/test_quota_classes.py @@ -21,6 +21,7 @@ from nova.api.openstack.compute import quota_classes \ as quota_classes_v21 from nova import exception from nova.limit import local as local_limit +from nova.limit import placement as placement_limit from nova import objects from nova import test from nova.tests.unit.api.openstack import fakes @@ -279,20 +280,22 @@ class UnifiedLimitsQuotaClassesTest(NoopQuotaClassesTest): local_limit.SERVER_GROUP_MEMBERS: 10} self.useFixture(limit_fixture.LimitFixture(reglimits, {})) - def test_show_v21(self): + @mock.patch.object(placement_limit, "get_legacy_default_limits") + def test_show_v21(self, mock_default): + mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3} req = fakes.HTTPRequest.blank("") response = self.controller.show(req, "test_class") expected_response = { 'quota_class_set': { 'id': 'test_class', - 'cores': -1, + 'cores': 2, 'fixed_ips': -1, 'floating_ips': -1, - 'ram': -1, + 'ram': 3, 'injected_file_content_bytes': 10240, 'injected_file_path_bytes': 255, 'injected_files': 5, - 'instances': -1, + 'instances': 1, 'key_pairs': 100, 'metadata_items': 128, 'security_group_rules': -1, @@ -301,15 +304,17 @@ class UnifiedLimitsQuotaClassesTest(NoopQuotaClassesTest): } self.assertEqual(expected_response, response) - def test_show_v257(self): + @mock.patch.object(placement_limit, "get_legacy_default_limits") + def test_show_v257(self, mock_default): + mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3} req = fakes.HTTPRequest.blank("", version='2.57') response = self.controller.show(req, "default") expected_response = { 'quota_class_set': { 'id': 'default', - 'cores': -1, - 'instances': -1, - 'ram': -1, + 'cores': 2, + 'instances': 1, + 'ram': 3, 'key_pairs': 100, 'metadata_items': 128, 'server_group_members': 10, @@ -325,23 +330,25 @@ class UnifiedLimitsQuotaClassesTest(NoopQuotaClassesTest): self.assertRaises(exception.ValidationError, self.controller.update, req, 'test_class', body=body) + @mock.patch.object(placement_limit, "get_legacy_default_limits") @mock.patch.object(objects.Quotas, "update_class") - def test_update_v21(self, mock_update): + def test_update_v21(self, mock_update, mock_default): + mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3} 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, + 'cores': 2, 'fixed_ips': -1, 'floating_ips': -1, 'injected_file_content_bytes': 10240, 'injected_file_path_bytes': 255, 'injected_files': 5, - 'instances': -1, + 'instances': 1, 'key_pairs': 100, 'metadata_items': 128, - 'ram': -1, + 'ram': 3, 'security_group_rules': -1, 'security_groups': -1 } @@ -350,16 +357,18 @@ class UnifiedLimitsQuotaClassesTest(NoopQuotaClassesTest): # TODO(johngarbutt) we should be proxying to keystone self.assertEqual(0, mock_update.call_count) + @mock.patch.object(placement_limit, "get_legacy_default_limits") @mock.patch.object(objects.Quotas, "update_class") - def test_update_v257(self, mock_update): + def test_update_v257(self, mock_update, mock_default): + mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3} 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, - 'ram': -1, + 'cores': 2, + 'instances': 1, + 'ram': 3, 'key_pairs': 100, 'metadata_items': 128, 'server_group_members': 10, diff --git a/nova/tests/unit/api/openstack/compute/test_quotas.py b/nova/tests/unit/api/openstack/compute/test_quotas.py index 56d13ea1aa..6cb8d9c7ad 100644 --- a/nova/tests/unit/api/openstack/compute/test_quotas.py +++ b/nova/tests/unit/api/openstack/compute/test_quotas.py @@ -23,6 +23,7 @@ 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.limit import local as local_limit +from nova.limit import placement as placement_limit from nova import objects from nova import quota from nova import test @@ -869,7 +870,8 @@ class NoopQuotaSetsTest(test.NoDBTestCase): class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): quota_driver = "nova.quota.UnifiedLimitsDriver" - expected_detail = {'in_use': -1, 'limit': -1, 'reserved': 0} + # this matches what the db driver returns + expected_detail = {'in_use': 0, 'limit': -1, 'reserved': 0} def setUp(self): super(UnifiedLimitsQuotaSetsTest, self).setUp() @@ -882,22 +884,24 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): local_limit.SERVER_GROUP_MEMBERS: 10} self.useFixture(limit_fixture.LimitFixture(reglimits, {})) - def test_show_v21(self): + @mock.patch.object(placement_limit, "get_legacy_project_limits") + def test_show_v21(self, mock_proj): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} req = fakes.HTTPRequest.blank("") response = self.controller.show(req, uuids.project_id) expected_response = { 'quota_set': { 'id': uuids.project_id, - 'cores': -1, + 'cores': 2, 'fixed_ips': -1, 'floating_ips': -1, 'injected_file_content_bytes': 10240, 'injected_file_path_bytes': 255, 'injected_files': 5, - 'instances': -1, + 'instances': 1, 'key_pairs': 100, 'metadata_items': 128, - 'ram': -1, + 'ram': 3, 'security_group_rules': -1, 'security_groups': -1, 'server_group_members': 10, @@ -906,30 +910,37 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): } self.assertEqual(expected_response, response) - def test_show_v257(self): + @mock.patch.object(placement_limit, "get_legacy_project_limits") + def test_show_v257(self, mock_proj): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} 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, + 'cores': 2, + 'instances': 1, 'key_pairs': 100, 'metadata_items': 128, - 'ram': -1, + 'ram': 3, 'server_group_members': 10, 'server_groups': 12}} self.assertEqual(expected_response, response) + @mock.patch.object(placement_limit, "get_legacy_counts") + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.InstanceGroupList, "get_counts") - def test_detail_v21(self, mock_count): + def test_detail_v21(self, mock_count, mock_proj, mock_kcount): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} + mock_kcount.return_value = {"instances": 4, "cores": 5, "ram": 6} mock_count.return_value = {'project': {'server_groups': 9}} req = fakes.HTTPRequest.blank("") response = self.controller.detail(req, uuids.project_id) expected_response = { 'quota_set': { 'id': uuids.project_id, - 'cores': self.expected_detail, + 'cores': { + 'in_use': 5, 'limit': 2, 'reserved': 0}, 'fixed_ips': self.expected_detail, 'floating_ips': self.expected_detail, 'injected_file_content_bytes': { @@ -938,12 +949,14 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): 'in_use': 0, 'limit': 255, 'reserved': 0}, 'injected_files': { 'in_use': 0, 'limit': 5, 'reserved': 0}, - 'instances': self.expected_detail, + 'instances': { + 'in_use': 4, 'limit': 1, 'reserved': 0}, 'key_pairs': { 'in_use': 0, 'limit': 100, 'reserved': 0}, 'metadata_items': { 'in_use': 0, 'limit': 128, 'reserved': 0}, - 'ram': self.expected_detail, + 'ram': { + 'in_use': 6, 'limit': 3, 'reserved': 0}, 'security_group_rules': self.expected_detail, 'security_groups': self.expected_detail, 'server_group_members': { @@ -954,15 +967,20 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): } self.assertEqual(expected_response, response) + @mock.patch.object(placement_limit, "get_legacy_counts") + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.InstanceGroupList, "get_counts") - def test_detail_v21_user(self, mock_count): + def test_detail_v21_user(self, mock_count, mock_proj, mock_kcount): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} + mock_kcount.return_value = {"instances": 4, "cores": 5, "ram": 6} mock_count.return_value = {'project': {'server_groups': 9}} req = fakes.HTTPRequest.blank("?user_id=42") response = self.controller.detail(req, uuids.project_id) expected_response = { 'quota_set': { 'id': uuids.project_id, - 'cores': self.expected_detail, + 'cores': { + 'in_use': 5, 'limit': 2, 'reserved': 0}, 'fixed_ips': self.expected_detail, 'floating_ips': self.expected_detail, 'injected_file_content_bytes': { @@ -971,12 +989,14 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): 'in_use': 0, 'limit': 255, 'reserved': 0}, 'injected_files': { 'in_use': 0, 'limit': 5, 'reserved': 0}, - 'instances': self.expected_detail, + 'instances': { + 'in_use': 4, 'limit': 1, 'reserved': 0}, 'key_pairs': { 'in_use': 0, 'limit': 100, 'reserved': 0}, 'metadata_items': { 'in_use': 0, 'limit': 128, 'reserved': 0}, - 'ram': self.expected_detail, + 'ram': { + 'in_use': 6, 'limit': 3, 'reserved': 0}, 'security_group_rules': self.expected_detail, 'security_groups': self.expected_detail, 'server_group_members': { @@ -987,24 +1007,26 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): } self.assertEqual(expected_response, response) + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.Quotas, "create_limit") - def test_update_v21(self, mock_create): + def test_update_v21(self, mock_create, mock_proj): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} req = fakes.HTTPRequest.blank("") # TODO(johngarbutt) still need to implement get_settable_quotas body = {'quota_set': {'server_groups': 2}} response = self.controller.update(req, uuids.project_id, body=body) expected_response = { 'quota_set': { - 'cores': -1, + 'cores': 2, 'fixed_ips': -1, 'floating_ips': -1, 'injected_file_content_bytes': 10240, 'injected_file_path_bytes': 255, 'injected_files': 5, - 'instances': -1, + 'instances': 1, 'key_pairs': 100, 'metadata_items': 128, - 'ram': -1, + 'ram': 3, 'security_group_rules': -1, 'security_groups': -1, 'server_group_members': 10, @@ -1014,23 +1036,25 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): self.assertEqual(expected_response, response) self.assertEqual(0, mock_create.call_count) + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.Quotas, "create_limit") - def test_update_v21_user(self, mock_create): + def test_update_v21_user(self, mock_create, mock_proj): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} 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, + 'cores': 2, 'fixed_ips': -1, 'floating_ips': -1, 'injected_file_content_bytes': 10240, 'injected_file_path_bytes': 255, 'injected_files': 5, - 'instances': -1, + 'instances': 1, 'key_pairs': 100, 'metadata_items': 128, - 'ram': -1, + 'ram': 3, 'security_group_rules': -1, 'security_groups': -1, 'server_group_members': 10, @@ -1040,22 +1064,24 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): self.assertEqual(expected_response, response) self.assertEqual(0, mock_create.call_count) - def test_defaults_v21(self): + @mock.patch.object(placement_limit, "get_legacy_default_limits") + def test_defaults_v21(self, mock_default): + mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3} req = fakes.HTTPRequest.blank("") response = self.controller.defaults(req, uuids.project_id) expected_response = { 'quota_set': { 'id': uuids.project_id, - 'cores': -1, + 'cores': 2, 'fixed_ips': -1, 'floating_ips': -1, 'injected_file_content_bytes': 10240, 'injected_file_path_bytes': 255, 'injected_files': 5, - 'instances': -1, + 'instances': 1, 'key_pairs': 100, 'metadata_items': 128, - 'ram': -1, + 'ram': 3, 'security_group_rules': -1, 'security_groups': -1, 'server_group_members': 10, @@ -1079,16 +1105,16 @@ class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest): expected_response = { 'quota_set': { 'id': uuids.project_id, - 'cores': -1, + 'cores': 0, 'fixed_ips': -1, 'floating_ips': -1, 'injected_file_content_bytes': 4, 'injected_file_path_bytes': 5, 'injected_files': 6, - 'instances': -1, + 'instances': 0, 'key_pairs': 1, 'metadata_items': 7, - 'ram': -1, + 'ram': 0, 'security_group_rules': -1, 'security_groups': -1, 'server_group_members': 2, diff --git a/nova/tests/unit/test_quota.py b/nova/tests/unit/test_quota.py index 58b6657215..edbb814ba7 100644 --- a/nova/tests/unit/test_quota.py +++ b/nova/tests/unit/test_quota.py @@ -26,6 +26,7 @@ from nova import context from nova.db.main import models from nova import exception from nova.limit import local as local_limit +from nova.limit import placement as placement_limit from nova import objects from nova import quota from nova import test @@ -1990,101 +1991,109 @@ class UnifiedLimitsDriverTestCase(NoopQuotaDriverTestCase): self.useFixture(limit_fixture.LimitFixture(reglimits, {})) self.expected_without_dict = { - 'cores': -1, + 'cores': 2, 'fixed_ips': -1, 'floating_ips': -1, 'injected_file_content_bytes': 10240, 'injected_file_path_bytes': 255, 'injected_files': 5, - 'instances': -1, + 'instances': 1, 'key_pairs': 100, 'metadata_items': 128, - 'ram': -1, + 'ram': 0, 'security_group_rules': -1, 'security_groups': -1, 'server_group_members': 10, 'server_groups': 12, } self.expected_without_usages = { - 'cores': {'limit': -1}, + 'cores': {'limit': 2}, 'fixed_ips': {'limit': -1}, 'floating_ips': {'limit': -1}, 'injected_file_content_bytes': {'limit': 10240}, 'injected_file_path_bytes': {'limit': 255}, 'injected_files': {'limit': 5}, - 'instances': {'limit': -1}, + 'instances': {'limit': 1}, 'key_pairs': {'limit': 100}, 'metadata_items': {'limit': 128}, - 'ram': {'limit': -1}, + 'ram': {'limit': 3}, 'security_group_rules': {'limit': -1}, 'security_groups': {'limit': -1}, 'server_group_members': {'limit': 10}, 'server_groups': {'limit': 12} } self.expected_with_usages = { - 'cores': {'in_use': -1, 'limit': -1}, - 'fixed_ips': {'in_use': -1, 'limit': -1}, - 'floating_ips': {'in_use': -1, 'limit': -1}, + 'cores': {'in_use': 5, 'limit': 2}, + 'fixed_ips': {'in_use': 0, 'limit': -1}, + 'floating_ips': {'in_use': 0, 'limit': -1}, 'injected_file_content_bytes': {'in_use': 0, 'limit': 10240}, 'injected_file_path_bytes': {'in_use': 0, 'limit': 255}, 'injected_files': {'in_use': 0, 'limit': 5}, - 'instances': {'in_use': -1, 'limit': -1}, + 'instances': {'in_use': 4, 'limit': 1}, 'key_pairs': {'in_use': 0, 'limit': 100}, 'metadata_items': {'in_use': 0, 'limit': 128}, - 'ram': {'in_use': -1, 'limit': -1}, - 'security_group_rules': {'in_use': -1, 'limit': -1}, - 'security_groups': {'in_use': -1, 'limit': -1}, + 'ram': {'in_use': 6, 'limit': 3}, + 'security_group_rules': {'in_use': 0, 'limit': -1}, + 'security_groups': {'in_use': 0, 'limit': -1}, 'server_group_members': {'in_use': 0, 'limit': 10}, 'server_groups': {'in_use': 9, 'limit': 12} } - def test_get_class_quotas(self): - result = self.driver.get_class_quotas( - None, quota.QUOTAS._resources, 'default') - expected_limits = { - 'cores': -1, - 'fixed_ips': -1, - 'floating_ips': -1, - 'injected_file_content_bytes': 10240, - 'injected_file_path_bytes': 255, - 'injected_files': 5, - 'instances': -1, - 'key_pairs': 100, - 'metadata_items': 128, - 'ram': -1, - 'security_group_rules': -1, - 'security_groups': -1, - 'server_group_members': 10, - 'server_groups': 12, - } - self.assertEqual(expected_limits, result) + @mock.patch.object(placement_limit, "get_legacy_default_limits") + def test_get_defaults(self, mock_default): + # zero for ram simulates no registered limit for ram + mock_default.return_value = {"instances": 1, "cores": 2, "ram": 0} + result = self.driver.get_defaults(None, quota.QUOTAS._resources) + self.assertEqual(self.expected_without_dict, result) + mock_default.assert_called_once_with() + @mock.patch.object(placement_limit, "get_legacy_default_limits") + def test_get_class_quotas(self, mock_default): + mock_default.return_value = {"instances": 1, "cores": 2, "ram": 0} + result = self.driver.get_class_quotas( + None, quota.QUOTAS._resources, 'test_class') + self.assertEqual(self.expected_without_dict, result) + mock_default.assert_called_once_with() + + @mock.patch.object(placement_limit, "get_legacy_counts") + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.InstanceGroupList, "get_counts") - def test_get_project_quotas(self, mock_count): + def test_get_project_quotas(self, mock_count, mock_proj, mock_kcount): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} + mock_kcount.return_value = {"instances": 4, "cores": 5, "ram": 6} mock_count.return_value = {'project': {'server_groups': 9}} result = self.driver.get_project_quotas( None, quota.QUOTAS._resources, 'test_project') self.assertEqual(self.expected_with_usages, result) mock_count.assert_called_once_with(None, "test_project") + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.InstanceGroupList, "get_counts") - def test_get_project_quotas_no_usages(self, mock_count): + def test_get_project_quotas_no_usages(self, mock_count, mock_proj): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} result = self.driver.get_project_quotas( None, quota.QUOTAS._resources, 'test_project', usages=False) self.assertEqual(self.expected_without_usages, result) # ensure usages not fetched when not required self.assertEqual(0, mock_count.call_count) + mock_proj.assert_called_once_with("test_project") + @mock.patch.object(placement_limit, "get_legacy_counts") + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.InstanceGroupList, "get_counts") - def test_get_user_quotas(self, mock_count): + def test_get_user_quotas(self, mock_count, mock_proj, mock_kcount): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} + mock_kcount.return_value = {"instances": 4, "cores": 5, "ram": 6} mock_count.return_value = {'project': {'server_groups': 9}} result = self.driver.get_user_quotas( None, quota.QUOTAS._resources, 'test_project', 'fake_user') self.assertEqual(self.expected_with_usages, result) mock_count.assert_called_once_with(None, "test_project") + @mock.patch.object(placement_limit, "get_legacy_project_limits") @mock.patch.object(objects.InstanceGroupList, "get_counts") - def test_get_user_quotas_no_usages(self, mock_count): + def test_get_user_quotas_no_usages(self, mock_count, mock_proj): + mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3} result = self.driver.get_user_quotas( None, quota.QUOTAS._resources, 'test_project', 'fake_user', usages=False)