Skip to content

Commit 754d8eb

Browse files
author
Eric Fried
committed
Perf: Use dicts for ProviderTree roots
ProviderTree used to keep track of root providers in a list. Since we don't yet have sharing providers, this would always be a list of one for non-ironic deployments, or N for ironic deployments of N nodes. To find a provider (by name or UUID), we would iterate over this list, an O(N) operation. For large ironic deployments, this added up fast - see the referenced bug. With this change, we store roots in two dicts: one keyed by UUID, one keyed by name. To find a provider, we first check these dicts. If the provider we're looking for is a root, this is now O(1). (If it's a child, it would still be O(N), because we iterate over all the roots looking for a descendant that matches. But ironic deployments don't have child providers (yet?) (right?) so that should be n/a. For non-ironic deployments it's unchanged: O(M) where M is the number of descendants, which should be very small for the time being.) Test note: Existing tests in nova.tests.unit.compute.test_provider_tree thoroughly cover all the affected code paths. There was one usage of ProviderTree.roots that was untested and broken (even before this change) which is now fixed. Change-Id: Ibf430a8bc2a2af9353b8cdf875f8506377a1c9c2 Closes-Bug: #1816086 (cherry picked from commit 8c79745)
1 parent 950e044 commit 754d8eb

File tree

3 files changed

+34
-10
lines changed

3 files changed

+34
-10
lines changed

nova/compute/provider_tree.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from oslo_concurrency import lockutils
2626
from oslo_log import log as logging
2727
from oslo_utils import uuidutils
28+
import six
2829

2930
from nova.i18n import _
3031

@@ -232,7 +233,12 @@ class ProviderTree(object):
232233
def __init__(self):
233234
"""Create an empty provider tree."""
234235
self.lock = lockutils.internal_lock(_LOCK_NAME)
235-
self.roots = []
236+
self.roots_by_uuid = {}
237+
self.roots_by_name = {}
238+
239+
@property
240+
def roots(self):
241+
return six.itervalues(self.roots_by_uuid)
236242

237243
def get_provider_uuids(self, name_or_uuid=None):
238244
"""Return a list, in top-down traversable order, of the UUIDs of all
@@ -343,7 +349,8 @@ def populate_from_iterable(self, provider_dicts):
343349

344350
provider = _Provider.from_dict(pd)
345351
if parent_uuid is None:
346-
self.roots.append(provider)
352+
self.roots_by_uuid[provider.uuid] = provider
353+
self.roots_by_name[provider.name] = provider
347354
else:
348355
parent = self._find_with_lock(parent_uuid)
349356
parent.add_child(provider)
@@ -357,7 +364,8 @@ def _remove_with_lock(self, name_or_uuid):
357364
parent = self._find_with_lock(found.parent_uuid)
358365
parent.remove_child(found)
359366
else:
360-
self.roots.remove(found)
367+
del self.roots_by_uuid[found.uuid]
368+
del self.roots_by_name[found.name]
361369

362370
def remove(self, name_or_uuid):
363371
"""Safely removes the provider identified by the supplied name_or_uuid
@@ -393,10 +401,21 @@ def new_root(self, name, uuid, generation=None):
393401
raise ValueError(err % uuid)
394402

395403
p = _Provider(name, uuid=uuid, generation=generation)
396-
self.roots.append(p)
404+
self.roots_by_uuid[uuid] = p
405+
self.roots_by_name[name] = p
397406
return p.uuid
398407

399408
def _find_with_lock(self, name_or_uuid, return_root=False):
409+
# Optimization for large number of roots (e.g. ironic): if name_or_uuid
410+
# represents a root, this is O(1).
411+
found = self.roots_by_uuid.get(name_or_uuid)
412+
if found:
413+
return found
414+
found = self.roots_by_name.get(name_or_uuid)
415+
if found:
416+
return found
417+
418+
# Okay, it's a child; do it the hard way.
400419
for root in self.roots:
401420
found = root.find(name_or_uuid)
402421
if found:

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21394,7 +21394,8 @@ def test_allocate_mdevs_with_no_gpu_capacity(self,
2139421394
self.assertRaises(exception.ComputeResourcesUnavailable,
2139521395
drvr._allocate_mdevs, allocations=allocations)
2139621396

21397-
def test_allocate_mdevs_with_no_idea_of_the_provider(self):
21397+
@mock.patch.object(libvirt_driver.LOG, 'warning')
21398+
def test_allocate_mdevs_with_no_idea_of_the_provider(self, mock_warning):
2139821399
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
2139921400
# Mock the fact update_provider_tree() should have run
2140021401
drvr.provider_tree = self._get_fake_provider_tree_with_vgpu()
@@ -21421,6 +21422,9 @@ def test_allocate_mdevs_with_no_idea_of_the_provider(self):
2142121422
# Remember, rp2 has a wrong naming convention
2142221423
self.assertRaises(exception.ComputeResourcesUnavailable,
2142321424
drvr._allocate_mdevs, allocations=allocations)
21425+
mock_warning.assert_called_once_with(
21426+
"pGPU device name %(name)s can't be guessed from the ProviderTree "
21427+
"roots %(roots)s", {'name': 'oops_I_did_it_again', 'roots': 'cn'})
2142421428

2142521429
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_mediated_devices')
2142621430
@mock.patch.object(libvirt_driver.LibvirtDriver,

nova/virt/libvirt/driver.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6365,18 +6365,19 @@ def _allocate_mdevs(self, allocations):
63656365
rp_name = allocated_rp.name
63666366
# There can be multiple roots, we need to find the root name
63676367
# to guess the physical device name
6368-
roots = self.provider_tree.roots
6368+
roots = list(self.provider_tree.roots)
63696369
for root in roots:
63706370
if rp_name.startswith(root.name + '_'):
63716371
# The RP name convention is :
63726372
# root_name + '_' + parent_device
63736373
parent_device = rp_name[len(root.name) + 1:]
63746374
break
63756375
else:
6376-
LOG.warning("pGPU device name %(name)s can't be guessed from "
6377-
"the ProviderTree "
6378-
"roots %(roots)s", {'name': rp_name,
6379-
'roots': roots})
6376+
LOG.warning(
6377+
"pGPU device name %(name)s can't be guessed from the "
6378+
"ProviderTree roots %(roots)s",
6379+
{'name': rp_name,
6380+
'roots': ', '.join([root.name for root in roots])})
63806381
# We f... have no idea what was the parent device
63816382
# If we can't find devices having available VGPUs, just raise
63826383
raise exception.ComputeResourcesUnavailable(

0 commit comments

Comments
 (0)