Update quota_class APIs for db and api limits
Implement a unified limits specific version of get_class_quotas as used by the quota_class API. This simply returns the limits defined in keystone that are now enforced when you enable unified limits. Note: this will need to be updated again once we add limits to things that use things like resource_class, etc. blueprint unified-limits-nova Change-Id: If9901662d30d15da13303a3da051e1b9fded72c0
This commit is contained in:
parent
4207493829
commit
94f9e443f2
|
@ -20,6 +20,7 @@ from nova.api.openstack.compute.schemas import quota_classes
|
|||
from nova.api.openstack import wsgi
|
||||
from nova.api import validation
|
||||
from nova import exception
|
||||
from nova.limit import utils as limit_utils
|
||||
from nova import objects
|
||||
from nova.policies import quota_class_sets as qcs_policies
|
||||
from nova import quota
|
||||
|
@ -129,11 +130,20 @@ class QuotaClassSetsController(wsgi.Controller):
|
|||
|
||||
quota_class = id
|
||||
|
||||
for key, value in body['quota_class_set'].items():
|
||||
try:
|
||||
objects.Quotas.update_class(context, quota_class, key, value)
|
||||
except exception.QuotaClassNotFound:
|
||||
objects.Quotas.create_class(context, quota_class, key, value)
|
||||
quota_updates = body['quota_class_set'].items()
|
||||
# TODO(johngarbutt) eventually cores, ram and instances changes will
|
||||
# get sent to keystone when using unified limits, but only when the
|
||||
# quota_class == "default".
|
||||
if not limit_utils.use_unified_limits():
|
||||
# When not unified limits, keep updating the database, even though
|
||||
# the noop driver doesn't read these values
|
||||
for key, value in quota_updates:
|
||||
try:
|
||||
objects.Quotas.update_class(
|
||||
context, quota_class, key, value)
|
||||
except exception.QuotaClassNotFound:
|
||||
objects.Quotas.create_class(
|
||||
context, quota_class, key, value)
|
||||
|
||||
values = QUOTAS.get_class_quotas(context, quota_class)
|
||||
return self._format_quota_set(None, values, filtered_quotas,
|
||||
|
|
|
@ -22,6 +22,7 @@ from oslo_log import log as logging
|
|||
import nova.conf
|
||||
from nova import context as nova_context
|
||||
from nova import exception
|
||||
from nova.limit import utils as nova_limit_utils
|
||||
from nova import objects
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -119,7 +120,7 @@ def enforce_api_limit(entity_type: str, count: int) -> None:
|
|||
This is generally used for limiting the size of certain API requests
|
||||
that eventually get stored in the database.
|
||||
"""
|
||||
if CONF.quota.driver != UNIFIED_LIMITS_DRIVER:
|
||||
if not nova_limit_utils.use_unified_limits():
|
||||
return
|
||||
|
||||
if entity_type not in API_LIMITS:
|
||||
|
@ -163,7 +164,7 @@ def enforce_db_limit(
|
|||
* server_groups scope is context.project_id
|
||||
* server_group_members scope is server_group_uuid
|
||||
"""
|
||||
if CONF.quota.driver != UNIFIED_LIMITS_DRIVER:
|
||||
if not nova_limit_utils.use_unified_limits():
|
||||
return
|
||||
|
||||
if entity_type not in DB_COUNT_FUNCTION.keys():
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2022 StackHPC
|
||||
#
|
||||
# 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 nova.conf
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
UNIFIED_LIMITS_DRIVER = "nova.quota.UnifiedLimitsDriver"
|
||||
|
||||
|
||||
def use_unified_limits():
|
||||
return CONF.quota.driver == UNIFIED_LIMITS_DRIVER
|
|
@ -28,6 +28,7 @@ from nova.db.api import api as api_db_api
|
|||
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 import objects
|
||||
from nova.scheduler.client import report
|
||||
from nova import utils
|
||||
|
@ -792,6 +793,30 @@ class UnifiedLimitsDriver(NoopQuotaDriver):
|
|||
# To make unified limits APIs the same as the DB driver, return 0
|
||||
return 0
|
||||
|
||||
def get_class_quotas(self, context, resources, quota_class):
|
||||
"""Given a list of resources, retrieve the quotas for the given
|
||||
quota class.
|
||||
|
||||
:param context: The request context, for access checks.
|
||||
:param resources: A dictionary of the registered resources.
|
||||
:param quota_class: Placeholder, we always assume default quota class.
|
||||
"""
|
||||
|
||||
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
|
||||
quotas = {}
|
||||
for resource in resources.values():
|
||||
quotas[resource.name] = local_limits.get(resource.name, -1)
|
||||
|
||||
return quotas
|
||||
|
||||
|
||||
class BaseResource(object):
|
||||
"""Describe a single resource for quota checking."""
|
||||
|
|
|
@ -14,11 +14,13 @@
|
|||
# under the License.
|
||||
import copy
|
||||
import mock
|
||||
from oslo_limit import fixture as limit_fixture
|
||||
import webob
|
||||
|
||||
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 import objects
|
||||
from nova import test
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
|
@ -262,3 +264,108 @@ class NoopQuotaClassesTest(test.NoDBTestCase):
|
|||
|
||||
class UnifiedLimitsQuotaClassesTest(NoopQuotaClassesTest):
|
||||
quota_driver = "nova.quota.UnifiedLimitsDriver"
|
||||
|
||||
def setUp(self):
|
||||
super(UnifiedLimitsQuotaClassesTest, self).setUp()
|
||||
# Set server_groups so all config options get a different value
|
||||
# but we also test as much as possible with the default config
|
||||
self.flags(driver="nova.quota.UnifiedLimitsDriver", group='quota')
|
||||
reglimits = {local_limit.SERVER_METADATA_ITEMS: 128,
|
||||
local_limit.INJECTED_FILES: 5,
|
||||
local_limit.INJECTED_FILES_CONTENT: 10 * 1024,
|
||||
local_limit.INJECTED_FILES_PATH: 255,
|
||||
local_limit.KEY_PAIRS: 100,
|
||||
local_limit.SERVER_GROUPS: 12,
|
||||
local_limit.SERVER_GROUP_MEMBERS: 10}
|
||||
self.useFixture(limit_fixture.LimitFixture(reglimits, {}))
|
||||
|
||||
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,
|
||||
'ram': -1,
|
||||
'injected_file_content_bytes': 10240,
|
||||
'injected_file_path_bytes': 255,
|
||||
'injected_files': 5,
|
||||
'instances': -1,
|
||||
'key_pairs': 100,
|
||||
'metadata_items': 128,
|
||||
'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,
|
||||
'ram': -1,
|
||||
'key_pairs': 100,
|
||||
'metadata_items': 128,
|
||||
'server_group_members': 10,
|
||||
'server_groups': 12,
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected_response, response)
|
||||
|
||||
def test_update_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': 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
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected_response, response)
|
||||
# TODO(johngarbutt) we should be proxying to keystone
|
||||
self.assertEqual(0, mock_update.call_count)
|
||||
|
||||
@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,
|
||||
'ram': -1,
|
||||
'key_pairs': 100,
|
||||
'metadata_items': 128,
|
||||
'server_group_members': 10,
|
||||
'server_groups': 12,
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected_response, response)
|
||||
# TODO(johngarbutt) we should be proxying to keystone
|
||||
self.assertEqual(0, mock_update.call_count)
|
||||
|
|
|
@ -24,6 +24,7 @@ from oslo_utils.fixture import uuidsentinel as uuids
|
|||
from nova import context
|
||||
from nova import exception
|
||||
from nova.limit import local as local_limit
|
||||
from nova.limit import utils as limit_utils
|
||||
from nova import objects
|
||||
from nova import test
|
||||
|
||||
|
@ -33,7 +34,7 @@ CONF = cfg.CONF
|
|||
class TestLocalLimits(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(TestLocalLimits, self).setUp()
|
||||
self.flags(driver=local_limit.UNIFIED_LIMITS_DRIVER, group="quota")
|
||||
self.flags(driver=limit_utils.UNIFIED_LIMITS_DRIVER, group="quota")
|
||||
self.context = context.RequestContext()
|
||||
|
||||
def test_enforce_api_limit_metadata(self):
|
||||
|
@ -240,7 +241,7 @@ class GetLegacyLimitsTest(test.NoDBTestCase):
|
|||
"server_group_members": 7}
|
||||
self.resources = list(local_limit.API_LIMITS | local_limit.DB_LIMITS)
|
||||
self.resources.sort()
|
||||
self.flags(driver=local_limit.UNIFIED_LIMITS_DRIVER, group="quota")
|
||||
self.flags(driver=limit_utils.UNIFIED_LIMITS_DRIVER, group="quota")
|
||||
|
||||
def test_convert_keys_to_legacy_name(self):
|
||||
limits = local_limit._convert_keys_to_legacy_name(self.new)
|
||||
|
|
|
@ -1956,6 +1956,37 @@ class UnifiedLimitsDriverTestCase(NoopQuotaDriverTestCase):
|
|||
def setUp(self):
|
||||
super(UnifiedLimitsDriverTestCase, self).setUp()
|
||||
self.driver = quota.UnifiedLimitsDriver()
|
||||
# Set this so all limits get a different value but we also test as much
|
||||
# as possible with the default config
|
||||
reglimits = {local_limit.SERVER_METADATA_ITEMS: 128,
|
||||
local_limit.INJECTED_FILES: 5,
|
||||
local_limit.INJECTED_FILES_CONTENT: 10 * 1024,
|
||||
local_limit.INJECTED_FILES_PATH: 255,
|
||||
local_limit.KEY_PAIRS: 100,
|
||||
local_limit.SERVER_GROUPS: 12,
|
||||
local_limit.SERVER_GROUP_MEMBERS: 10}
|
||||
self.useFixture(limit_fixture.LimitFixture(reglimits, {}))
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
|
Loading…
Reference in New Issue