Move registration "ChassisBandwidthConfigEvent" to OvnSbIdl init

The "ChassisBandwidthConfigEvent" event registration is now done in the
"OvnSbIdl" class initialization. That ensures this event is registered
in a worker thread.

If the event is called before the "OVNMechanismDriver"'s "OVNClient"
has been instantiated, the event is skipped. When the OVN placement
extension is loaded, all chassis configurations are read and loaded,
thus any previous event is not relevant.

This patch also adds a "match_fn" check to
"ChassisBandwidthConfigEvent". If during an update event, the bandwidth
options are not modified, this class does not update the resource
provider inventories.

Closes-Bug: #1998108
Related-Bug: #1578989
Change-Id: I74883041c642b9498299ebf2b5bf885776e241e0
This commit is contained in:
Rodolfo Alonso Hernandez 2022-11-17 21:14:40 +01:00 committed by Rodolfo Alonso
parent 16399a2ce5
commit 49eab7d308
3 changed files with 68 additions and 17 deletions

View File

@ -110,8 +110,8 @@ def dict_chassis_config(state):
class ChassisBandwidthConfigEvent(row_event.RowEvent):
"""Chassis create update event to track the bandwidth config changes."""
def __init__(self, placement_extension):
self._placement_extension = placement_extension
def __init__(self, driver):
self._driver = driver
# NOTE(ralonsoh): BW resource provider information is stored in
# "Chassis", not "Chassis_Private".
table = 'Chassis'
@ -119,9 +119,30 @@ class ChassisBandwidthConfigEvent(row_event.RowEvent):
super().__init__(events, table, None)
self.event_name = 'ChassisBandwidthConfigEvent'
@property
def placement_extension(self):
if self._driver._post_fork_event.is_set():
return self._driver._ovn_client.placement_extension
def match_fn(self, event, row, old=None):
# If the OVNMechanismDriver OVNClient has not been instantiated, the
# event is skipped. All chassis configurations are read during the
# OVN placement extension initialization.
if (not self.placement_extension or
not self.placement_extension.enabled):
return False
elif event == self.ROW_CREATE:
return True
elif event == self.ROW_UPDATE and old and hasattr(old, 'other_config'):
row_bw = _parse_ovn_cms_options(row)
old_bw = _parse_ovn_cms_options(old)
if row_bw != old_bw:
return True
return False
def run(self, event, row, old):
name2uuid = self._placement_extension.name2uuid()
state = self._placement_extension.build_placement_state(row, name2uuid)
name2uuid = self.placement_extension.name2uuid()
state = self.placement_extension.build_placement_state(row, name2uuid)
if not state:
return
@ -153,18 +174,6 @@ class OVNClientPlacementExtension(object):
self._plugin = None
self.uuid_ns = ovn_const.OVN_RP_UUID
self.supported_vnic_types = ovn_const.OVN_SUPPORTED_VNIC_TYPES
if not self.enabled:
return
if not self._config_event:
self._config_event = ChassisBandwidthConfigEvent(self)
try:
self._driver._sb_idl.idl.notify_handler.watch_events(
[self._config_event])
except AttributeError:
# "sb_idl.idl.notify_handler" is not present in the
# MaintenanceWorker.
pass
@property
def placement_plugin(self):

View File

@ -35,6 +35,8 @@ from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.db import ovn_hash_ring_db
from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent as n_agent
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions import \
placement
CONF = cfg.CONF
@ -808,6 +810,7 @@ class OvnSbIdl(OvnIdlDistributedLock):
ChassisAgentTypeChangeEvent(self.driver),
ChassisMetadataAgentWriteEvent(self.driver),
PortBindingUpdateVirtualPortsEvent(driver),
placement.ChassisBandwidthConfigEvent(driver),
])
@classmethod

View File

@ -16,15 +16,18 @@ from unittest import mock
from neutron_lib import constants as n_const
from neutron_lib.plugins import constants as plugins_constants
from oslo_utils import uuidutils
from neutron.common.ovn import constants as ovn_const
from neutron.common import utils as common_utils
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
import placement as placement_extension
from neutron.tests.functional import base
from neutron.tests.functional.plugins.ml2.drivers.ovn.mech_driver.ovsdb \
import test_ovsdb_monitor
class TestOVNClientQosExtension(base.TestOVNFunctionalBase):
class TestOVNClientPlacementExtension(base.TestOVNFunctionalBase):
EMPTY_CHASSIS = {n_const.RP_BANDWIDTHS: {},
n_const.RP_INVENTORY_DEFAULTS: {},
@ -186,3 +189,39 @@ class TestOVNClientQosExtension(base.TestOVNFunctionalBase):
inventory_defaults='allocation_ratio:1.1;min_unit:1',
hypervisors='br-provider0:host2')
self._check_placement_config({**self.CHASSIS1, **self.CHASSIS2_B})
@mock.patch.object(placement_extension, '_send_deferred_batch')
def test_chassis_bandwidth_config_event(self, mock_send_placement):
ch_host = 'fake-chassis-host'
ch_name = uuidutils.generate_uuid()
ch_event = test_ovsdb_monitor.WaitForChassisPrivateCreateEvent(
ch_name, self.mech_driver.agent_chassis_table)
self.mech_driver.sb_ovn.idl.notify_handler.watch_event(ch_event)
self.chassis_name = self.add_fake_chassis(ch_host, name=ch_name)
self.assertTrue(ch_event.wait())
common_utils.wait_until_true(lambda: mock_send_placement.called,
timeout=2)
mock_send_placement.reset_mock()
# Once the chassis registger has been created, this new event will
# catch any chassis BW update.
self._update_chassis(
ch_name,
bandwidths='br-provider0:3000:4000',
inventory_defaults='allocation_ratio:3.0;min_unit:1',
hypervisors='br-provider0:host2')
common_utils.wait_until_true(lambda: mock_send_placement.called,
timeout=2)
mock_send_placement.reset_mock()
# The chassis BW information is written again without any change.
# That should not trigger the placement update.
self._update_chassis(
ch_name,
bandwidths='br-provider0:3000:4000',
inventory_defaults='allocation_ratio:3.0;min_unit:1',
hypervisors='br-provider0:host2')
self.assertRaises(common_utils.WaitTimeout,
common_utils.wait_until_true,
lambda: mock_send_placement.called,
timeout=2)