[OVN][Placement] Read the initial config in the maintenance worker

Read the chassis bandwidth configuration (stored in the "Chassis"
registers) only once, in the maintenance worker. The SB synchronizer
will call the OVN client Placement extension
"read_initial_chassis_config" method.

This new approach changes how the Placement information is stored. The
Placement extension does not store anymore a local cache of the
resource providers. Instead of this, in future patches, when this
information is required, the Placement extension will retrieve this
information from the SB DB, reading the content from the "Chassis"
registers and parsing the values.

Partial-Bug: #1578989
Change-Id: I160b1dda85596277125c532ea4ce4df8e4d25b63
This commit is contained in:
Rodolfo Alonso Hernandez 2021-12-15 15:50:27 +00:00
parent 4fe4d25f42
commit 56dbfb7ac4
6 changed files with 101 additions and 67 deletions

View File

@ -301,6 +301,25 @@ SR-IOV and OVS agents. This is how the values are registered:
Each configuration option defined in "external_ids:ovn-cms-options" is divided
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 second method to update the Placement information is when a "Chassis"
registers is updated. The ``OVNClientPlacementExtension`` extension registers
an event handler that attends the OVN SB "Chassis" bandwidth configuration
changes. This event handler builds a ``PlacementState`` instance and sends it
to the Placement API. If a new chassis is added or an existing one changes its
resource provider configuration, this event updates it in the Placement
database.
Propagation of resource information
-----------------------------------

View File

@ -100,7 +100,7 @@ def _send_deferred_batch(state):
LOG.exception('Placement client call failed: %s', str(deferred))
def _dict_chassis_config(state):
def dict_chassis_config(state):
if state:
return {n_const.RP_BANDWIDTHS: state._rp_bandwidths,
n_const.RP_INVENTORY_DEFAULTS: state._rp_inventory_defaults,
@ -122,10 +122,11 @@ class ChassisBandwidthConfigEvent(row_event.RowEvent):
def run(self, event, row, old):
name2uuid = self._placement_extension.name2uuid()
state = self._placement_extension.build_placement_state(row, name2uuid)
if not state:
return
_send_deferred_batch(state)
self._placement_extension.add_chassis_config(
row.name, _dict_chassis_config(state))
ch_config = self._placement_extension.get_chassis_config(row.name)
ch_config = dict_chassis_config(state)
LOG.debug('OVN chassis %(chassis)s Placement configuration modified: '
'%(config)s', {'chassis': row.name, 'config': ch_config})
@ -152,9 +153,7 @@ class OVNClientPlacementExtension(object):
self._plugin = None
self.uuid_ns = ovn_const.OVN_RP_UUID
self.supported_vnic_types = ovn_const.OVN_SUPPORTED_VNIC_TYPES
self._enabled = bool(self.placement_plugin)
self._chassis = {} # Initial config read could take some time.
if not self._enabled:
if not self.enabled:
return
if not self._config_event:
@ -163,12 +162,9 @@ class OVNClientPlacementExtension(object):
self._driver._sb_idl.idl.notify_handler.watch_events(
[self._config_event])
except AttributeError:
self._enabled = False
if not self._enabled:
return
self._chassis = self._read_initial_chassis_config()
# "sb_idl.idl.notify_handler" is not present in the
# MaintenanceWorker.
pass
@property
def placement_plugin(self):
@ -177,6 +173,10 @@ class OVNClientPlacementExtension(object):
plugins_constants.PLACEMENT_REPORT)
return self._placement_plugin
@property
def enabled(self):
return bool(self.placement_plugin)
@property
def plugin(self):
if self._plugin is None:
@ -190,24 +190,33 @@ class OVNClientPlacementExtension(object):
self.plugin.mechanism_manager.mech_drivers['ovn'].obj)
return self._ovn_mech_driver
@property
def chassis(self):
return self._chassis
def _read_initial_chassis_config(self):
def get_chassis_config(self):
"""Read all Chassis BW config and returns the Placement states"""
chassis = {}
name2uuid = self.name2uuid()
for ch in self._driver._sb_idl.chassis_list().execute(
check_error=True):
state = self.build_placement_state(ch, name2uuid)
_send_deferred_batch(state)
config = _dict_chassis_config(state)
if config:
chassis[ch.name] = config
if state:
chassis[ch.name] = state
msg = '\n'.join(['Chassis %s: %s' % (name, config)
for (name, config) in chassis.items()])
LOG.debug('OVN chassis Placement initial configuration:\n%s', msg)
return chassis
def read_initial_chassis_config(self):
"""Read the Chassis BW configuration and update the Placement API
This method is called once from the MaintenanceWorker when the Neutron
server starts.
"""
if not self.enabled:
return
chassis = self.get_chassis_config()
for state in chassis.values():
_send_deferred_batch(state)
msg = ', '.join(['Chassis %s: %s' % (name, dict_chassis_config(state))
for (name, state) in chassis.items()]) or '(no info)'
LOG.debug('OVN chassis Placement initial configuration: %s', msg)
return chassis
def name2uuid(self, name=None):
@ -248,7 +257,9 @@ class OVNClientPlacementExtension(object):
device in hypervisor_rps and device in bridges}
# NOTE(ralonsoh): OVN only reports min BW RPs; packet processing RPs
# will be added in a future implementation.
# will be added in a future implementation. If no RP_BANDWIDTHS values
# are present (that means there is no BW information for any interface
# in this host), no "PlacementState" is returned.
return placement_report.PlacementState(
rp_bandwidths=cms_options[n_const.RP_BANDWIDTHS],
rp_inventory_defaults=cms_options[n_const.RP_INVENTORY_DEFAULTS],
@ -260,10 +271,3 @@ class OVNClientPlacementExtension(object):
device_mappings=bridge_mappings,
supported_vnic_types=self.supported_vnic_types,
client=self.placement_plugin._placement_client)
def get_chassis_config(self, chassis_name):
return self._chassis.get(chassis_name)
def add_chassis_config(self, chassis_name, config):
if config:
self._chassis[chassis_name] = config

