Skip to content

Commit ce4f796

Browse files
JohnGarbuttmelwitt
authored andcommitted
Update limit APIs
Ensure the limit related APIs reflect the new reality of enforcing the API and DB limits based on keystone. Do this by implementing the get_project_quotas and get_user_quotas methods. This still leaves get_settable_quotas and get_defaults that are needed to fix the quota sets APIs. Note: this will need to be updated again once we add limits for cores, ram, instances, etc. blueprint unified-limits-nova Change-Id: I3c4c29740e6275449887c4136d2467eade04fb06
1 parent 94f9e44 commit ce4f796

File tree

4 files changed

+343
-0
lines changed

4 files changed

+343
-0
lines changed

nova/quota.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,36 @@ def get_class_quotas(self, context, resources, quota_class):
817817

818818
return quotas
819819

820+
def get_project_quotas(self, context, resources, project_id,
821+
quota_class=None,
822+
usages=True, remains=False):
823+
if quota_class is not None:
824+
raise NotImplementedError("quota_class")
825+
826+
if remains:
827+
raise NotImplementedError("remains")
828+
829+
local_limits = self.get_class_quotas(context, resources, quota_class)
830+
local_in_use = {}
831+
if usages:
832+
local_in_use = local_limit.get_in_use(context, project_id)
833+
834+
quotas = {}
835+
# As we only apply limits to resources we know about,
836+
# we return unlimited (-1) for all other resources
837+
for resource in resources.values():
838+
quota = {"limit": local_limits.get(resource.name, -1)}
839+
if usages:
840+
quota["in_use"] = local_in_use.get(resource.name, -1)
841+
quotas[resource.name] = quota
842+
843+
return quotas
844+
845+
def get_user_quotas(self, context, resources, project_id, user_id,
846+
quota_class=None, usages=True):
847+
return self.get_project_quotas(context, resources, project_id,
848+
quota_class, usages)
849+
820850

821851
class BaseResource(object):
822852
"""Describe a single resource for quota checking."""

nova/tests/unit/api/openstack/compute/test_limits.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from io import StringIO
2222

2323
import mock
24+
from oslo_limit import fixture as limit_fixture
2425
from oslo_serialization import jsonutils
2526
from oslo_utils import encodeutils
2627

@@ -29,6 +30,8 @@
2930
from nova.api.openstack import wsgi
3031
import nova.context
3132
from nova import exception
33+
from nova.limit import local as local_limit
34+
from nova import objects
3235
from nova.policies import limits as l_policies
3336
from nova import quota
3437
from nova import test
@@ -549,3 +552,73 @@ def test_index_v275(self):
549552

550553
class UnifiedLimitsControllerTest(NoopLimitsControllerTest):
551554
quota_driver = "nova.quota.UnifiedLimitsDriver"
555+
556+
def setUp(self):
557+
super(UnifiedLimitsControllerTest, self).setUp()
558+
reglimits = {local_limit.SERVER_METADATA_ITEMS: 128,
559+
local_limit.INJECTED_FILES: 5,
560+
local_limit.INJECTED_FILES_CONTENT: 10 * 1024,
561+
local_limit.INJECTED_FILES_PATH: 255,
562+
local_limit.KEY_PAIRS: 100,
563+
local_limit.SERVER_GROUPS: 12,
564+
local_limit.SERVER_GROUP_MEMBERS: 10}
565+
self.useFixture(limit_fixture.LimitFixture(reglimits, {}))
566+
567+
@mock.patch.object(objects.InstanceGroupList, "get_counts")
568+
def test_index_v21(self, mock_count):
569+
mock_count.return_value = {'project': {'server_groups': 9}}
570+
req = fakes.HTTPRequest.blank("/")
571+
response = self.controller.index(req)
572+
expected_response = {
573+
"limits": {
574+
"rate": [],
575+
"absolute": {
576+
'maxImageMeta': 128,
577+
'maxPersonality': 5,
578+
'maxPersonalitySize': 10240,
579+
'maxSecurityGroupRules': -1,
580+
'maxSecurityGroups': -1,
581+
'maxServerGroupMembers': 10,
582+
'maxServerGroups': 12,
583+
'maxServerMeta': 128,
584+
'maxTotalCores': -1,
585+
'maxTotalFloatingIps': -1,
586+
'maxTotalInstances': -1,
587+
'maxTotalKeypairs': 100,
588+
'maxTotalRAMSize': -1,
589+
'totalCoresUsed': -1,
590+
'totalFloatingIpsUsed': -1,
591+
'totalInstancesUsed': -1,
592+
'totalRAMUsed': -1,
593+
'totalSecurityGroupsUsed': -1,
594+
'totalServerGroupsUsed': 9,
595+
},
596+
},
597+
}
598+
self.assertEqual(expected_response, response)
599+
600+
@mock.patch.object(objects.InstanceGroupList, "get_counts")
601+
def test_index_v275(self, mock_count):
602+
mock_count.return_value = {'project': {'server_groups': 9}}
603+
req = fakes.HTTPRequest.blank("/?tenant_id=faketenant",
604+
version='2.75')
605+
response = self.controller.index(req)
606+
expected_response = {
607+
"limits": {
608+
"rate": [],
609+
"absolute": {
610+
'maxServerGroupMembers': 10,
611+
'maxServerGroups': 12,
612+
'maxServerMeta': 128,
613+
'maxTotalCores': -1,
614+
'maxTotalInstances': -1,
615+
'maxTotalKeypairs': 100,
616+
'maxTotalRAMSize': -1,
617+
'totalCoresUsed': -1,
618+
'totalInstancesUsed': -1,
619+
'totalRAMUsed': -1,
620+
'totalServerGroupsUsed': 9,
621+
},
622+
},
623+
}
624+
self.assertEqual(expected_response, response)

