Skip to content

Commit 22bc841

Browse files
JohnGarbuttmelwitt
authored andcommitted
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
1 parent d80d253 commit 22bc841

File tree

5 files changed

+193
-121
lines changed

5 files changed

+193
-121
lines changed

nova/quota.py

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from nova.db.main import api as main_db_api
3030
from nova import exception
3131
from nova.limit import local as local_limit
32+
from nova.limit import placement as placement_limit
3233
from nova import objects
3334
from nova.scheduler.client import report
3435
from nova import utils
@@ -806,17 +807,17 @@ def get_class_quotas(self, context, resources, quota_class):
806807

807808
def get_defaults(self, context, resources):
808809
local_limits = local_limit.get_legacy_default_limits()
809-
# TODO(melwitt): This is temporary when we are in a state where cores,
810-
# ram, and instances quota limits are not known/enforced with unified
811-
# limits yet. This will occur in later patches and when it does, we
812-
# will change the default to 0 to signal to operators that they need to
813-
# register a limit for a resource before that resource will be
814-
# allocated.
815-
# Default to unlimited, as per no-op for everything that isn't
816-
# a local limit
810+
# Note we get 0 if there is no registered limit,
811+
# to mirror oslo_limit behaviour when there is no registered limit
812+
placement_limits = placement_limit.get_legacy_default_limits()
817813
quotas = {}
818814
for resource in resources.values():
819-
quotas[resource.name] = local_limits.get(resource.name, -1)
815+
if resource.name in placement_limits:
816+
quotas[resource.name] = placement_limits[resource.name]
817+
else:
818+
# return -1 for things like security_group_rules
819+
# that are neither a keystone limit or a local limit
820+
quotas[resource.name] = local_limits.get(resource.name, -1)
820821

821822
return quotas
822823

@@ -829,21 +830,39 @@ def get_project_quotas(self, context, resources, project_id,
829830
if remains:
830831
raise NotImplementedError("remains")
831832

832-
local_limits = self.get_class_quotas(context, resources, quota_class)
833-
local_in_use = {}
834-
if usages:
835-
local_in_use = local_limit.get_in_use(context, project_id)
833+
local_limits = local_limit.get_legacy_default_limits()
834+
# keystone limits always returns core, ram and instances
835+
# if nothing set in keystone, we get back 0, i.e. don't allow
836+
placement_limits = placement_limit.get_legacy_project_limits(
837+
project_id)
836838

837-
quotas = {}
838-
# As we only apply limits to resources we know about,
839-
# we return unlimited (-1) for all other resources
839+
project_quotas = {}
840840
for resource in resources.values():
841-
quota = {"limit": local_limits.get(resource.name, -1)}
842-
if usages:
843-
quota["in_use"] = local_in_use.get(resource.name, -1)
844-
quotas[resource.name] = quota
841+
if resource.name in placement_limits:
842+
limit = placement_limits[resource.name]
843+
else:
844+
# return -1 for things like security_group_rules
845+
# that are neither a keystone limit or a local limit
846+
limit = local_limits.get(resource.name, -1)
847+
project_quotas[resource.name] = {"limit": limit}
845848

846-
return quotas
849+
if usages:
850+
local_in_use = local_limit.get_in_use(context, project_id)
851+
p_in_use = placement_limit.get_legacy_counts(context, project_id)
852+
853+
for resource in resources.values():
854+
# default to 0 for resources that are deprecated,
855+
# i.e. not in keystone or local limits, such that we
856+
# are API compatible with what was returned with
857+
# the db driver, even though noop driver returned -1
858+
usage_count = 0
859+
if resource.name in local_in_use:
860+
usage_count = local_in_use[resource.name]
861+
if resource.name in p_in_use:
862+
usage_count = p_in_use[resource.name]
863+
project_quotas[resource.name]["in_use"] = usage_count
864+
865+
return project_quotas
847866

848867
def get_user_quotas(self, context, resources, project_id, user_id,
849868
quota_class=None, usages=True):

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

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import nova.context
3232
from nova import exception
3333
from nova.limit import local as local_limit
34+
from nova.limit import placement as placement_limit
3435
from nova import objects
3536
from nova.policies import limits as l_policies
3637
from nova import quota
@@ -564,8 +565,12 @@ def setUp(self):
564565
local_limit.SERVER_GROUP_MEMBERS: 10}
565566
self.useFixture(limit_fixture.LimitFixture(reglimits, {}))
566567

