From 70a86637e71eb67e7785f090ebc1045040bca897 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 29 Nov 2022 19:43:09 +0100 Subject: [PATCH] Allow shared resources between physical and tunnelled networks Since [1] it is possible to create resource providers for tunnelled networks and model the available bandwidth for overlay networks. In some deployments, the admin uses the same physical network interface for a physical network and the tunnelled networks. In that case it is complicated to correctly split the bandwidth between the inventories of the resource providers. This patch adds the ability to create a resource provider with traits of both the physical network and the tunnelled networks. E.g.: $ openstack resource provider trait list $brex +----------------------------------+ | name | +----------------------------------+ | CUSTOM_NETWORK_TUNNEL_PROVIDER | | CUSTOM_PHYSNET_PUBLIC | | ... | +----------------------------------+ With this resource provider, a request comming from a port in the physical network or an overlay network is provided by the same inventory. [1]https://review.opendev.org/c/openstack/neutron/+/860639 What is missing and will be added in next patches: * Tempest tests, that will be pushed to the corresponding repository. Closes-Bug: #1998202 Change-Id: I801e770fd8b4d6504f5407108e1d1fd848f96c09 --- neutron/agent/common/placement_report.py | 24 ++++++++-- .../agent/common/test_placement_report.py | 44 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/neutron/agent/common/placement_report.py b/neutron/agent/common/placement_report.py index 103a23b949b..dab95e91089 100644 --- a/neutron/agent/common/placement_report.py +++ b/neutron/agent/common/placement_report.py @@ -205,14 +205,30 @@ class PlacementState(object): return agent_rp_traits def deferred_update_resource_provider_traits(self): + + def _get_traits(device, physical_bridges, physnet_trait_mappings): + if device == self._rp_tun_name and device not in physical_bridges: + # That means the RP for tunnelled networks is not associated + # to a physical bridge interface. + return [n_const.TRAIT_NETWORK_TUNNEL] + elif device == self._rp_tun_name and device in physical_bridges: + # The physical network and the tunnelled networks share the + # same physical interface. + return [n_const.TRAIT_NETWORK_TUNNEL, + physnet_trait_mappings[device]] + else: + # Just the physical interface. + return [physnet_trait_mappings.get(device)] + rp_traits = [] - tunnelled_trait_mappings = { - self._rp_tun_name: n_const.TRAIT_NETWORK_TUNNEL} + physical_bridges = {br for brs in self._device_mappings.values() for + br in brs} physnet_trait_mappings = {} for physnet, devices in self._device_mappings.items(): for device in devices: physnet_trait_mappings[device] = place_utils.physnet_trait( physnet) + vnic_type_traits = [place_utils.vnic_type_trait(vnic_type) for vnic_type in self._supported_vnic_types] @@ -221,8 +237,8 @@ class PlacementState(object): self._driver_uuid_namespace, self._hypervisor_rps[device]['name'], device) - traits = [physnet_trait_mappings.get(device) or - tunnelled_trait_mappings[device]] + traits = _get_traits(device, physical_bridges, + physnet_trait_mappings) traits.extend(vnic_type_traits) rp_traits.append( DeferredCall( diff --git a/neutron/tests/unit/agent/common/test_placement_report.py b/neutron/tests/unit/agent/common/test_placement_report.py index 26c41de20b5..8b108b971df 100644 --- a/neutron/tests/unit/agent/common/test_placement_report.py +++ b/neutron/tests/unit/agent/common/test_placement_report.py @@ -15,6 +15,8 @@ from unittest import mock import uuid +from oslo_config import cfg + from neutron.agent.common import placement_report from neutron.common import _constants as n_const from neutron.conf.plugins.ml2 import config as ml2_config @@ -243,6 +245,48 @@ class PlacementStateTestCase(base.BaseTestCase): {'CUSTOM_VNIC_TYPE_NORMAL'}], actual_traits) + def test_deferred_update_resource_provider_traits_shared_rp(self): + import uuid + self.kwargs.update({ + 'device_mappings': { + 'physnet0': ['eth0'], + }, + 'rp_bandwidths': { + 'eth0': {'egress': 1, 'ingress': 1}, + }, + 'supported_vnic_types': ['normal'], + }) + cfg.CONF.set_override('tunnelled_network_rp_name', 'eth0', group='ml2') + state = placement_report.PlacementState(**self.kwargs) + + for deferred in state.deferred_update_resource_provider_traits(): + deferred.execute() + + expected_calls = [ + # uuid below generated by the following command: + # uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost:eth0' + mock.call( + resource_provider_uuid=uuid.UUID( + '1ea6f823-bcf2-5dc5-9bee-4ee6177a6451'), + traits=mock.ANY), + + # uuid -v5 '00000000-0000-0000-0000-000000000001' 'fakehost' + mock.call( + resource_provider_uuid=uuid.UUID( + 'c0b4abe5-516f-54b8-b965-ff94060dcbcc'), + traits=mock.ANY)] + self.client_mock.update_resource_provider_traits.assert_has_calls( + expected_calls) + + # NOTE(bence romsics): To avoid testing the _order_ of traits. + actual_traits = [set(args[1]['traits']) for args in + self.client_mock.update_resource_provider_traits.call_args_list] + self.assertEqual( + [{n_const.TRAIT_NETWORK_TUNNEL, 'CUSTOM_PHYSNET_PHYSNET0', + 'CUSTOM_VNIC_TYPE_NORMAL'}, + {'CUSTOM_VNIC_TYPE_NORMAL'}], + actual_traits) + def test_deferred_update_resource_provider_inventories_bw(self): self.kwargs.update({ 'device_mappings': {