Since OVN 20.06, config is stored in "Chassis.other_config"
Since OVN 20.06 [1], the OVN configuration is stored in "Chassis.other_config". Since OVN 22.09, the "Chassis" configuration stored in "Chassis.other_config" will not be replicated to "Chassis.external_ids". The ML2/OVN plugin tries to retrieve the "Chassis" configuration from the "other_config" field first; if this field does not exist (in OVN versions before 20.06), the plugin will use "external_ids" field instead. Neutron will be compatible with the different OVN versions (with and without "other_config" field). [1]74d90c2223
[2]51309429cc
NOTE: this patch is similar to [1], but in this case neutron keeps compatibility with the different OVN versions (with and without "other_config" field). Since [2], the Neutron CI has a new job that uses the OVN/OVS packages distributed by the operating system installed by the CI (in this case, Ubuntu 20.04 and OVN 20.03). [1]https://review.opendev.org/c/openstack/neutron/+/859642 [2]https://review.opendev.org/c/openstack/neutron/+/860636 Conflicts: neutron/plugins/ml2/drivers/ovn/agent/neutron_agent.py neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/placement.py neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_placement.py neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py neutron/tests/unit/fake_resources.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl_ovn.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py Closes-Bug: #1990229 Change-Id: I54c8fd4d065ae537f396408df16832b158ee8998 (cherry picked from commit536498a29a
) (cherry picked from commit8a4c62d094
)
This commit is contained in:
parent
427af5305c
commit
a007da1e9a
@ -594,7 +594,7 @@ def compute_address_pairs_diff(ovn_port, neutron_port):
|
||||
|
||||
def get_ovn_cms_options(chassis):
|
||||
"""Return the list of CMS options in a Chassis."""
|
||||
return [opt.strip() for opt in chassis.external_ids.get(
|
||||
return [opt.strip() for opt in get_ovn_chassis_other_config(chassis).get(
|
||||
constants.OVN_CMS_OPTIONS, '').split(',')]
|
||||
|
||||
|
||||
@ -784,3 +784,11 @@ def create_neutron_pg_drop():
|
||||
}]
|
||||
|
||||
OvsdbClientTransactCommand.run(command)
|
||||
|
||||
|
||||
def get_ovn_chassis_other_config(chassis):
|
||||
# NOTE(ralonsoh): LP#1990229 to be removed when min OVN version is 22.09
|
||||
try:
|
||||
return chassis.other_config
|
||||
except AttributeError:
|
||||
return chassis.external_ids
|
||||
|
@ -90,7 +90,8 @@ class NeutronAgent(abc.ABC):
|
||||
'configurations': {
|
||||
'chassis_name': self.chassis.name,
|
||||
'bridge-mappings':
|
||||
self.chassis.external_ids.get('ovn-bridge-mappings', '')},
|
||||
ovn_utils.get_ovn_chassis_other_config(self.chassis).get(
|
||||
'ovn-bridge-mappings', '')},
|
||||
'start_flag': True,
|
||||
'agent_type': self.agent_type,
|
||||
'id': self.agent_id,
|
||||
@ -142,9 +143,9 @@ class ControllerAgent(NeutronAgent):
|
||||
|
||||
@staticmethod # it is by default, but this makes pep8 happy
|
||||
def __new__(cls, chassis_private, driver, updated_at=None):
|
||||
external_ids = cls.chassis_from_private(chassis_private).external_ids
|
||||
if ('enable-chassis-as-gw' in
|
||||
external_ids.get('ovn-cms-options', [])):
|
||||
_chassis = cls.chassis_from_private(chassis_private)
|
||||
other_config = ovn_utils.get_ovn_chassis_other_config(_chassis)
|
||||
if 'enable-chassis-as-gw' in other_config.get('ovn-cms-options', []):
|
||||
cls = ControllerGatewayAgent
|
||||
return super().__new__(cls)
|
||||
|
||||
@ -167,8 +168,9 @@ class ControllerAgent(NeutronAgent):
|
||||
|
||||
def update(self, chassis_private, updated_at=None, clear_down=False):
|
||||
super().update(chassis_private, updated_at, clear_down)
|
||||
external_ids = self.chassis_from_private(chassis_private).external_ids
|
||||
if 'enable-chassis-as-gw' in external_ids.get('ovn-cms-options', []):
|
||||
_chassis = self.chassis_from_private(chassis_private)
|
||||
other_config = ovn_utils.get_ovn_chassis_other_config(_chassis)
|
||||
if 'enable-chassis-as-gw' in other_config.get('ovn-cms-options', []):
|
||||
self.__class__ = ControllerGatewayAgent
|
||||
|
||||
|
||||
@ -177,9 +179,10 @@ class ControllerGatewayAgent(ControllerAgent):
|
||||
|
||||
def update(self, chassis_private, updated_at=None, clear_down=False):
|
||||
super().update(chassis_private, updated_at, clear_down)
|
||||
external_ids = self.chassis_from_private(chassis_private).external_ids
|
||||
_chassis = self.chassis_from_private(chassis_private)
|
||||
other_config = ovn_utils.get_ovn_chassis_other_config(_chassis)
|
||||
if ('enable-chassis-as-gw' not in
|
||||
external_ids.get('ovn-cms-options', [])):
|
||||
other_config.get('ovn-cms-options', [])):
|
||||
self.__class__ = ControllerAgent
|
||||
|
||||
|
||||
|
@ -181,7 +181,8 @@ class OVNMechanismDriver(api.MechanismDriver):
|
||||
def get_supported_vif_types(self):
|
||||
vif_types = set()
|
||||
for ch in self.sb_ovn.chassis_list().execute(check_error=True):
|
||||
dp_type = ch.external_ids.get('datapath-type', '')
|
||||
other_config = ovn_utils.get_ovn_chassis_other_config(ch)
|
||||
dp_type = other_config.get('datapath-type', '')
|
||||
if dp_type == ovn_const.CHASSIS_DATAPATH_NETDEV:
|
||||
vif_types.add(portbindings.VIF_TYPE_VHOST_USER)
|
||||
else:
|
||||
|
@ -0,0 +1,274 @@
|
||||
# Copyright 2021 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import itertools
|
||||
|
||||
from keystoneauth1 import exceptions as ks_exc
|
||||
from neutron_lib import constants as n_const
|
||||
from neutron_lib.placement import constants as placement_constants
|
||||
from neutron_lib.placement import utils as placement_utils
|
||||
from neutron_lib.plugins import constants as plugins_constants
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.utils import helpers
|
||||
from oslo_log import log as logging
|
||||
from ovsdbapp.backend.ovs_idl import event as row_event
|
||||
|
||||
from neutron.agent.common import placement_report
|
||||
from neutron.common.ovn import constants as ovn_const
|
||||
from neutron.common.ovn import utils as ovn_utils
|
||||
from neutron.common import utils as common_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _parse_ovn_cms_options(chassis):
|
||||
cms_options = ovn_utils.get_ovn_cms_options(chassis)
|
||||
return {n_const.RP_BANDWIDTHS: _parse_bandwidths(cms_options),
|
||||
n_const.RP_INVENTORY_DEFAULTS: _parse_inventory_defaults(
|
||||
cms_options),
|
||||
ovn_const.RP_HYPERVISORS: _parse_hypervisors(cms_options)}
|
||||
|
||||
|
||||
def _parse_bridge_mappings(chassis):
|
||||
other_config = ovn_utils.get_ovn_chassis_other_config(chassis)
|
||||
bridge_mappings = other_config.get('ovn-bridge-mappings', '')
|
||||
bridge_mappings = helpers.parse_mappings(bridge_mappings.split(','),
|
||||
unique_values=False)
|
||||
return {k: [v] for k, v in bridge_mappings.items()}
|
||||
|
||||
|
||||
def _parse_placement_option(option_name, cms_options):
|
||||
for cms_option in (cms_option for cms_option in cms_options if
|
||||
option_name in cms_option):
|
||||
try:
|
||||
return cms_option.split('=')[1]
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
|
||||
def _parse_bandwidths(cms_options):
|
||||
bw_values = _parse_placement_option(n_const.RP_BANDWIDTHS, cms_options)
|
||||
if not bw_values:
|
||||
return {}
|
||||
|
||||
return placement_utils.parse_rp_bandwidths(bw_values.split(';'))
|
||||
|
||||
|
||||
def _parse_inventory_defaults(cms_options):
|
||||
inv_defaults = _parse_placement_option(n_const.RP_INVENTORY_DEFAULTS,
|
||||
cms_options)
|
||||
if not inv_defaults:
|
||||
return {}
|
||||
|
||||
inventory = {}
|
||||
for inv_default in inv_defaults.split(';'):
|
||||
for key in placement_constants.INVENTORY_OPTIONS:
|
||||
if key in inv_default:
|
||||
inventory[key] = inv_default.split(':')[1]
|
||||
return placement_utils.parse_rp_inventory_defaults(inventory)
|
||||
|
||||
|
||||
def _parse_hypervisors(cms_options):
|
||||
hyperv = _parse_placement_option(ovn_const.RP_HYPERVISORS, cms_options)
|
||||
if not hyperv:
|
||||
return {}
|
||||
|
||||
return helpers.parse_mappings(hyperv.split(';'), unique_values=False)
|
||||
|
||||
|
||||
def _send_deferred_batch(state):
|
||||
if not state:
|
||||
return
|
||||
|
||||
deferred_batch = state.deferred_sync()
|
||||
for deferred in deferred_batch:
|
||||
try:
|
||||
LOG.debug('Placement client: %s', str(deferred))
|
||||
deferred.execute()
|
||||
except Exception:
|
||||
LOG.exception('Placement client call failed: %s', str(deferred))
|
||||
|
||||
|
||||
def dict_chassis_config(state):
|
||||
if state:
|
||||
return {n_const.RP_BANDWIDTHS: state._rp_bandwidths,
|
||||
n_const.RP_INVENTORY_DEFAULTS: state._rp_inventory_defaults,
|
||||
ovn_const.RP_HYPERVISORS: state._hypervisor_rps}
|
||||
|
||||
|
||||
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
|
||||
# NOTE(ralonsoh): BW resource provider information is stored in
|
||||
# "Chassis", not "Chassis_Private".
|
||||
table = 'Chassis'
|
||||
events = (self.ROW_CREATE, self.ROW_UPDATE)
|
||||
super().__init__(events, table, None)
|
||||
self.event_name = 'ChassisBandwidthConfigEvent'
|
||||
|
||||
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)
|
||||
ch_config = dict_chassis_config(state)
|
||||
LOG.debug('OVN chassis %(chassis)s Placement configuration modified: '
|
||||
'%(config)s', {'chassis': row.name, 'config': ch_config})
|
||||
|
||||
|
||||
@common_utils.SingletonDecorator
|
||||
class OVNClientPlacementExtension(object):
|
||||
"""OVN client Placement API extension"""
|
||||
|
||||
def __init__(self, driver):
|
||||
LOG.info('Starting OVNClientPlacementExtension')
|
||||
super().__init__()
|
||||
self._config_event = None
|
||||
self._reset(driver)
|
||||
|
||||
def _reset(self, driver):
|
||||
"""Reset the interval members values
|
||||
This class is a singleton. Once initialized, any other new instance
|
||||
will return the same object reference with the same member values.
|
||||
This method is used to reset all of them as when the class is initially
|
||||
instantiated if needed.
|
||||
"""
|
||||
self._driver = driver
|
||||
self._placement_plugin = None
|
||||
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):
|
||||
if self._placement_plugin is None:
|
||||
self._placement_plugin = directory.get_plugin(
|
||||
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:
|
||||
self._plugin = self._driver._plugin
|
||||
return self._plugin
|
||||
|
||||
@property
|
||||
def ovn_mech_driver(self):
|
||||
if self._ovn_mech_driver is None:
|
||||
self._ovn_mech_driver = (
|
||||
self.plugin.mechanism_manager.mech_drivers['ovn'].obj)
|
||||
return self._ovn_mech_driver
|
||||
|
||||
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)
|
||||
if state:
|
||||
chassis[ch.name] = state
|
||||
|
||||
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):
|
||||
try:
|
||||
rps = self.placement_plugin._placement_client.\
|
||||
list_resource_providers(name=name)['resource_providers']
|
||||
except (ks_exc.HttpError, ks_exc.ClientException):
|
||||
LOG.warning('Error connecting to Placement API.')
|
||||
return {}
|
||||
|
||||
_name2uuid = {rp['name']: rp['uuid'] for rp in rps}
|
||||
LOG.info('Placement information about resource providers '
|
||||
'(name:uuid):%s ', _name2uuid)
|
||||
return _name2uuid
|
||||
|
||||
def build_placement_state(self, chassis, name2uuid):
|
||||
bridge_mappings = _parse_bridge_mappings(chassis)
|
||||
cms_options = _parse_ovn_cms_options(chassis)
|
||||
LOG.debug('Building placement options for chassis %s: %s',
|
||||
chassis.name, cms_options)
|
||||
hypervisor_rps = {}
|
||||
for device, hyperv in cms_options[ovn_const.RP_HYPERVISORS].items():
|
||||
try:
|
||||
hypervisor_rps[device] = {'name': hyperv,
|
||||
'uuid': name2uuid[hyperv]}
|
||||
except (KeyError, AttributeError):
|
||||
continue
|
||||
|
||||
bridges = set(itertools.chain(*bridge_mappings.values()))
|
||||
# Remove "cms_options[RP_BANDWIDTHS]" not present in "hypervisor_rps"
|
||||
# and "bridge_mappings". If we don't have a way to match the RP bridge
|
||||
# with a host ("hypervisor_rps") or a way to match the RP bridge with
|
||||
# an external network ("bridge_mappings"), this value is irrelevant.
|
||||
rp_bw = cms_options[n_const.RP_BANDWIDTHS]
|
||||
if rp_bw:
|
||||
cms_options[n_const.RP_BANDWIDTHS] = {
|
||||
device: bw for device, bw in rp_bw.items() if
|
||||
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. 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],
|
||||
rp_pkt_processing={},
|
||||
rp_pkt_processing_inventory_defaults=None,
|
||||
driver_uuid_namespace=self.uuid_ns,
|
||||
agent_type=ovn_const.OVN_CONTROLLER_AGENT,
|
||||
hypervisor_rps=hypervisor_rps,
|
||||
device_mappings=bridge_mappings,
|
||||
supported_vnic_types=self.supported_vnic_types,
|
||||
client=self.placement_plugin._placement_client)
|
@ -836,7 +836,8 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
|
||||
return cls(conn)
|
||||
|
||||
def _get_chassis_physnets(self, chassis):
|
||||
bridge_mappings = chassis.external_ids.get('ovn-bridge-mappings', '')
|
||||
other_config = utils.get_ovn_chassis_other_config(chassis)
|
||||
bridge_mappings = other_config.get('ovn-bridge-mappings', '')
|
||||
mapping_dict = helpers.parse_mappings(bridge_mappings.split(','))
|
||||
return list(mapping_dict.keys())
|
||||
|
||||
@ -854,7 +855,8 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
|
||||
return [ch.name if name_only else ch
|
||||
for ch in self.chassis_list().execute(check_error=True)
|
||||
if ovn_const.CMS_OPT_CHASSIS_AS_GW in
|
||||
ch.external_ids.get(ovn_const.OVN_CMS_OPTIONS, '').split(',')]
|
||||
utils.get_ovn_chassis_other_config(ch).get(
|
||||
ovn_const.OVN_CMS_OPTIONS, '').split(',')]
|
||||
|
||||
def get_chassis_and_physnets(self):
|
||||
chassis_info_dict = {}
|
||||
@ -874,8 +876,9 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
|
||||
except StopIteration:
|
||||
msg = _('Chassis with hostname %s does not exist') % hostname
|
||||
raise RuntimeError(msg)
|
||||
return (chassis.external_ids.get('datapath-type', ''),
|
||||
chassis.external_ids.get('iface-types', ''),
|
||||
other_config = utils.get_ovn_chassis_other_config(chassis)
|
||||
return (other_config.get('datapath-type', ''),
|
||||
other_config.get('iface-types', ''),
|
||||
self._get_chassis_physnets(chassis))
|
||||
|
||||
def get_metadata_port_network(self, network):
|
||||
|
@ -172,12 +172,21 @@ class ChassisEvent(row_event.RowEvent):
|
||||
def match_fn(self, event, row, old):
|
||||
if event != self.ROW_UPDATE:
|
||||
return True
|
||||
# NOTE(lucasgomes): If the external_ids column wasn't updated
|
||||
# (meaning, Chassis "gateway" status didn't change) just returns
|
||||
if not hasattr(old, 'external_ids') and event == self.ROW_UPDATE:
|
||||
|
||||
# NOTE(ralonsoh): LP#1990229 to be removed when min OVN version is
|
||||
# 22.09
|
||||
other_config = ('other_config' if hasattr(row, 'other_config') else
|
||||
'external_ids')
|
||||
# NOTE(lucasgomes): If the other_config/external_ids column wasn't
|
||||
# updated (meaning, Chassis "gateway" status didn't change) just
|
||||
# returns
|
||||
if not hasattr(old, other_config) and event == self.ROW_UPDATE:
|
||||
return False
|
||||
if (old.external_ids.get('ovn-bridge-mappings') !=
|
||||
row.external_ids.get('ovn-bridge-mappings')):
|
||||
old_br_mappings = utils.get_ovn_chassis_other_config(old).get(
|
||||
'ovn-bridge-mappings')
|
||||
new_br_mappings = utils.get_ovn_chassis_other_config(row).get(
|
||||
'ovn-bridge-mappings')
|
||||
if old_br_mappings != new_br_mappings:
|
||||
return True
|
||||
# Check if either the Gateway status or Availability Zones has
|
||||
# changed in the Chassis
|
||||
@ -192,8 +201,9 @@ class ChassisEvent(row_event.RowEvent):
|
||||
def run(self, event, row, old):
|
||||
host = row.hostname
|
||||
phy_nets = []
|
||||
new_other_config = utils.get_ovn_chassis_other_config(row)
|
||||
if event != self.ROW_DELETE:
|
||||
bridge_mappings = row.external_ids.get('ovn-bridge-mappings', '')
|
||||
bridge_mappings = new_other_config.get('ovn-bridge-mappings', '')
|
||||
mapping_dict = helpers.parse_mappings(bridge_mappings.split(','))
|
||||
phy_nets = list(mapping_dict)
|
||||
|
||||
@ -208,9 +218,10 @@ class ChassisEvent(row_event.RowEvent):
|
||||
if event == self.ROW_DELETE:
|
||||
kwargs['event_from_chassis'] = row.name
|
||||
elif event == self.ROW_UPDATE:
|
||||
old_mappings = old.external_ids.get('ovn-bridge-mappings',
|
||||
old_other_config = utils.get_ovn_chassis_other_config(old)
|
||||
old_mappings = old_other_config.get('ovn-bridge-mappings',
|
||||
set()) or set()
|
||||
new_mappings = row.external_ids.get('ovn-bridge-mappings',
|
||||
new_mappings = new_other_config.get('ovn-bridge-mappings',
|
||||
set()) or set()
|
||||
if old_mappings:
|
||||
old_mappings = set(old_mappings.split(','))
|
||||
@ -339,11 +350,17 @@ class ChassisAgentTypeChangeEvent(ChassisEvent):
|
||||
events = (BaseEvent.ROW_UPDATE,)
|
||||
|
||||
def match_fn(self, event, row, old=None):
|
||||
if not getattr(old, 'external_ids', False):
|
||||
# NOTE(ralonsoh): LP#1990229 to be removed when min OVN version is
|
||||
# 22.09
|
||||
other_config = ('other_config' if hasattr(row, 'other_config') else
|
||||
'external_ids')
|
||||
if not getattr(old, other_config, False):
|
||||
return False
|
||||
agent_type_change = n_agent.NeutronAgent.chassis_from_private(
|
||||
row).external_ids.get('ovn-cms-options', []) != (
|
||||
old.external_ids.get('ovn-cms-options', []))
|
||||
chassis = n_agent.NeutronAgent.chassis_from_private(row)
|
||||
new_other_config = utils.get_ovn_chassis_other_config(chassis)
|
||||
old_other_config = utils.get_ovn_chassis_other_config(old)
|
||||
agent_type_change = new_other_config.get('ovn-cms-options', []) != (
|
||||
old_other_config.get('ovn-cms-options', []))
|
||||
return agent_type_change
|
||||
|
||||
def run(self, event, row, old):
|
||||
|
@ -414,7 +414,8 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
||||
self._start_ovn_northd()
|
||||
|
||||
def add_fake_chassis(self, host, physical_nets=None, external_ids=None,
|
||||
name=None, enable_chassis_as_gw=False):
|
||||
name=None, enable_chassis_as_gw=False,
|
||||
other_config=None):
|
||||
def append_cms_options(ext_ids, value):
|
||||
if 'ovn-cms-options' not in ext_ids:
|
||||
ext_ids['ovn-cms-options'] = value
|
||||
@ -423,14 +424,15 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
||||
|
||||
physical_nets = physical_nets or []
|
||||
external_ids = external_ids or {}
|
||||
other_config = other_config or {}
|
||||
if enable_chassis_as_gw:
|
||||
append_cms_options(external_ids, 'enable-chassis-as-gw')
|
||||
append_cms_options(other_config, 'enable-chassis-as-gw')
|
||||
|
||||
bridge_mapping = ",".join(["%s:br-provider%s" % (phys_net, i)
|
||||
for i, phys_net in enumerate(physical_nets)])
|
||||
if name is None:
|
||||
name = uuidutils.generate_uuid()
|
||||
external_ids['ovn-bridge-mappings'] = bridge_mapping
|
||||
other_config['ovn-bridge-mappings'] = bridge_mapping
|
||||
# We'll be using different IP addresses every time for the Encap of
|
||||
# the fake chassis as the SB schema doesn't allow to have two entries
|
||||
# with same (ip,type) pairs as of OVS 2.11. This shouldn't have any
|
||||
@ -441,7 +443,8 @@ class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
|
||||
self._counter += 1
|
||||
chassis = self.sb_api.chassis_add(
|
||||
name, ['geneve'], '172.24.4.%d' % self._counter,
|
||||
external_ids=external_ids, hostname=host).execute(check_error=True)
|
||||
external_ids=external_ids, hostname=host,
|
||||
other_config=other_config).execute(check_error=True)
|
||||
if self.sb_api.is_table_present('Chassis_Private'):
|
||||
nb_cfg_timestamp = timeutils.utcnow_ts() * 1000
|
||||
self.sb_api.db_create(
|
||||
|
@ -0,0 +1,188 @@
|
||||
# Copyright 2021 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from neutron_lib import constants as n_const
|
||||
from neutron_lib.plugins import constants as plugins_constants
|
||||
|
||||
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
|
||||
|
||||
|
||||
class TestOVNClientQosExtension(base.TestOVNFunctionalBase):
|
||||
|
||||
EMPTY_CHASSIS = {n_const.RP_BANDWIDTHS: {},
|
||||
n_const.RP_INVENTORY_DEFAULTS: {},
|
||||
ovn_const.RP_HYPERVISORS: {}}
|
||||
|
||||
RP_BANDWIDTHS_1 = {'br-provider0': {'egress': 1000, 'ingress': 2000}}
|
||||
RP_INVENTORY_DEFAULTS_1 = {'allocation_ratio': 1.0, 'min_unit': 2}
|
||||
RP_HYPERVISORS_1 = {'br-provider0': {'name': 'host1', 'uuid': 'uuid1'}}
|
||||
CHASSIS1 = {
|
||||
'chassis1': {
|
||||
n_const.RP_BANDWIDTHS: RP_BANDWIDTHS_1,
|
||||
n_const.RP_INVENTORY_DEFAULTS: RP_INVENTORY_DEFAULTS_1,
|
||||
ovn_const.RP_HYPERVISORS: RP_HYPERVISORS_1
|
||||
}
|
||||
}
|
||||
RP_BANDWIDTHS_2 = {'br-provider0': {'egress': 3000, 'ingress': 4000}}
|
||||
RP_INVENTORY_DEFAULTS_2 = {'allocation_ratio': 3.0, 'min_unit': 1}
|
||||
RP_HYPERVISORS_2 = {'br-provider0': {'name': 'host2', 'uuid': 'uuid2'}}
|
||||
CHASSIS2 = {
|
||||
'chassis2': {
|
||||
n_const.RP_BANDWIDTHS: RP_BANDWIDTHS_2,
|
||||
n_const.RP_INVENTORY_DEFAULTS: RP_INVENTORY_DEFAULTS_2,
|
||||
ovn_const.RP_HYPERVISORS: RP_HYPERVISORS_2
|
||||
}
|
||||
}
|
||||
|
||||
RP_BANDWIDTHS_3 = {'br-provider0': {'egress': 5000, 'ingress': 6000}}
|
||||
RP_INVENTORY_DEFAULTS_3 = {'allocation_ratio': 1.1, 'min_unit': 1}
|
||||
CHASSIS2_B = {
|
||||
'chassis2': {
|
||||
n_const.RP_BANDWIDTHS: RP_BANDWIDTHS_3,
|
||||
n_const.RP_INVENTORY_DEFAULTS: RP_INVENTORY_DEFAULTS_3,
|
||||
ovn_const.RP_HYPERVISORS: RP_HYPERVISORS_2
|
||||
}
|
||||
}
|
||||
|
||||
def setUp(self, maintenance_worker=False, service_plugins=None):
|
||||
service_plugins = {plugins_constants.PLACEMENT_REPORT: 'placement'}
|
||||
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.mock_name2uuid = mock.patch.object(
|
||||
self.placement_ext, 'name2uuid').start()
|
||||
self.mock_send_batch = mock.patch.object(
|
||||
placement_extension, '_send_deferred_batch').start()
|
||||
|
||||
def _build_other_config(self, bandwidths, inventory_defaults, hypervisors):
|
||||
options = []
|
||||
if bandwidths:
|
||||
options.append(n_const.RP_BANDWIDTHS + '=' + bandwidths)
|
||||
if inventory_defaults:
|
||||
options.append(n_const.RP_INVENTORY_DEFAULTS + '=' +
|
||||
inventory_defaults)
|
||||
if hypervisors:
|
||||
options.append(ovn_const.RP_HYPERVISORS + '=' + hypervisors)
|
||||
return {'ovn-cms-options': ','.join(options)}
|
||||
|
||||
def _create_chassis(self, host, name, physical_nets=None, bandwidths=None,
|
||||
inventory_defaults=None, hypervisors=None):
|
||||
other_config = self._build_other_config(bandwidths, inventory_defaults,
|
||||
hypervisors)
|
||||
self.add_fake_chassis(host, physical_nets=physical_nets,
|
||||
other_config=other_config, name=name)
|
||||
|
||||
def _update_chassis(self, name, bandwidths=None, inventory_defaults=None,
|
||||
hypervisors=None):
|
||||
other_config = self._build_other_config(bandwidths, inventory_defaults,
|
||||
hypervisors)
|
||||
self.sb_api.db_set(
|
||||
'Chassis', name, ('other_config', other_config)
|
||||
).execute(check_error=True)
|
||||
|
||||
def _check_placement_config(self, expected_chassis):
|
||||
current_chassis = None
|
||||
|
||||
def check_chassis():
|
||||
nonlocal current_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:
|
||||
common_utils.wait_until_true(check_chassis, timeout=5)
|
||||
except common_utils.WaitTimeout:
|
||||
self.fail('OVN client Placement extension cache does not have '
|
||||
'the expected chassis information.\nExpected: %s.\n'
|
||||
'Actual: %s' % (expected_chassis, current_chassis))
|
||||
|
||||
def test_read_initial_config_and_update(self):
|
||||
self.mock_name2uuid.return_value = {'host1': 'uuid1',
|
||||
'host2': 'uuid2'}
|
||||
self._create_chassis(
|
||||
'host1', 'chassis1', physical_nets=['phys1'],
|
||||
bandwidths='br-provider0:1000:2000',
|
||||
inventory_defaults='allocation_ratio:1.0;min_unit:2',
|
||||
hypervisors='br-provider0:host1')
|
||||
self._create_chassis(
|
||||
'host2', 'chassis2', physical_nets=['phys2'],
|
||||
bandwidths='br-provider0:3000:4000',
|
||||
inventory_defaults='allocation_ratio:3.0;min_unit:1',
|
||||
hypervisors='br-provider0:host2')
|
||||
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_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_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_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_config({**self.CHASSIS1, **self.CHASSIS2})
|
||||
|
||||
def test_update_twice(self):
|
||||
self.mock_name2uuid.return_value = {'host1': 'uuid1',
|
||||
'host2': 'uuid2'}
|
||||
self._create_chassis(
|
||||
'host1', 'chassis1', physical_nets=['phys1'],
|
||||
bandwidths='br-provider0:1000:2000',
|
||||
inventory_defaults='allocation_ratio:1.0;min_unit:2',
|
||||
hypervisors='br-provider0:host1')
|
||||
self._create_chassis('host2', 'chassis2', physical_nets=['phys2'])
|
||||
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_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_config({**self.CHASSIS1, **self.CHASSIS2_B})
|
@ -52,11 +52,11 @@ class TestSbApi(BaseOvnIdlTest):
|
||||
super(TestSbApi, self).setUp()
|
||||
self.data = {
|
||||
'chassis': [
|
||||
{'external_ids': {'ovn-bridge-mappings':
|
||||
{'other_config': {'ovn-bridge-mappings':
|
||||
'public:br-ex,private:br-0'}},
|
||||
{'external_ids': {'ovn-bridge-mappings':
|
||||
{'other_config': {'ovn-bridge-mappings':
|
||||
'public:br-ex,public2:br-ex2'}},
|
||||
{'external_ids': {'ovn-bridge-mappings':
|
||||
{'other_config': {'ovn-bridge-mappings':
|
||||
'public:br-ex'}},
|
||||
]
|
||||
}
|
||||
@ -70,7 +70,7 @@ class TestSbApi(BaseOvnIdlTest):
|
||||
txn.add(self.api.chassis_add(
|
||||
chassis['name'], ['geneve'], chassis['hostname'],
|
||||
hostname=chassis['hostname'],
|
||||
external_ids=chassis['external_ids']))
|
||||
other_config=chassis['other_config']))
|
||||
|
||||
def test_get_chassis_hostname_and_physnets(self):
|
||||
mapping = self.api.get_chassis_hostname_and_physnets()
|
||||
@ -104,7 +104,7 @@ class TestSbApi(BaseOvnIdlTest):
|
||||
def test_multiple_physnets_in_one_bridge(self):
|
||||
self.data = {
|
||||
'chassis': [
|
||||
{'external_ids': {'ovn-bridge-mappings': 'p1:br-ex,p2:br-ex'}}
|
||||
{'other_config': {'ovn-bridge-mappings': 'p1:br-ex,p2:br-ex'}}
|
||||
]
|
||||
}
|
||||
self.load_test_data()
|
||||
|
@ -437,9 +437,8 @@ class TestAgentMonitor(base.TestOVNFunctionalBase):
|
||||
chassis_name, self.mech_driver.agent_chassis_table)
|
||||
self.mech_driver.sb_ovn.idl.notify_handler.watch_event(row_event)
|
||||
self.chassis_name = self.add_fake_chassis(
|
||||
self.FAKE_CHASSIS_HOST,
|
||||
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'},
|
||||
name=chassis_name)
|
||||
self.FAKE_CHASSIS_HOST, name=chassis_name,
|
||||
enable_chassis_as_gw=True)
|
||||
self.assertTrue(row_event.wait())
|
||||
n_utils.wait_until_true(
|
||||
lambda: len(list(neutron_agent.AgentCache())) == 1)
|
||||
@ -447,11 +446,11 @@ class TestAgentMonitor(base.TestOVNFunctionalBase):
|
||||
def test_agent_change_controller(self):
|
||||
self.assertEqual(neutron_agent.ControllerGatewayAgent,
|
||||
type(neutron_agent.AgentCache()[self.chassis_name]))
|
||||
self.sb_api.db_set('Chassis', self.chassis_name, ('external_ids',
|
||||
self.sb_api.db_set('Chassis', self.chassis_name, ('other_config',
|
||||
{'ovn-cms-options': ''})).execute(check_error=True)
|
||||
n_utils.wait_until_true(lambda:
|
||||
neutron_agent.AgentCache()[self.chassis_name].
|
||||
chassis.external_ids['ovn-cms-options'] == '')
|
||||
chassis.other_config['ovn-cms-options'] == '')
|
||||
self.assertEqual(neutron_agent.ControllerAgent,
|
||||
type(neutron_agent.AgentCache()[self.chassis_name]))
|
||||
|
||||
|
@ -59,12 +59,12 @@ class TestPortBinding(base.TestOVNFunctionalBase):
|
||||
self.add_fake_chassis(self.ovs_host)
|
||||
self.add_fake_chassis(
|
||||
self.dpdk_host,
|
||||
external_ids={'datapath-type': 'netdev',
|
||||
other_config={'datapath-type': 'netdev',
|
||||
'iface-types': 'dummy,dummy-internal,dpdkvhostuser'})
|
||||
|
||||
self.add_fake_chassis(
|
||||
self.invalid_dpdk_host,
|
||||
external_ids={'datapath-type': 'netdev',
|
||||
other_config={'datapath-type': 'netdev',
|
||||
'iface-types': 'dummy,dummy-internal,geneve,vxlan'})
|
||||
self.n1 = self._make_network(self.fmt, 'n1', True)
|
||||
res = self._create_subnet(self.fmt, self.n1['network']['id'],
|
||||
|
@ -129,7 +129,7 @@ class TestRouter(base.TestOVNFunctionalBase):
|
||||
# Test if chassis3 is selected as candidate or not.
|
||||
self.chassis3 = self.add_fake_chassis(
|
||||
'ovs-host3', physical_nets=['physnet1'],
|
||||
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'})
|
||||
other_config={'ovn-cms-options': 'enable-chassis-as-gw'})
|
||||
self._check_gateway_chassis_candidates([self.chassis3])
|
||||
|
||||
def test_gateway_chassis_with_cms_and_no_bridge_mappings(self):
|
||||
@ -137,7 +137,7 @@ class TestRouter(base.TestOVNFunctionalBase):
|
||||
# chassis3 is having enable-chassis-as-gw, but no bridge mappings.
|
||||
self.chassis3 = self.add_fake_chassis(
|
||||
'ovs-host3',
|
||||
external_ids={'ovn-cms-options': 'enable-chassis-as-gw'})
|
||||
other_config={'ovn-cms-options': 'enable-chassis-as-gw'})
|
||||
ovn_client = self.l3_plugin._ovn_client
|
||||
ext1 = self._create_ext_network(
|
||||
'ext1', 'vlan', 'physnet1', 1, "10.0.0.1", "10.0.0.0/24")
|
||||
@ -482,7 +482,7 @@ class TestRouter(base.TestOVNFunctionalBase):
|
||||
self.skipTest('L3 HA not supported')
|
||||
ovn_client = self.l3_plugin._ovn_client
|
||||
chassis4 = self.add_fake_chassis(
|
||||
'ovs-host4', physical_nets=['physnet4'], external_ids={
|
||||
'ovs-host4', physical_nets=['physnet4'], other_config={
|
||||
'ovn-cms-options': 'enable-chassis-as-gw'})
|
||||
ovn_client._ovn_scheduler = l3_sched.OVNGatewayLeastLoadedScheduler()
|
||||
ext1 = self._create_ext_network(
|
||||
@ -504,7 +504,7 @@ class TestRouter(base.TestOVNFunctionalBase):
|
||||
|
||||
# Add another chassis as a gateway chassis
|
||||
chassis5 = self.add_fake_chassis(
|
||||
'ovs-host5', physical_nets=['physnet4'], external_ids={
|
||||
'ovs-host5', physical_nets=['physnet4'], other_config={
|
||||
'ovn-cms-options': 'enable-chassis-as-gw'})
|
||||
# Add a node as compute node. Compute node wont be
|
||||
# used to schedule the router gateway ports therefore
|
||||
@ -534,8 +534,7 @@ class TestRouter(base.TestOVNFunctionalBase):
|
||||
chassis_list.append(
|
||||
self.add_fake_chassis(
|
||||
'ovs-host%s' % i, physical_nets=['physnet1'],
|
||||
external_ids={
|
||||
'ovn-cms-options': 'enable-chassis-as-gw'}))
|
||||
other_config={'ovn-cms-options': 'enable-chassis-as-gw'}))
|
||||
|
||||
ext1 = self._create_ext_network(
|
||||
'ext1', 'vlan', 'physnet1', 1, "10.0.0.1", "10.0.0.0/24")
|
||||
|
@ -61,33 +61,31 @@ class TestUtils(base.BaseTestCase):
|
||||
|
||||
def test_is_gateway_chassis(self):
|
||||
chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'external_ids': {'ovn-cms-options': 'enable-chassis-as-gw'}})
|
||||
'other_config': {'ovn-cms-options': 'enable-chassis-as-gw'}})
|
||||
non_gw_chassis_0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'external_ids': {'ovn-cms-options': ''}})
|
||||
non_gw_chassis_1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={})
|
||||
non_gw_chassis_2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'external_ids': {}})
|
||||
'other_config': {'ovn-cms-options': ''}})
|
||||
non_gw_chassis_1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'other_config': {}})
|
||||
|
||||
self.assertTrue(utils.is_gateway_chassis(chassis))
|
||||
self.assertFalse(utils.is_gateway_chassis(non_gw_chassis_0))
|
||||
self.assertFalse(utils.is_gateway_chassis(non_gw_chassis_1))
|
||||
self.assertFalse(utils.is_gateway_chassis(non_gw_chassis_2))
|
||||
|
||||
def test_get_chassis_availability_zones_no_azs(self):
|
||||
chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'external_ids': {'ovn-cms-options': 'enable-chassis-as-gw'}})
|
||||
'other_config': {'ovn-cms-options': 'enable-chassis-as-gw'}})
|
||||
self.assertEqual(set(), utils.get_chassis_availability_zones(chassis))
|
||||
|
||||
def test_get_chassis_availability_zones_one_az(self):
|
||||
chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'external_ids': {'ovn-cms-options':
|
||||
'other_config': {'ovn-cms-options':
|
||||
'enable-chassis-as-gw,availability-zones=az0'}})
|
||||
self.assertEqual(
|
||||
{'az0'}, utils.get_chassis_availability_zones(chassis))
|
||||
|
||||
def test_get_chassis_availability_zones_multiple_az(self):
|
||||
chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'external_ids': {
|
||||
'other_config': {
|
||||
'ovn-cms-options':
|
||||
'enable-chassis-as-gw,availability-zones=az0:az1 :az2:: :'}})
|
||||
self.assertEqual(
|
||||
@ -96,7 +94,7 @@ class TestUtils(base.BaseTestCase):
|
||||
|
||||
def test_get_chassis_availability_zones_malformed(self):
|
||||
chassis = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'external_ids': {'ovn-cms-options':
|
||||
'other_config': {'ovn-cms-options':
|
||||
'enable-chassis-as-gw,availability-zones:az0'}})
|
||||
self.assertEqual(
|
||||
set(), utils.get_chassis_availability_zones(chassis))
|
||||
@ -155,16 +153,16 @@ class TestUtils(base.BaseTestCase):
|
||||
def test_get_chassis_in_azs(self):
|
||||
ch0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'name': 'ch0',
|
||||
'external_ids': {
|
||||
'other_config': {
|
||||
'ovn-cms-options':
|
||||
'enable-chassis-as-gw,availability-zones=az0:az1:az2'}})
|
||||
ch1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'name': 'ch1',
|
||||
'external_ids': {
|
||||
'other_config': {
|
||||
'ovn-cms-options': 'enable-chassis-as-gw'}})
|
||||
ch2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'name': 'ch2',
|
||||
'external_ids': {
|
||||
'other_config': {
|
||||
'ovn-cms-options':
|
||||
'enable-chassis-as-gw,availability-zones=az1:az5'}})
|
||||
|
||||
@ -182,21 +180,21 @@ class TestUtils(base.BaseTestCase):
|
||||
def test_get_gateway_chassis_without_azs(self):
|
||||
ch0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'name': 'ch0',
|
||||
'external_ids': {
|
||||
'other_config': {
|
||||
'ovn-cms-options':
|
||||
'enable-chassis-as-gw,availability-zones=az0:az1:az2'}})
|
||||
ch1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'name': 'ch1',
|
||||
'external_ids': {
|
||||
'other_config': {
|
||||
'ovn-cms-options': 'enable-chassis-as-gw'}})
|
||||
ch2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'name': 'ch2',
|
||||
'external_ids': {
|
||||
'other_config': {
|
||||
'ovn-cms-options':
|
||||
'enable-chassis-as-gw,availability-zones=az1:az5'}})
|
||||
ch3 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
|
||||
'name': 'ch3',
|
||||
'external_ids': {}})
|
||||
'other_config': {}})
|
||||
|
||||
chassis_list = [ch0, ch1, ch2, ch3]
|
||||
self.assertEqual(
|
||||
|
@ -827,20 +827,23 @@ class FakeChassis(object):
|
||||
if chassis_as_gw:
|
||||
cms_opts.append(ovn_const.CMS_OPT_CHASSIS_AS_GW)
|
||||
|
||||
external_ids = {}
|
||||
# NOTE(ralonsoh): LP#1990229, once min OVN version >= 20.06, the CMS
|
||||
# options and the bridge mappings should be stored only in
|
||||
# "other_config".
|
||||
other_config = {}
|
||||
if cms_opts:
|
||||
external_ids[ovn_const.OVN_CMS_OPTIONS] = ','.join(cms_opts)
|
||||
other_config[ovn_const.OVN_CMS_OPTIONS] = ','.join(cms_opts)
|
||||
|
||||
attrs = {
|
||||
chassis_attrs = {
|
||||
'encaps': [],
|
||||
'external_ids': external_ids,
|
||||
'external_ids': '',
|
||||
'hostname': '',
|
||||
'name': uuidutils.generate_uuid(),
|
||||
'nb_cfg': 0,
|
||||
'other_config': {},
|
||||
'other_config': other_config,
|
||||
'transport_zones': [],
|
||||
'vtep_logical_switches': []}
|
||||
|
||||
# Overwrite default attributes.
|
||||
attrs.update(attrs)
|
||||
return type('Chassis', (object, ), attrs)
|
||||
chassis_attrs.update(attrs)
|
||||
return type('Chassis', (object, ), chassis_attrs)
|
||||
|
@ -31,7 +31,7 @@ class AgentCacheTestCase(base.BaseTestCase):
|
||||
self.names_ref = []
|
||||
for i in range(10): # Add 10 agents.
|
||||
chassis_private = fakes.FakeOvsdbRow.create_one_ovsdb_row(
|
||||
attrs={'name': 'chassis' + str(i)})
|
||||
attrs={'name': 'chassis' + str(i), 'other_config': {}})
|
||||
self.agent_cache.update(ovn_const.OVN_CONTROLLER_AGENT,
|
||||
chassis_private)
|
||||
self.names_ref.append('chassis' + str(i))
|
||||
|
@ -81,6 +81,9 @@ OVN_SB_SCHEMA = {
|
||||
"name": {"type": "string"},
|
||||
"hostname": {"type": "string"},
|
||||
"external_ids": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}},
|
||||
"other_config": {
|
||||
"type": {"key": "string", "value": "string",
|
||||
"min": 0, "max": "unlimited"}}},
|
||||
"isRoot": True,
|
||||
@ -524,7 +527,7 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
self.row_json = {
|
||||
"name": "fake-name",
|
||||
"hostname": "fake-hostname",
|
||||
"external_ids": ['map', [["ovn-bridge-mappings",
|
||||
"other_config": ['map', [["ovn-bridge-mappings",
|
||||
"fake-phynet1:fake-br1"]]]
|
||||
}
|
||||
self._mock_hash_ring = mock.patch.object(
|
||||
@ -548,14 +551,18 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
self.sb_idl.notify_handler.notify_loop()
|
||||
|
||||
def test_chassis_create_event(self):
|
||||
self._test_chassis_helper('create', self.row_json)
|
||||
old_row_json = {'other_config': ['map', []]}
|
||||
self._test_chassis_helper('create', self.row_json,
|
||||
old_row_json=old_row_json)
|
||||
self.mech_driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', ['fake-phynet1'])
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
event_from_chassis=None)
|
||||
|
||||
def test_chassis_delete_event(self):
|
||||
self._test_chassis_helper('delete', self.row_json)
|
||||
old_row_json = {'other_config': ['map', []]}
|
||||
self._test_chassis_helper('delete', self.row_json,
|
||||
old_row_json=old_row_json)
|
||||
self.mech_driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', [])
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
@ -563,7 +570,7 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
|
||||
def test_chassis_update_event(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
old_row_json['external_ids'][1][0][1] = (
|
||||
old_row_json['other_config'][1][0][1] = (
|
||||
"fake-phynet2:fake-br2")
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.mech_driver.update_segment_host_mapping.assert_called_once_with(
|
||||
@ -572,9 +579,9 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
event_from_chassis=None)
|
||||
|
||||
def test_chassis_update_event_reschedule_not_needed(self):
|
||||
self.row_json['external_ids'][1].append(['foo_field', 'foo_value_new'])
|
||||
self.row_json['other_config'][1].append(['foo_field', 'foo_value_new'])
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
old_row_json['external_ids'][1][1][1] = (
|
||||
old_row_json['other_config'][1][1][1] = (
|
||||
"foo_value")
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.mech_driver.update_segment_host_mapping.assert_not_called()
|
||||
@ -582,14 +589,14 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
|
||||
def test_chassis_update_event_reschedule_lost_physnet(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
self.row_json['external_ids'][1][0][1] = ''
|
||||
self.row_json['other_config'][1][0][1] = ''
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.l3_plugin.schedule_unhosted_gateways.assert_called_once_with(
|
||||
event_from_chassis='fake-name')
|
||||
|
||||
def test_chassis_update_event_reschedule_add_physnet(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
self.row_json['external_ids'][1][0][1] += ',foo_physnet:foo_br'
|
||||
self.row_json['other_config'][1][0][1] += ',foo_physnet:foo_br'
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.mech_driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', ['fake-phynet1', 'foo_physnet'])
|
||||
@ -598,7 +605,7 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
|
||||
def test_chassis_update_event_reschedule_add_and_remove_physnet(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
self.row_json['external_ids'][1][0][1] = 'foo_physnet:foo_br'
|
||||
self.row_json['other_config'][1][0][1] = 'foo_physnet:foo_br'
|
||||
self._test_chassis_helper('update', self.row_json, old_row_json)
|
||||
self.mech_driver.update_segment_host_mapping.assert_called_once_with(
|
||||
'fake-hostname', ['foo_physnet'])
|
||||
@ -607,7 +614,7 @@ class TestOvnSbIdlNotifyHandler(test_mech_driver.OVNMechanismDriverTestCase):
|
||||
|
||||
def test_chassis_update_empty_no_external_ids(self):
|
||||
old_row_json = copy.deepcopy(self.row_json)
|
||||
old_row_json.pop('external_ids')
|
||||
old_row_json.pop('other_config')
|
||||
with mock.patch(
|
||||
'neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
|
||||
'ovsdb_monitor.ChassisEvent.'
|
||||
@ -640,10 +647,10 @@ class TestChassisEvent(base.BaseTestCase):
|
||||
|
||||
def _test_handle_ha_chassis_group_changes_create(self, event):
|
||||
# Chassis
|
||||
ext_ids = {
|
||||
other_config = {
|
||||
'ovn-cms-options': 'enable-chassis-as-gw,availability-zones=az-0'}
|
||||
row = fakes.FakeOvsdbTable.create_one_ovsdb_table(
|
||||
attrs={'name': 'SpongeBob', 'external_ids': ext_ids})
|
||||
attrs={'name': 'SpongeBob', 'other_config': other_config})
|
||||
# HA Chassis
|
||||
ch0 = fakes.FakeOvsdbTable.create_one_ovsdb_table(
|
||||
attrs={'priority': 10})
|
||||
@ -675,10 +682,10 @@ class TestChassisEvent(base.BaseTestCase):
|
||||
|
||||
def _test_handle_ha_chassis_group_changes_delete(self, event):
|
||||
# Chassis
|
||||
ext_ids = {
|
||||
other_config = {
|
||||
'ovn-cms-options': 'enable-chassis-as-gw,availability-zones=az-0'}
|
||||
row = fakes.FakeOvsdbTable.create_one_ovsdb_table(
|
||||
attrs={'name': 'SpongeBob', 'external_ids': ext_ids})
|
||||
attrs={'name': 'SpongeBob', 'other_config': other_config})
|
||||
# HA Chassis
|
||||
ha_ch = fakes.FakeOvsdbTable.create_one_ovsdb_table(
|
||||
attrs={'priority': 10})
|
||||
@ -702,10 +709,10 @@ class TestChassisEvent(base.BaseTestCase):
|
||||
def test_handle_ha_chassis_group_changes_update_no_changes(self):
|
||||
# Assert nothing was done because the update didn't
|
||||
# change the gateway chassis status or the availability zones
|
||||
ext_ids = {
|
||||
other_config = {
|
||||
'ovn-cms-options': 'enable-chassis-as-gw,availability-zones=az-0'}
|
||||
new = fakes.FakeOvsdbTable.create_one_ovsdb_table(
|
||||
attrs={'name': 'SpongeBob', 'external_ids': ext_ids})
|
||||
attrs={'name': 'SpongeBob', 'other_config': other_config})
|
||||
old = new
|
||||
self.assertIsNone(self.event.handle_ha_chassis_group_changes(
|
||||
self.event.ROW_UPDATE, new, old))
|
||||
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
other:
|
||||
- |
|
||||
Since OVN 20.06, the "Chassis" register configuration is stored in the
|
||||
"other_config" field and replicated into "external_ids". This replication
|
||||
is stopped in OVN 22.09. The ML2/OVN plugin tries to retrieve the "Chassis"
|
||||
configuration from the "other_config" field first; if this field does not
|
||||
exist (in OVN versions before 20.06), the plugin will use "external_ids"
|
||||
field instead. Neutron will be compatible with the different OVN versions
|
||||
(with and without "other_config" field).
|
Loading…
Reference in New Issue
Block a user