nova/tests/unit/api/openstack/compute/test_quotas.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
# under the License.
1616

1717
import mock
18+
from oslo_limit import fixture as limit_fixture
1819
from oslo_utils.fixture import uuidsentinel as uuids
1920
import webob
2021

2122
from nova.api.openstack.compute import quota_sets as quotas_v21
2223
from nova.db import constants as db_const
2324
from nova import exception
25+
from nova.limit import local as local_limit
2426
from nova import objects
2527
from nova import quota
2628
from nova import test
@@ -868,3 +870,176 @@ def test_user_quotas_delete(self, mock_destroy_all_by_user):
868870
class UnifiedLimitsQuotaSetsTest(NoopQuotaSetsTest):
869871
quota_driver = "nova.quota.UnifiedLimitsDriver"
870872
expected_detail = {'in_use': -1, 'limit': -1, 'reserved': 0}
873+
874+
def setUp(self):
875+
super(UnifiedLimitsQuotaSetsTest, self).setUp()
876+
reglimits = {local_limit.SERVER_METADATA_ITEMS: 128,
877+
local_limit.INJECTED_FILES: 5,
878+
local_limit.INJECTED_FILES_CONTENT: 10 * 1024,
879+
local_limit.INJECTED_FILES_PATH: 255,
880+
local_limit.KEY_PAIRS: 100,
881+
local_limit.SERVER_GROUPS: 12,
882+
local_limit.SERVER_GROUP_MEMBERS: 10}
883+
self.useFixture(limit_fixture.LimitFixture(reglimits, {}))
884+
885+
def test_show_v21(self):
886+
req = fakes.HTTPRequest.blank("")
887+
response = self.controller.show(req, uuids.project_id)
888+
expected_response = {
889+
'quota_set': {
890+
'id': uuids.project_id,
891+
'cores': -1,
892+
'fixed_ips': -1,
893+
'floating_ips': -1,
894+
'injected_file_content_bytes': 10240,
895+
'injected_file_path_bytes': 255,
896+
'injected_files': 5,
897+
'instances': -1,
898+
'key_pairs': 100,
899+
'metadata_items': 128,
900+
'ram': -1,
901+
'security_group_rules': -1,
902+
'security_groups': -1,
903+
'server_group_members': 10,
904+
'server_groups': 12,
905+
}
906+
}
907+
self.assertEqual(expected_response, response)
908+
909+
def test_show_v257(self):
910+
req = fakes.HTTPRequest.blank("", version='2.57')
911+
response = self.controller.show(req, uuids.project_id)
912+
expected_response = {
913+
'quota_set': {
914+
'id': uuids.project_id,
915+
'cores': -1,
916+
'instances': -1,
917+
'key_pairs': 100,
918+
'metadata_items': 128,
919+
'ram': -1,
920+
'server_group_members': 10,
921+
'server_groups': 12}}
922+
self.assertEqual(expected_response, response)
923+
924+
@mock.patch.object(objects.InstanceGroupList, "get_counts")
925+
def test_detail_v21(self, mock_count):
926+
mock_count.return_value = {'project': {'server_groups': 9}}
927+
req = fakes.HTTPRequest.blank("")
928+
response = self.controller.detail(req, uuids.project_id)
929+
expected_response = {
930+
'quota_set': {
931+
'id': uuids.project_id,
932+
'cores': self.expected_detail,
933+
'fixed_ips': self.expected_detail,
934+
'floating_ips': self.expected_detail,
935+
'injected_file_content_bytes': {
936+
'in_use': 0, 'limit': 10240, 'reserved': 0},
937+
'injected_file_path_bytes': {
938+
'in_use': 0, 'limit': 255, 'reserved': 0},
939+
'injected_files': {
940+
'in_use': 0, 'limit': 5, 'reserved': 0},
941+
'instances': self.expected_detail,
942+
'key_pairs': {
943+
'in_use': 0, 'limit': 100, 'reserved': 0},
944+
'metadata_items': {
945+
'in_use': 0, 'limit': 128, 'reserved': 0},
946+
'ram': self.expected_detail,
947+
'security_group_rules': self.expected_detail,
948+
'security_groups': self.expected_detail,
949+
'server_group_members': {
950+
'in_use': 0, 'limit': 10, 'reserved': 0},
951+
'server_groups': {
952+
'in_use': 9, 'limit': 12, 'reserved': 0},
953+
}
954+
}
955+
self.assertEqual(expected_response, response)
956+
957+
@mock.patch.object(objects.InstanceGroupList, "get_counts")
958+
def test_detail_v21_user(self, mock_count):
959+
mock_count.return_value = {'project': {'server_groups': 9}}
960+
req = fakes.HTTPRequest.blank("?user_id=42")
961+
response = self.controller.detail(req, uuids.project_id)
962+
expected_response = {
963+
'quota_set': {
964+
'id': uuids.project_id,
965+
'cores': self.expected_detail,
966+
'fixed_ips': self.expected_detail,
967+
'floating_ips': self.expected_detail,
968+
'injected_file_content_bytes': {
969+
'in_use': 0, 'limit': 10240, 'reserved': 0},
970+
'injected_file_path_bytes': {
971+
'in_use': 0, 'limit': 255, 'reserved': 0},
972+
'injected_files': {
973+
'in_use': 0, 'limit': 5, 'reserved': 0},
974+
'instances': self.expected_detail,
975+
'key_pairs': {
976+
'in_use': 0, 'limit': 100, 'reserved': 0},
977+
'metadata_items': {
978+
'in_use': 0, 'limit': 128, 'reserved': 0},
979+
'ram': self.expected_detail,
980+
'security_group_rules': self.expected_detail,
981+
'security_groups': self.expected_detail,
982+
'server_group_members': {
983+
'in_use': 0, 'limit': 10, 'reserved': 0},
984+
'server_groups': {
985+
'in_use': 9, 'limit': 12, 'reserved': 0},
986+
}
987+
}
988+
self.assertEqual(expected_response, response)
989+
990+
@mock.patch.object(objects.Quotas, "create_limit")
991+
def test_update_v21(self, mock_create):
992+
req = fakes.HTTPRequest.blank("")
993+
# TODO(johngarbutt) still need to implement get_settable_quotas
994+
body = {'quota_set': {'server_groups': 2}}
995+
response = self.controller.update(req, uuids.project_id, body=body)
996+
expected_response = {
997+
'quota_set': {
998+
'cores': -1,
999+
'fixed_ips': -1,
1000+
'floating_ips': -1,
1001+
'injected_file_content_bytes': 10240,
1002+
'injected_file_path_bytes': 255,
1003+
'injected_files': 5,
1004+
'instances': -1,
1005+
'key_pairs': 100,
1006+
'metadata_items': 128,
1007+
'ram': -1,
1008+
'security_group_rules': -1,
1009+
'security_groups': -1,
1010+
'server_group_members': 10,
1011+
'server_groups': 12,
1012+
}
1013+
}
1014+
self.assertEqual(expected_response, response)
1015+
mock_create.assert_called_once_with(req.environ['nova.context'],
1016+
uuids.project_id, "server_groups",
1017+
2, user_id=None)
1018+
1019+
@mock.patch.object(objects.Quotas, "create_limit")
1020+
def test_update_v21_user(self, mock_create):
1021+
req = fakes.HTTPRequest.blank("?user_id=42")
1022+
body = {'quota_set': {'key_pairs': 52}}
1023+
response = self.controller.update(req, uuids.project_id, body=body)
1024+
expected_response = {
1025+
'quota_set': {
1026+
'cores': -1,
1027+
'fixed_ips': -1,
1028+
'floating_ips': -1,
1029+
'injected_file_content_bytes': 10240,
1030+
'injected_file_path_bytes': 255,
1031+
'injected_files': 5,
1032+
'instances': -1,
1033+
'key_pairs': 100,
1034+
'metadata_items': 128,
1035+
'ram': -1,
1036+
'security_group_rules': -1,
1037+
'security_groups': -1,
1038+
'server_group_members': 10,
1039+
'server_groups': 12,
1040+
}
1041+
}
1042+
self.assertEqual(expected_response, response)
1043+
mock_create.assert_called_once_with(req.environ['nova.context'],
1044+
uuids.project_id, "key_pairs", 52,
1045+
user_id="42")

