[OVN][Placement] Placement init config with Chassis create event

Now the initial Placement configuration per Chassis is build when
a Chassis create event is received. That will happen when:
* The Neutron API is restarted.
* When a new Chassis register is created.

Closes-Bug: #2102090
Change-Id: I9aa8accdcead079fe2e713fe569f8bff7403be92
This commit is contained in:
Rodolfo Alonso Hernandez 2025-03-19 12:24:17 +00:00
parent 89053ab55f
commit 5841cacecb
5 changed files with 65 additions and 20 deletions

View File

@ -325,13 +325,11 @@ by commas.
This information is retrieved from the OVN SB database during the Neutron
server initialization and when the "Chassis" registers are updated.
During the Neutron server initialization, a ``MaintenanceWorker`` thread will
call ``OvnSbSynchronizer.do_sync``, that will call
``OVNClientPlacementExtension.read_initial_chassis_config``. This method lists
all chassis and builds the resource provider information needed by Placement.
This information is stored in the "Chassis" registers, in
"external_ids:ovn-cms-options", with the same format as retrieved from the
local "Open_vSwitch" registers from each chassis.
The initial Placement configuration is retrieved when the Neutron API receives
a "Chassis" create event, that happens when the IDL is connected to the
database server. When a creation event is received, the Neutron API reads the
configuration, builds a ``PlacementState`` instance and sends it to the
Placement API.
The second method to update the Placement information is when a "Chassis"
registers is updated. The ``OVNClientPlacementExtension`` extension registers

View File

@ -125,16 +125,19 @@ class ChassisBandwidthConfigEvent(row_event.RowEvent):
if self._driver._post_fork_event.is_set():
return self._driver._ovn_client.placement_extension
@property
def placement_extension_enabled(self):
return self.placement_extension and self.placement_extension.enabled
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
if event == self.ROW_CREATE:
return True
if event == self.ROW_UPDATE and old and hasattr(old, 'other_config'):
# If the OVNMechanismDriver OVNClient has not been instantiated, the
# update event is skipped.
if not self.placement_extension_enabled:
return False
if 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:
@ -142,6 +145,14 @@ class ChassisBandwidthConfigEvent(row_event.RowEvent):
return False
def run(self, event, row, old):
if event == self.ROW_CREATE:
# It is possible that a Chassis create event is received before
# the OVNMechanismDriver OVNClient has been instantiated. Wait for
# it and check the Placement extension.
self._driver._post_fork_event.wait()
if not self.placement_extension_enabled:
return
name2uuid = self.placement_extension.name2uuid()
state = self.placement_extension.build_placement_state(row, name2uuid)
if not state:
@ -149,7 +160,7 @@ class ChassisBandwidthConfigEvent(row_event.RowEvent):
_send_deferred_batch(state)
ch_config = dict_chassis_config(state)
LOG.debug('OVN chassis %(chassis)s Placement configuration modified: '
LOG.info('OVN chassis %(chassis)s Placement configuration modified: '
'%(config)s', {'chassis': row.name, 'config': ch_config})
@ -228,7 +239,7 @@ class OVNClientPlacementExtension:
msg = ', '.join(['Chassis {}: {}'.format(
name, dict_chassis_config(state))
for (name, state) in chassis.items()]) or '(no info)'
LOG.debug('OVN chassis Placement initial configuration: %s', msg)
LOG.info('OVN chassis Placement initial configuration: %s', msg)
return chassis
def name2uuid(self, name=None):

View File

@ -1351,9 +1351,6 @@ class OvnSbSynchronizer(OvnDbSynchronizer):
self.sync_hostname_and_physical_networks(ctx)
if utils.is_ovn_l3(self.l3_plugin):
self.l3_plugin.schedule_unhosted_gateways()
# NOTE(ralonsoh): this could be called using a resource event.
self.ovn_driver._ovn_client.placement_extension.\
read_initial_chassis_config()
LOG.debug("OVN-Southbound DB sync process completed @ %s",
str(datetime.now()))

View File

@ -223,3 +223,34 @@ class TestOVNClientPlacementExtension(base.TestOVNFunctionalBase):
common_utils.wait_until_true,
lambda: mock_send_placement.called,
timeout=2)
@mock.patch.object(placement_extension, '_send_deferred_batch')
def test_chassis_bandwidth_initial_config_event(self, mock_send_placement):
ch_name = uuidutils.generate_uuid()
rp_uuid = uuidutils.generate_uuid()
ch_event = test_ovsdb_monitor.WaitForChassisPrivateCreateEvent(ch_name)
self.mech_driver.sb_ovn.idl.notify_handler.watch_event(ch_event)
self.mock_name2uuid.return_value = {'host1': rp_uuid}
self._create_chassis(
'host1', ch_name, physical_nets=['phys1'],
bandwidths='br-provider0:1000:2000',
inventory_defaults='allocation_ratio:1.0;min_unit:2',
hypervisors='br-provider0:host1')
self.assertTrue(ch_event.wait())
common_utils.wait_until_true(lambda: mock_send_placement.called,
timeout=2)
mock_send_placement.assert_called_once()
placement_state = mock_send_placement.call_args[0][0]
device_mappings = {'phys1': ['br-provider0']}
self.assertEqual(placement_state._device_mappings, device_mappings)
hypervisor_rps = {'br-provider0': {'name': 'host1', 'uuid': rp_uuid}}
self.assertEqual(placement_state._hypervisor_rps, hypervisor_rps)
rp_bandwidths = {'br-provider0': {'egress': 1000, 'ingress': 2000}}
self.assertEqual(placement_state._rp_bandwidths, rp_bandwidths)
rp_inventory_defaults = {'allocation_ratio': 1.0, 'min_unit': 2}
self.assertEqual(placement_state._rp_inventory_defaults,
rp_inventory_defaults)

View File

@ -0,0 +1,8 @@
---
issues:
- |
The ML2/OVN Placement initial configuration is executed now in the Neutron
API process and removed from the maintenance worker; since the migration
to WSGI, now the API and the maintenance worker are different processes.
When an OVN ``Chassis`` creation event is received, the configuration is
read, a ``PlacementState`` object created and sent to the Placement API.