Implement specific tracked resource count method per quota driver

This patch implements a new method specific for each quota driver
class. This method, "get_resource_count", returns the current number
of resources created in a project of a tracked resource. A tracked
resource is an instance of ``neutron.quota.resource.TrackedResource``.
This method does not count the current reservations, just the actual
resources created.

This new method, "get_resource_count", will be added to the abstract
class ``neutron_lib.db.quota_api.QuotaDriverAPI``.

This patch also fixes ``TestDbQuotaDriverNoLock``, that was using a
plugin inheriting from ``DbQuotaDriver`` instead of
``DbQuotaNoLockDriver``.

Closes-Bug: #1982962

Conflicts:
  neutron/db/quota/driver_null.py

Change-Id: I2707506468cb60d93a4459ea364f1e79faa83838
(cherry picked from commit bd60f0833b)
This commit is contained in:
Rodolfo Alonso Hernandez 2022-07-27 17:55:48 +02:00
parent d8152fe963
commit ca96555e81
5 changed files with 92 additions and 9 deletions

View File

@ -76,9 +76,8 @@ class DbQuotaDriver(nlib_quota_api.QuotaDriverAPI):
return project_quota
@staticmethod
@db_api.retry_if_session_inactive()
def get_detailed_project_quotas(context, resources, project_id):
def get_detailed_project_quotas(self, context, resources, project_id):
"""Given a list of resources and a specific project, retrieve
the detailed quotas (limit, used, reserved).
:param context: The request context, for access checks.
@ -92,8 +91,7 @@ class DbQuotaDriver(nlib_quota_api.QuotaDriverAPI):
project_quota_ext = {}
for key, resource in resources.items():
if isinstance(resource, res.TrackedResource):
used = resource.count_used(context, project_id,
resync_usage=False)
used = self.get_resource_count(context, project_id, resource)
else:
# NOTE(ihrachys) .count won't use the plugin we pass, but we
# pass it regardless to keep the quota driver API intact
@ -319,6 +317,11 @@ class DbQuotaDriver(nlib_quota_api.QuotaDriverAPI):
return tracked_resource.count(context, None, project_id,
resync_usage=False)
@staticmethod
def get_resource_count(context, project_id, tracked_resource):
return tracked_resource.count_used(context, project_id,
resync_usage=False)
def quota_limit_check(self, context, project_id, resources, deltas):
# Ensure no value is less than zero
unders = [key for key, val in deltas.items() if val < 0]

View File

@ -92,6 +92,10 @@ class DbQuotaNoLockDriver(quota_driver.DbQuotaDriver):
return tracked_resource.count(context, None, project_id,
count_db_registers=True)
@staticmethod
def get_resource_count(context, project_id, tracked_resource):
return tracked_resource.count_db_registers(context, project_id)
@staticmethod
def get_workers():
interval = quota_api.RESERVATION_EXPIRATION_TIMEOUT

View File

@ -60,9 +60,7 @@ class QuotaSetsController(wsgi.Controller):
def __init__(self, plugin):
self._resource_name = RESOURCE_NAME
self._plugin = plugin
self._driver = importutils.import_class(
cfg.CONF.QUOTAS.quota_driver
)
self._driver = importutils.import_class(cfg.CONF.QUOTAS.quota_driver)()
self._update_extended_attributes = True
def _update_attributes(self):

View File

@ -333,13 +333,13 @@ class TrackedResource(BaseResource):
CountableResource instances.
"""
if count_db_registers:
count = self._count_db_registers(context, project_id)
count = self.count_db_registers(context, project_id)
else:
count = self.count_used(context, project_id, resync_usage)
return count + self.count_reserved(context, project_id)
def _count_db_registers(self, context, project_id):
def count_db_registers(self, context, project_id):
"""Return the existing resources (self._model_class) in a project.
The query executed must be as fast as possible. To avoid retrieving all

View File

@ -15,18 +15,33 @@
import itertools
import netaddr
from neutron_lib import constants
from neutron_lib import context
from neutron_lib.db import api as db_api
from neutron.db import db_base_plugin_v2 as base_plugin
from neutron.db import models_v2
from neutron.db.quota import api as quota_api
from neutron.db.quota import driver_nolock
from neutron.objects import network as network_obj
from neutron.objects import ports as port_obj
from neutron.objects import quota as quota_obj
from neutron.objects import subnet as subnet_obj
from neutron.quota import resource as quota_resource
from neutron.tests.unit.db.quota import test_driver
class FakePlugin(base_plugin.NeutronDbPluginV2,
driver_nolock.DbQuotaNoLockDriver):
"""A fake plugin class containing all DB methods."""
class TestDbQuotaDriverNoLock(test_driver.TestDbQuotaDriver):
def setUp(self):
super(TestDbQuotaDriverNoLock, self).setUp()
self.plugin = FakePlugin()
self.quota_driver = driver_nolock.DbQuotaNoLockDriver()
@staticmethod
@ -60,3 +75,66 @@ class TestDbQuotaDriverNoLock(test_driver.TestDbQuotaDriver):
self.quota_driver._remove_expired_reservations()
res = quota_obj.Reservation.get_objects(self.context)
self.assertEqual([], res)
def test_get_detailed_project_quotas_resource(self):
user_ctx = context.Context(user_id=self.project_1,
tenant_id=self.project_1)
tracked_resource = quota_resource.TrackedResource(
'network', models_v2.Network, 'quota_network')
res = {'network': tracked_resource}
self.plugin.update_quota_limit(user_ctx, self.project_1, 'network', 20)
self.quota_driver.make_reservation(user_ctx, self.project_1, res,
{'network': 5}, self.plugin)
with db_api.CONTEXT_WRITER.using(user_ctx):
network_obj.Network(user_ctx, project_id=self.project_1).create()
detailed_quota = self.plugin.get_detailed_project_quotas(
user_ctx, res, self.project_1)
reference = {'network': {'limit': 20, 'used': 1, 'reserved': 5}}
self.assertEqual(reference, detailed_quota)
@staticmethod
def _create_tracked_resources():
return {
'network': quota_resource.TrackedResource(
'network', models_v2.Network, 'quota_network'),
'subnet': quota_resource.TrackedResource(
'subnet', models_v2.Subnet, 'quota_subnet'),
'port': quota_resource.TrackedResource(
'port', models_v2.Port, 'quota_port'),
}
def test_get_detailed_project_quotas_multiple_resource(self):
resources = self._create_tracked_resources()
for project_id in self.projects:
user_ctx = context.Context(user_id=project_id,
tenant_id=project_id)
self.plugin.update_quota_limit(
user_ctx, project_id, 'network', 101)
self.plugin.update_quota_limit(user_ctx, project_id, 'subnet', 102)
self.plugin.update_quota_limit(user_ctx, project_id, 'port', 103)
with db_api.CONTEXT_WRITER.using(user_ctx):
net = network_obj.Network(
user_ctx, project_id=project_id)
net.create()
subnet_obj.Subnet(
user_ctx, project_id=project_id, network_id=net.id,
ip_version=constants.IP_VERSION_4,
cidr=netaddr.IPNetwork('1.2.3.0/24')).create()
port_obj.Port(
user_ctx, project_id=project_id,
network_id=net.id,
mac_address=netaddr.EUI('ca:fe:ca:fe:ca:fe'),
admin_state_up=False, status='DOWN', device_id='',
device_owner='').create()
reference = {'network': {'limit': 101, 'used': 1, 'reserved': 0},
'subnet': {'limit': 102, 'used': 1, 'reserved': 0},
'port': {'limit': 103, 'used': 1, 'reserved': 0}}
for project_id in self.projects:
user_ctx = context.Context(user_id=project_id,
tenant_id=project_id)
returned = self.plugin.get_detailed_project_quotas(
user_ctx, resources, project_id)
self.assertEqual(reference, returned)