nova/tests/unit/test_quota.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,38 @@ def setUp(self):
19661966
local_limit.SERVER_GROUPS: 12,
19671967
local_limit.SERVER_GROUP_MEMBERS: 10}
19681968
self.useFixture(limit_fixture.LimitFixture(reglimits, {}))
1969+
self.expected_without_usages = {
1970+
'cores': {'limit': -1},
1971+
'fixed_ips': {'limit': -1},
1972+
'floating_ips': {'limit': -1},
1973+
'injected_file_content_bytes': {'limit': 10240},
1974+
'injected_file_path_bytes': {'limit': 255},
1975+
'injected_files': {'limit': 5},
1976+
'instances': {'limit': -1},
1977+
'key_pairs': {'limit': 100},
1978+
'metadata_items': {'limit': 128},
1979+
'ram': {'limit': -1},
1980+
'security_group_rules': {'limit': -1},
1981+
'security_groups': {'limit': -1},
1982+
'server_group_members': {'limit': 10},
1983+
'server_groups': {'limit': 12}
1984+
}
1985+
self.expected_with_usages = {
1986+
'cores': {'in_use': -1, 'limit': -1},
1987+
'fixed_ips': {'in_use': -1, 'limit': -1},
1988+
'floating_ips': {'in_use': -1, 'limit': -1},
1989+
'injected_file_content_bytes': {'in_use': 0, 'limit': 10240},
1990+
'injected_file_path_bytes': {'in_use': 0, 'limit': 255},
1991+
'injected_files': {'in_use': 0, 'limit': 5},
1992+
'instances': {'in_use': -1, 'limit': -1},
1993+
'key_pairs': {'in_use': 0, 'limit': 100},
1994+
'metadata_items': {'in_use': 0, 'limit': 128},
1995+
'ram': {'in_use': -1, 'limit': -1},
1996+
'security_group_rules': {'in_use': -1, 'limit': -1},
1997+
'security_groups': {'in_use': -1, 'limit': -1},
1998+
'server_group_members': {'in_use': 0, 'limit': 10},
1999+
'server_groups': {'in_use': 9, 'limit': 12}
2000+
}
19692001