568+
@mock.patch.object(placement_limit, "get_legacy_counts")
569+
@mock.patch.object(placement_limit, "get_legacy_project_limits")
567570
@mock.patch.object(objects.InstanceGroupList, "get_counts")
568-
def test_index_v21(self, mock_count):
571+
def test_index_v21(self, mock_count, mock_proj, mock_kcount):
572+
mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3}
573+
mock_kcount.return_value = {"instances": 4, "cores": 5, "ram": 6}
569574
mock_count.return_value = {'project': {'server_groups': 9}}
570575
req = fakes.HTTPRequest.blank("/")
571576
response = self.controller.index(req)
@@ -581,24 +586,28 @@ def test_index_v21(self, mock_count):
581586
'maxServerGroupMembers': 10,
582587
'maxServerGroups': 12,
583588
'maxServerMeta': 128,
584-
'maxTotalCores': -1,
589+
'maxTotalCores': 2,
585590
'maxTotalFloatingIps': -1,
586-
'maxTotalInstances': -1,
591+
'maxTotalInstances': 1,
587592
'maxTotalKeypairs': 100,
588-
'maxTotalRAMSize': -1,
589-
'totalCoresUsed': -1,
590-
'totalFloatingIpsUsed': -1,
591-
'totalInstancesUsed': -1,
592-
'totalRAMUsed': -1,
593-
'totalSecurityGroupsUsed': -1,
593+
'maxTotalRAMSize': 3,
594+
'totalCoresUsed': 5,
595+
'totalFloatingIpsUsed': 0,
596+
'totalInstancesUsed': 4,
597+
'totalRAMUsed': 6,
598+
'totalSecurityGroupsUsed': 0,
594599
'totalServerGroupsUsed': 9,
595600
},
596601
},
597602
}
598603
self.assertEqual(expected_response, response)
599604

605+
@mock.patch.object(placement_limit, "get_legacy_counts")
606+
@mock.patch.object(placement_limit, "get_legacy_project_limits")
600607
@mock.patch.object(objects.InstanceGroupList, "get_counts")
601-
def test_index_v275(self, mock_count):
608+
def test_index_v275(self, mock_count, mock_proj, mock_kcount):
609+
mock_proj.return_value = {"instances": 1, "cores": 2, "ram": 3}
610+
mock_kcount.return_value = {"instances": 4, "cores": 5, "ram": 6}
602611
mock_count.return_value = {'project': {'server_groups': 9}}
603612
req = fakes.HTTPRequest.blank("/?tenant_id=faketenant",
604613
version='2.75')
@@ -610,13 +619,13 @@ def test_index_v275(self, mock_count):
610619
'maxServerGroupMembers': 10,
611620
'maxServerGroups': 12,
612621
'maxServerMeta': 128,
613-
'maxTotalCores': -1,
614-
'maxTotalInstances': -1,
622+
'maxTotalCores': 2,
623+
'maxTotalInstances': 1,
615624
'maxTotalKeypairs': 100,
616-
'maxTotalRAMSize': -1,
617-
'totalCoresUsed': -1,
618-
'totalInstancesUsed': -1,
619-
'totalRAMUsed': -1,
625+
'maxTotalRAMSize': 3,
626+
'totalCoresUsed': 5,
627+
'totalInstancesUsed': 4,
628+
'totalRAMUsed': 6,
620629
'totalServerGroupsUsed': 9,
621630
},
622631
},

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

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
as quota_classes_v21
2222
from nova import exception
2323
from nova.limit import local as local_limit
24+
from nova.limit import placement as placement_limit
2425
from nova import objects
2526
from nova import test
2627
from nova.tests.unit.api.openstack import fakes
@@ -279,20 +280,22 @@ def setUp(self):
279280
local_limit.SERVER_GROUP_MEMBERS: 10}
280281
self.useFixture(limit_fixture.LimitFixture(reglimits, {}))
281282

