Merge "[quota]Refactor group counting to scatter-gather"

This commit is contained in:
Zuul 2025-05-06 22:40:35 +00:00 committed by Gerrit Code Review
commit a106f25e8e
2 changed files with 118 additions and 28 deletions

View File

@ -32,7 +32,6 @@ 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
LOG = logging.getLogger(__name__)
CONF = nova.conf.CONF
@ -1208,38 +1207,31 @@ def _keypair_get_count_by_user(context, user_id):
def _server_group_count_members_by_user_legacy(context, group, user_id):
# NOTE(melwitt): This is mostly duplicated from
# InstanceGroup.count_members_by_user() to query across multiple cells.
# We need to be able to pass the correct cell context to
# InstanceList.get_by_filters().
# NOTE(melwitt): Counting across cells for instances means we will miss
# counting resources if a cell is down.
cell_mappings = objects.CellMappingList.get_all(context)
greenthreads = []
filters = {'deleted': False, 'user_id': user_id, 'uuid': group.members}
for cell_mapping in cell_mappings:
with nova_context.target_cell(context, cell_mapping) as cctxt:
greenthreads.append(utils.spawn(
objects.InstanceList.get_by_filters, cctxt, filters,
expected_attrs=[]))
instances = objects.InstanceList(objects=[])
for greenthread in greenthreads:
found = greenthread.wait()
instances = instances + found
# Count build requests using the same filters to catch group members
# that are not yet created in a cell.
# NOTE(mriedem): BuildRequestList.get_by_filters is not very efficient for
# what we need and we can optimize this with a new query method.
build_requests = objects.BuildRequestList.get_by_filters(context, filters)
def group_member_uuids(cctxt):
return {inst.uuid for inst in objects.InstanceList.get_by_filters(
cctxt, filters, expected_attrs=[])}
# Ignore any duplicates since build requests and instances can co-exist
# for a short window of time after the instance is created in a cell but
# before the build request is deleted.
instance_uuids = [inst.uuid for inst in instances]
count = len(instances)
instance_uuids = set()
# NOTE(melwitt): Counting across cells for instances means we will miss
# counting resources if a cell is down.
per_cell = nova_context.scatter_gather_all_cells(
context, group_member_uuids)
for uuids in per_cell.values():
instance_uuids |= uuids
# Count build requests using the same filters to catch group members
# that are not yet created in a cell.
build_requests = objects.BuildRequestList.get_by_filters(context, filters)
for build_request in build_requests:
if build_request.instance_uuid not in instance_uuids:
count += 1
return {'user': {'server_group_members': count}}
instance_uuids.add(build_request.instance_uuid)
return {'user': {'server_group_members': len(instance_uuids)}}
def is_qfd_populated(context):

View File

@ -2287,3 +2287,101 @@ class QuotaCountTestCase(test.NoDBTestCase):
quota._instances_cores_ram_count(mock.sentinel.context,
mock.sentinel.project_id)
mock_uid_qfd_populated.assert_not_called()
class LegacyGroupMemberQuotaTestCase(test.NoDBTestCase):
@mock.patch('nova.objects.BuildRequestList.get_by_filters')
@mock.patch('nova.objects.InstanceList.get_by_filters')
@mock.patch('nova.objects.CellMappingList.get_all')
def test_no_cells_no_build_reqs(
self, mock_get_cell_mappings, mock_inst_get, mock_build_req_get
):
mock_get_cell_mappings.return_value = []
group = objects.InstanceGroup()
group.members = []
self.assertEqual(
{'user': {'server_group_members': 0}},
quota._server_group_count_members_by_user_legacy(
mock.sentinel.context, group, mock.sentinel.user_id))
mock_get_cell_mappings.assert_called_once()
mock_inst_get.assert_not_called()
mock_build_req_get.assert_called_once_with(
mock.sentinel.context,
{'deleted': False, 'user_id': mock.sentinel.user_id, 'uuid': []})
@mock.patch('nova.objects.BuildRequestList.get_by_filters')
@mock.patch('nova.objects.InstanceList.get_by_filters')
@mock.patch('nova.objects.CellMappingList.get_all')
def test_no_cells_just_build_reqs(
self, mock_get_cell_mappings, mock_inst_get, mock_build_req_get
):
mock_get_cell_mappings.return_value = []
br = objects.BuildRequest()
br.instance_uuid = uuids.inst1
mock_build_req_get.return_value = objects.BuildRequestList(
objects=[br])
group = objects.InstanceGroup()
group.members = []
self.assertEqual(
{'user': {'server_group_members': 1}},
quota._server_group_count_members_by_user_legacy(
mock.sentinel.context, group, mock.sentinel.user_id))
mock_get_cell_mappings.assert_called_once()
mock_inst_get.assert_not_called()
mock_build_req_get.assert_called_once_with(
mock.sentinel.context,
{'deleted': False, 'user_id': mock.sentinel.user_id, 'uuid': []})
@mock.patch('nova.objects.BuildRequestList.get_by_filters')
@mock.patch('nova.objects.InstanceList.get_by_filters')
@mock.patch('nova.objects.CellMappingList.get_all')
def test_cells_and_build_reqs(
self, mock_get_cell_mappings, mock_inst_get, mock_build_req_get
):
cell1 = objects.CellMapping()
cell1.name = "cell1"
cell1.uuid = uuids.cell1
cell2 = objects.CellMapping()
cell2.name = "cell2"
cell2.uuid = uuids.cell2
mock_get_cell_mappings.return_value = objects.CellMappingList(
objects=[cell1, cell2])
br1 = objects.BuildRequest()
br1.instance_uuid = uuids.inst1
br2 = objects.BuildRequest()
br2.instance_uuid = uuids.inst2
mock_build_req_get.return_value = objects.BuildRequestList(
objects=[br1, br2])
inst1 = objects.Instance()
inst1.uuid = uuids.inst1 # same as br1
inst3 = objects.Instance()
inst3.uuid = uuids.inst3
mock_inst_get.side_effect = [
# cell1
objects.InstanceList(objects=[inst1]),
# cell2
objects.InstanceList(objects=[inst3]),
]
group = objects.InstanceGroup()
group.members = []
# as br1 and inst1 counted only once as they are the same
self.assertEqual(
{'user': {'server_group_members': 3}},
quota._server_group_count_members_by_user_legacy(
mock.sentinel.context, group, mock.sentinel.user_id))
mock_get_cell_mappings.assert_called_once()
self.assertEqual(2, len(mock_inst_get.mock_calls))
mock_build_req_get.assert_called_once_with(
mock.sentinel.context,
{'deleted': False, 'user_id': mock.sentinel.user_id, 'uuid': []})