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
This commit is contained in:
Rodolfo Alonso Hernandez 2022-11-29 19:43:09 +01:00 committed by Rodolfo Alonso
parent ab79ba917b
commit 70a86637e7
2 changed files with 64 additions and 4 deletions

View File

@ -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(

View File

@ -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': {