282-
def test_show_v21(self):
283+
@mock.patch.object(placement_limit, "get_legacy_default_limits")
284+
def test_show_v21(self, mock_default):
285+
mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3}
283286
req = fakes.HTTPRequest.blank("")
284287
response = self.controller.show(req, "test_class")
285288
expected_response = {
286289
'quota_class_set': {
287290
'id': 'test_class',
288-
'cores': -1,
291+
'cores': 2,
289292
'fixed_ips': -1,
290293
'floating_ips': -1,
291-
'ram': -1,
294+
'ram': 3,
292295
'injected_file_content_bytes': 10240,
293296
'injected_file_path_bytes': 255,
294297
'injected_files': 5,
295-
'instances': -1,
298+
'instances': 1,
296299
'key_pairs': 100,
297300
'metadata_items': 128,
298301
'security_group_rules': -1,
@@ -301,15 +304,17 @@ def test_show_v21(self):
301304
}
302305
self.assertEqual(expected_response, response)
303306

304-
def test_show_v257(self):
307+
@mock.patch.object(placement_limit, "get_legacy_default_limits")
308+
def test_show_v257(self, mock_default):
309+
mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3}
305310
req = fakes.HTTPRequest.blank("", version='2.57')
306311
response = self.controller.show(req, "default")
307312
expected_response = {
308313
'quota_class_set': {
309314
'id': 'default',
310-
'cores': -1,
311-
'instances': -1,
312-
'ram': -1,
315+
'cores': 2,
316+
'instances': 1,
317+
'ram': 3,
313318
'key_pairs': 100,
314319
'metadata_items': 128,
315320
'server_group_members': 10,
@@ -325,23 +330,25 @@ def test_update_still_rejects_badrequests(self):
325330
self.assertRaises(exception.ValidationError, self.controller.update,
326331
req, 'test_class', body=body)
327332

333+
@mock.patch.object(placement_limit, "get_legacy_default_limits")
328334
@mock.patch.object(objects.Quotas, "update_class")
329-
def test_update_v21(self, mock_update):
335+
def test_update_v21(self, mock_update, mock_default):
336+
mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3}
330337
req = fakes.HTTPRequest.blank("")
331338
body = {'quota_class_set': {'ram': 51200}}
332339
response = self.controller.update(req, 'default', body=body)
333340
expected_response = {
334341
'quota_class_set': {
335-
'cores': -1,
342+
'cores': 2,
336343
'fixed_ips': -1,
337344
'floating_ips': -1,
338345
'injected_file_content_bytes': 10240,
339346
'injected_file_path_bytes': 255,
340347
'injected_files': 5,
341-
'instances': -1,
348+
'instances': 1,
342349
'key_pairs': 100,
343350
'metadata_items': 128,
344-
'ram': -1,
351+
'ram': 3,
345352
'security_group_rules': -1,
346353
'security_groups': -1
347354
}
@@ -350,16 +357,18 @@ def test_update_v21(self, mock_update):
350357
# TODO(johngarbutt) we should be proxying to keystone
351358
self.assertEqual(0, mock_update.call_count)
352359

360+
@mock.patch.object(placement_limit, "get_legacy_default_limits")
353361
@mock.patch.object(objects.Quotas, "update_class")
354-
def test_update_v257(self, mock_update):
362+
def test_update_v257(self, mock_update, mock_default):
363+
mock_default.return_value = {"instances": 1, "cores": 2, "ram": 3}
355364
req = fakes.HTTPRequest.blank("", version='2.57')
356365
body = {'quota_class_set': {'ram': 51200}}
357366
response = self.controller.update(req, 'default', body=body)
358367
expected_response = {
359368
'quota_class_set': {
360-
'cores': -1,
361-
'instances': -1,
362-
'ram': -1,
369+
'cores': 2,
370+
'instances': 1,
371+
'ram': 3,
363372
'key_pairs': 100,
364373
'metadata_items': 128,
365374
'server_group_members': 10,

0 commit comments

Comments
 (0)