View File

@ -80,7 +80,7 @@ class OVNClient(object):
# TODO(ralonsoh): handle the OVN client extensions with an ext. manager
self._qos_driver = qos_extension.OVNClientQosExtension(driver=self)
self._placement_extension = (
self.placement_extension = (
placement_extension.OVNClientPlacementExtension(self))
self._ovn_scheduler = l3_ovn_scheduler.get_scheduler()

View File

@ -1273,6 +1273,9 @@ 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()
def sync_hostname_and_physical_networks(self, ctx):
LOG.debug('OVN-SB Sync hostname and physical networks started')

View File

@ -66,7 +66,7 @@ class TestOVNClientQosExtension(base.TestOVNFunctionalBase):
super().setUp(maintenance_worker=maintenance_worker,
service_plugins=service_plugins)
self.ovn_client = self.mech_driver._ovn_client
self.placement_ext = self.ovn_client._placement_extension
self.placement_ext = self.ovn_client.placement_extension
self.mock_name2uuid = mock.patch.object(
self.placement_ext, 'name2uuid').start()
self.mock_send_batch = mock.patch.object(
@ -98,12 +98,15 @@ class TestOVNClientQosExtension(base.TestOVNFunctionalBase):
'Chassis', name, ('external_ids', external_ids)
).execute(check_error=True)
def _check_placement_cache(self, expected_chassis):
def _check_placement_config(self, expected_chassis):
current_chassis = None
def check_chassis():
nonlocal current_chassis
current_chassis = self.placement_ext.chassis
current_chassis = self.placement_ext.get_chassis_config()
current_chassis = {
chassis_name: placement_extension.dict_chassis_config(state)
for chassis_name, state in current_chassis.items()}
return current_chassis == expected_chassis
try:
@ -126,37 +129,37 @@ class TestOVNClientQosExtension(base.TestOVNFunctionalBase):
bandwidths='br-provider0:3000:4000',
inventory_defaults='allocation_ratio:3.0;min_unit:1',
hypervisors='br-provider0:host2')
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2})
self._check_placement_config({**self.CHASSIS1, **self.CHASSIS2})
self._update_chassis(
'chassis2',
bandwidths='br-provider0:5000:6000',
inventory_defaults='allocation_ratio:1.1;min_unit:1',
hypervisors='br-provider0:host2')
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2_B})
self._check_placement_config({**self.CHASSIS1, **self.CHASSIS2_B})
def test_read_initial_empty_config_and_update(self):
self.mock_name2uuid.return_value = {'host1': 'uuid1',
'host2': 'uuid2'}
self._create_chassis('host1', 'chassis1', physical_nets=['phys1'])
self._create_chassis('host2', 'chassis2', physical_nets=['phys2'])
self._check_placement_cache({**{'chassis1': self.EMPTY_CHASSIS},
**{'chassis2': self.EMPTY_CHASSIS}})
self._check_placement_config({**{'chassis1': self.EMPTY_CHASSIS},
**{'chassis2': self.EMPTY_CHASSIS}})
self._update_chassis(
'chassis1',
bandwidths='br-provider0:1000:2000',
inventory_defaults='allocation_ratio:1.0;min_unit:2',
hypervisors='br-provider0:host1')
self._check_placement_cache({**self.CHASSIS1,
**{'chassis2': self.EMPTY_CHASSIS}})
self._check_placement_config({**self.CHASSIS1,
**{'chassis2': self.EMPTY_CHASSIS}})
self._update_chassis(
'chassis2',
bandwidths='br-provider0:3000:4000',
inventory_defaults='allocation_ratio:3.0;min_unit:1',
hypervisors='br-provider0:host2')
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2})
self._check_placement_config({**self.CHASSIS1, **self.CHASSIS2})
def test_update_twice(self):
self.mock_name2uuid.return_value = {'host1': 'uuid1',
@ -167,19 +170,19 @@ class TestOVNClientQosExtension(base.TestOVNFunctionalBase):
inventory_defaults='allocation_ratio:1.0;min_unit:2',
hypervisors='br-provider0:host1')
self._create_chassis('host2', 'chassis2', physical_nets=['phys2'])
self._check_placement_cache({**self.CHASSIS1,
**{'chassis2': self.EMPTY_CHASSIS}})
self._check_placement_config({**self.CHASSIS1,
**{'chassis2': self.EMPTY_CHASSIS}})
self._update_chassis(
'chassis2',
bandwidths='br-provider0:3000:4000',
inventory_defaults='allocation_ratio:3.0;min_unit:1',
hypervisors='br-provider0:host2')
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2})
self._check_placement_config({**self.CHASSIS1, **self.CHASSIS2})
self._update_chassis(
'chassis2',
bandwidths='br-provider0:5000:6000',
inventory_defaults='allocation_ratio:1.1;min_unit:1',
hypervisors='br-provider0:host2')
self._check_placement_cache({**self.CHASSIS1, **self.CHASSIS2_B})
self._check_placement_config({**self.CHASSIS1, **self.CHASSIS2_B})

