From 5841cacecb79ba73a0ba02fd23db75fcc5909502 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Wed, 19 Mar 2025 12:24:17 +0000 Subject: [PATCH] [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 --- doc/source/admin/config-qos-min-bw.rst | 12 +++---- .../mech_driver/ovsdb/extensions/placement.py | 31 +++++++++++++------ .../ovn/mech_driver/ovsdb/ovn_db_sync.py | 3 -- .../ovsdb/extensions/test_placement.py | 31 +++++++++++++++++++ ...lacement-init-config-6198f572e1dadcba.yaml | 8 +++++ 5 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/ovn-placement-init-config-6198f572e1dadcba.yaml diff --git a/doc/source/admin/config-qos-min-bw.rst b/doc/source/admin/config-qos-min-bw.rst index a6d149b2f19..02ffbb3809f 100644 --- a/doc/source/admin/config-qos-min-bw.rst +++ b/doc/source/admin/config-qos-min-bw.rst @@ -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 diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py index 962e5896afa..3ae2a59e0bf 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py @@ -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,8 +160,8 @@ class ChassisBandwidthConfigEvent(row_event.RowEvent): _send_deferred_batch(state) ch_config = dict_chassis_config(state) - LOG.debug('OVN chassis %(chassis)s Placement configuration modified: ' - '%(config)s', {'chassis': row.name, 'config': ch_config}) + LOG.info('OVN chassis %(chassis)s Placement configuration modified: ' + '%(config)s', {'chassis': row.name, 'config': ch_config}) @common_utils.SingletonDecorator @@ -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): diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py index 60abdef6a36..9a8f5142356 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_db_sync.py @@ -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())) diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py index 3af56961711..e929e7d13eb 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py @@ -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) diff --git a/releasenotes/notes/ovn-placement-init-config-6198f572e1dadcba.yaml b/releasenotes/notes/ovn-placement-init-config-6198f572e1dadcba.yaml new file mode 100644 index 00000000000..b51b20fdcfd --- /dev/null +++ b/releasenotes/notes/ovn-placement-init-config-6198f572e1dadcba.yaml @@ -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.