Filter computes without remote-managed ports early

Add a pre-filter for requests that contain VNIC_TYPE_REMOTE_MANAGED
ports in them: hosts that do not have either the relevant compute
driver capability COMPUTE_REMOTE_MANAGED_PORTS or PCI device pools
with "remote_managed" devices are filtered out early. Presence of
devices actually available for allocation is checked at a later
point by the PciPassthroughFilter.

Change-Id: I168d3ccc914f25a3d4255c9b319ee6b91a2f66e2
Implements: blueprint integration-with-off-path-network-backends
This commit is contained in:
Dmitrii Shcherbakov 2022-02-04 12:50:28 +03:00
parent d1e9ecb443
commit c487c730d0
8 changed files with 124 additions and 5 deletions

View File

@ -1126,6 +1126,28 @@ class ResourceTracker(object):
LOG.error('Unable to find services table record for nova-compute '
'host %s', self.host)
def _should_expose_remote_managed_ports_trait(self,
is_supported: bool):
"""Determine whether COMPUTE_REMOTE_MANAGED_PORTS should be exposed.
Determines if the COMPUTE_REMOTE_MANAGED_PORTS trait needs to be
exposed based on the respective compute driver capability and
the presence of remote managed devices on a given host. Whether such
devices are present or not depends on the Whitelist configuration
(presence of a remote_managed tag association with some PCI devices)
and their physical presence (plugged in, enumerated by the OS).
The aim of having this check is to optimize host lookup by prefiltering
hosts that have compute driver support but no hardware. The check
does not consider free device count - just the presence of device
pools since device availability may change between a prefilter check
and a later check in PciPassthroughFilter.
:param bool is_supported: Is the trait supported by the compute driver
"""
return (is_supported and
self.pci_tracker.pci_stats.has_remote_managed_device_pools())
def _get_traits(self, context, nodename, provider_tree):
"""Synchronizes internal and external traits for the node provider.
@ -1149,7 +1171,11 @@ class ResourceTracker(object):
# traits that are missing, and remove any existing set traits
# that are not currently supported.
for trait, supported in self.driver.capabilities_as_traits().items():
if supported:
add_trait = supported
if trait == os_traits.COMPUTE_REMOTE_MANAGED_PORTS:
add_trait &= self._should_expose_remote_managed_ports_trait(
supported)
if add_trait:
traits.add(trait)
elif trait in traits:
traits.remove(trait)

View File

@ -106,6 +106,7 @@ VNIC_TYPE_VIRTIO_FORWARDER = 'virtio-forwarder'
VNIC_TYPE_VDPA = 'vdpa'
VNIC_TYPE_ACCELERATOR_DIRECT = 'accelerator-direct'
VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL = 'accelerator-direct-physical'
VNIC_TYPE_REMOTE_MANAGED = "remote-managed"
# Define list of ports which needs pci request.
# Note: The macvtap port needs a PCI request as it is a tap interface
@ -121,14 +122,15 @@ VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL = 'accelerator-direct-physical'
# selected compute node.
VNIC_TYPES_SRIOV = (
VNIC_TYPE_DIRECT, VNIC_TYPE_MACVTAP, VNIC_TYPE_DIRECT_PHYSICAL,
VNIC_TYPE_VIRTIO_FORWARDER, VNIC_TYPE_VDPA)
VNIC_TYPE_VIRTIO_FORWARDER, VNIC_TYPE_VDPA, VNIC_TYPE_REMOTE_MANAGED)
# Define list of ports which are passthrough to the guest
# and need a special treatment on snapshot and suspend/resume
VNIC_TYPES_DIRECT_PASSTHROUGH = (VNIC_TYPE_DIRECT,
VNIC_TYPE_DIRECT_PHYSICAL,
VNIC_TYPE_ACCELERATOR_DIRECT,
VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL)
VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL,
VNIC_TYPE_REMOTE_MANAGED)
# Define list of ports which contains devices managed by cyborg.
VNIC_TYPES_ACCELERATOR = (

View File

@ -2123,6 +2123,27 @@ class API:
# the port binding profile and we can handle it as a boolean.
return strutils.bool_from_string(value)
@staticmethod
def _is_remote_managed(vnic_type):
"""Determine if the port is remote_managed or not by VNIC type.
:param str vnic_type: The VNIC type to assess.
:return: A boolean indicator whether the NIC is remote managed or not.
:rtype: bool
"""
return vnic_type == network_model.VNIC_TYPE_REMOTE_MANAGED
def is_remote_managed_port(self, context, port_id):
"""Determine if a port has a REMOTE_MANAGED VNIC type.
:param context: The request context
:param port_id: The id of the Neutron port
"""
port = self.show_port(context, port_id)['port']
return self._is_remote_managed(
port.get('binding:vnic_type', network_model.VNIC_TYPE_NORMAL)
)
# NOTE(sean-k-mooney): we might want to have this return a
# nova.network.model.VIF object instead in the future.
def _get_port_vnic_info(self, context, neutron, port_id):

View File

@ -729,3 +729,16 @@ class PciDeviceStats(object):
"""Return the contents of the pools as a PciDevicePoolList object."""
stats = [x for x in self]
return pci_device_pool.from_pci_stats(stats)
def has_remote_managed_device_pools(self) -> bool:
"""Determine whether remote managed device pools are present on a host.
The check is pool-based, not free device-based and is NUMA cell
agnostic.
"""
dummy_req = objects.InstancePCIRequest(
count=0,
spec=[{'remote_managed': True}]
)
pools = self._filter_pools_for_spec(self.pools, dummy_req)
return bool(pools)

View File

@ -365,6 +365,33 @@ def routed_networks_filter(
return True
@trace_request_filter
def remote_managed_ports_filter(
context: nova_context.RequestContext,
request_spec: 'objects.RequestSpec',
) -> bool:
"""Filter out hosts without remote managed port support (driver or hw).
If a request spec contains VNIC_TYPE_REMOTE_MANAGED ports then a
remote-managed port trait (COMPUTE_REMOTE_MANAGED_PORTS) is added to
the request in order to pre-filter hosts that do not use compute
drivers supporting remote managed ports and the ones that do not have
the device pools providing remote-managed ports (actual device
availability besides a pool presence check is done at the time of
PciPassthroughFilter execution).
"""
if request_spec.requested_networks:
network_api = neutron.API()
for request_net in request_spec.requested_networks:
if request_net.port_id and network_api.is_remote_managed_port(
context, request_net.port_id):
request_spec.root_required.add(
os_traits.COMPUTE_REMOTE_MANAGED_PORTS)
LOG.debug('remote_managed_ports_filter request filter added '
f'trait {os_traits.COMPUTE_REMOTE_MANAGED_PORTS}')
return True
ALL_REQUEST_FILTERS = [
require_tenant_aggregate,
map_az_to_placement_aggregate,
@ -374,6 +401,7 @@ ALL_REQUEST_FILTERS = [
transform_image_metadata,
accelerators_filter,
routed_networks_filter,
remote_managed_ports_filter,
]

View File

@ -45,7 +45,6 @@ class ProviderTreeTests(integrated_helpers.ProviderUsageBaseTestCase):
os_traits.COMPUTE_VOLUME_EXTEND,
os_traits.COMPUTE_VOLUME_MULTI_ATTACH,
os_traits.COMPUTE_TRUSTED_CERTS,
os_traits.COMPUTE_REMOTE_MANAGED_PORTS,
]
])

View File

@ -1577,9 +1577,13 @@ class TestUpdateComputeNode(BaseTestCase):
self.rt._update(mock.sentinel.ctx, new_compute)
save_mock.assert_called_once_with()
@mock.patch(
'nova.pci.stats.PciDeviceStats.has_remote_managed_device_pools',
return_value=True)
@mock.patch('nova.compute.resource_tracker.ResourceTracker.'
'_sync_compute_service_disabled_trait')
def test_existing_node_capabilities_as_traits(self, mock_sync_disabled):
def test_existing_node_capabilities_as_traits(
self, mock_sync_disabled, mock_has_remote_managed_device_pools):
"""The capabilities_as_traits() driver method returns traits
information for a node/provider.
"""
@ -1587,6 +1591,15 @@ class TestUpdateComputeNode(BaseTestCase):
rc = self.rt.reportclient
rc.set_traits_for_provider = mock.MagicMock()
# TODO(dmitriis): Remove once the PCI tracker is always created
# upon the resource tracker initialization.
with mock.patch.object(
objects.PciDeviceList, 'get_by_compute_node',
return_value=objects.PciDeviceList()
):
self.rt.pci_tracker = pci_manager.PciDevTracker(
mock.sentinel.ctx, _COMPUTE_NODE_FIXTURES[0])
# Emulate a driver that has implemented the update_from_provider_tree()
# virt driver method
self.driver_mock.update_provider_tree = mock.Mock()

View File

@ -3566,6 +3566,23 @@ class TestAPI(TestAPIBase):
self.assertFalse(tunneled)
self.assertIsNone(physnet_name)
def test_is_remote_managed(self):
cases = {
(model.VNIC_TYPE_NORMAL, False),
(model.VNIC_TYPE_DIRECT, False),
(model.VNIC_TYPE_MACVTAP, False),
(model.VNIC_TYPE_DIRECT_PHYSICAL, False),
(model.VNIC_TYPE_BAREMETAL, False),
(model.VNIC_TYPE_VIRTIO_FORWARDER, False),
(model.VNIC_TYPE_VDPA, False),
(model.VNIC_TYPE_ACCELERATOR_DIRECT, False),
(model.VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL, False),
(model.VNIC_TYPE_REMOTE_MANAGED, True),
}
for vnic_type, expected in cases:
self.assertEqual(self.api._is_remote_managed(vnic_type), expected)
def _test_get_port_vnic_info(
self, mock_get_client, binding_vnic_type, expected_vnic_type,
port_resource_request=None, numa_policy=None