View File

@ -48,9 +48,14 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
{'name': 'compute2', 'uuid': 'uuid2'}]
}
def test__read_initial_chassis_config(self):
def test_read_initial_chassis_config(self):
# Add two public networks, a RP per bridge and the correlation between
# the hypervisors and the bridges.
def _check_expected_config(init_conf, expected):
res = {chassis_name: p_extension.dict_chassis_config(state) for
chassis_name, state in init_conf.items()}
self.assertEqual(expected, res)
chassis = fakes.FakeChassis.create(
bridge_mappings=['public1:br-ext1', 'public2:br-ext2'],
rp_bandwidths=['br-ext1:1000:2000', 'br-ext2:3000:4000'],
@ -58,7 +63,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {
'br-ext1': {'egress': 1000, 'ingress': 2000},
@ -69,7 +74,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)
# Add an extra bridge mapping that is discarded because it is not in
# the hypervisors list (wrong configuration).
@ -81,7 +86,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {
'br-ext1': {'egress': 1000, 'ingress': 2000},
@ -92,7 +97,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)
# Add an unknown bridge, not present in the bridge mappings, that is
# discarded (wrong configuration).
@ -103,7 +108,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {
'br-ext1': {'egress': 1000, 'ingress': 2000}},
@ -113,7 +118,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)
# Add an unknown hypervisor, that is not present in the Placement list
# of resource providers. This hypervisor is discarded (wrong
@ -126,7 +131,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute3'])
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {
'br-ext1': {'egress': 1000, 'ingress': 2000}},
@ -135,7 +140,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
ovn_const.RP_HYPERVISORS: {
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'}}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)
# Missing bridge mapping for br-ext2, the RP for this bridge will be
# discarded.
@ -146,7 +151,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {
'br-ext1': {'egress': 1000, 'ingress': 2000}},
@ -156,7 +161,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)
# No bridge mappings, no RP BW inventories.
chassis = fakes.FakeChassis.create(
@ -166,7 +171,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors=['br-ext1:compute1', 'br-ext2:compute2'])
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {},
n_const.RP_INVENTORY_DEFAULTS: {
@ -175,7 +180,7 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
'br-ext1': {'name': 'compute1', 'uuid': 'uuid1'},
'br-ext2': {'name': 'compute2', 'uuid': 'uuid2'}}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)
# No bridge mappings nor hypervisors, no RP BW inventories.
chassis = fakes.FakeChassis.create(
@ -185,14 +190,14 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors=None)
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {},
n_const.RP_INVENTORY_DEFAULTS: {
'allocation_ratio': 1.0, 'min_unit': 5},
ovn_const.RP_HYPERVISORS: {}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)
# If no RP BW information (any deployment not using it), OVN Placement
# extension won't break anything (sorry for LP#1936983, that was me).
@ -203,13 +208,13 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors=None)
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {},
n_const.RP_INVENTORY_DEFAULTS: {},
ovn_const.RP_HYPERVISORS: {}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)
# Test wrongly defined parameters. E.g.:
# external_ids: {ovn-cms-options={resource_provider_bandwidths=, ...}}
@ -220,10 +225,10 @@ class TestOVNClientPlacementExtension(test_plugin.Ml2PluginV2TestCase):
rp_hypervisors='')
self.plugin_driver._sb_idl.chassis_list.return_value.execute.\
return_value = [chassis]
init_conf = self.placement_driver._read_initial_chassis_config()
init_conf = self.placement_driver.read_initial_chassis_config()
expected = {chassis.name: {
n_const.RP_BANDWIDTHS: {},
n_const.RP_INVENTORY_DEFAULTS: {},
ovn_const.RP_HYPERVISORS: {}
}}
self.assertEqual(expected, init_conf)
_check_expected_config(init_conf, expected)