19702002
def test_get_class_quotas(self):
19712003
result = self.driver.get_class_quotas(
@@ -1988,6 +2020,39 @@ def test_get_class_quotas(self):
19882020
}
19892021
self.assertEqual(expected_limits, result)
19902022

2023+
@mock.patch.object(objects.InstanceGroupList, "get_counts")
2024+
def test_get_project_quotas(self, mock_count):
2025+
mock_count.return_value = {'project': {'server_groups': 9}}
2026+
result = self.driver.get_project_quotas(
2027+
None, quota.QUOTAS._resources, 'test_project')
2028+
self.assertEqual(self.expected_with_usages, result)
2029+
mock_count.assert_called_once_with(None, "test_project")
2030+
2031+
@mock.patch.object(objects.InstanceGroupList, "get_counts")
2032+
def test_get_project_quotas_no_usages(self, mock_count):
2033+
result = self.driver.get_project_quotas(
2034+
None, quota.QUOTAS._resources, 'test_project', usages=False)
2035+
self.assertEqual(self.expected_without_usages, result)
2036+
# ensure usages not fetched when not required
2037+
self.assertEqual(0, mock_count.call_count)
2038+
2039+
@mock.patch.object(objects.InstanceGroupList, "get_counts")
2040+
def test_get_user_quotas(self, mock_count):
2041+
mock_count.return_value = {'project': {'server_groups': 9}}
2042+
result = self.driver.get_user_quotas(
2043+
None, quota.QUOTAS._resources, 'test_project', 'fake_user')
2044+
self.assertEqual(self.expected_with_usages, result)
2045+
mock_count.assert_called_once_with(None, "test_project")
2046+
2047+
@mock.patch.object(objects.InstanceGroupList, "get_counts")
2048+
def test_get_user_quotas_no_usages(self, mock_count):
2049+
result = self.driver.get_user_quotas(
2050+
None, quota.QUOTAS._resources, 'test_project', 'fake_user',
2051+
usages=False)
2052+
self.assertEqual(self.expected_without_usages, result)
2053+
# ensure usages not fetched when not required
2054+
self.assertEqual(0, mock_count.call_count)
2055+
19912056

19922057
@ddt.ddt
19932058
class QuotaCountTestCase(test.NoDBTestCase):

0 commit comments

Comments
 (0)