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

This commit is contained in:
Zuul 2022-02-23 22:21:55 +00:00 committed by Gerrit Code Review
commit 5c47957e89
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)