Skip to content

Commit 3dbb211

Browse files
authored
Merge pull request #64 from stackhpc/upstream/yoga-2023-09-04
Synchronise yoga with upstream
2 parents 99556e9 + 716ab72 commit 3dbb211

File tree

17 files changed

+276
-34
lines changed

17 files changed

+276
-34
lines changed

neutron/common/ovn/extensions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
from neutron_lib.api.definitions import subnet_dns_publish_fixed_ip
6666
from neutron_lib.api.definitions import subnet_service_types
6767
from neutron_lib.api.definitions import trunk
68+
from neutron_lib.api.definitions import uplink_status_propagation
6869
from neutron_lib.api.definitions import vlantransparent
6970
from neutron_lib import constants
7071

@@ -149,4 +150,5 @@
149150
floating_ip_port_forwarding.ALIAS,
150151
vlantransparent.ALIAS,
151152
logging.ALIAS,
153+
uplink_status_propagation.ALIAS,
152154
]

neutron/common/ovn/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,3 +836,10 @@ def get_ovn_chassis_other_config(chassis):
836836
return chassis.other_config
837837
except AttributeError:
838838
return chassis.external_ids
839+
840+
841+
def get_requested_chassis(requested_chassis):
842+
"""Returns a list with the items in the LSP.options:requested-chassis"""
843+
if isinstance(requested_chassis, str):
844+
return requested_chassis.split(',')
845+
return []

neutron/db/ovn_hash_ring_db.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ def remove_node_by_uuid(context, node_uuid):
6060
LOG.info('Node "%s" removed from the Hash Ring', node_uuid)
6161

6262

63+
@db_api.retry_if_session_inactive()
64+
def cleanup_old_nodes(context, days):
65+
age = timeutils.utcnow() - datetime.timedelta(days=days)
66+
with db_api.CONTEXT_WRITER.using(context):
67+
context.session.query(ovn_models.OVNHashRing).filter(
68+
ovn_models.OVNHashRing.updated_at < age).delete()
69+
LOG.info('Cleaned up Hash Ring nodes older than %d days', days)
70+
71+
6372
@db_api.retry_if_session_inactive()
6473
def _touch(context, updated_at=None, **filter_args):
6574
if updated_at is None:

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/impl_idl_ovn.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ def schema_helper(cls):
160160
cls.schema)
161161
return cls._schema_helper
162162

163+
@classmethod
164+
def get_schema_version(cls):
165+
return cls.schema_helper.schema_json['version']
166+
163167
@classmethod
164168
def schema_has_table(cls, table_name):
165169
return table_name in cls.schema_helper.schema_json['tables']

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ def check_for_localnet_legacy_port_name(self):
727727
txn.add(cmd)
728728
raise periodics.NeverAgain()
729729

730-
# TODO(lucasagomes): Remove this in the Z cycle
730+
# TODO(lucasagomes): Remove this in the B+3 cycle
731731
# A static spacing value is used here, but this method will only run
732732
# once per lock due to the use of periodics.NeverAgain().
733733
@periodics.periodic(spacing=600, run_immediately=True)
@@ -738,21 +738,36 @@ def check_for_mcast_flood_reports(self):
738738
cmds = []
739739
for port in self._nb_idl.lsp_list().execute(check_error=True):
740740
port_type = port.type.strip()
741-
if port_type in ("vtep", ovn_const.LSP_TYPE_LOCALPORT, "router"):
742-
continue
743-
744741
options = port.options
745-
if port_type == ovn_const.LSP_TYPE_LOCALNET:
746-
mcast_flood_value = options.get(
742+
mcast_flood_reports_value = options.get(
747743
ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS)
748-
if mcast_flood_value == 'false':
744+
745+
if self._ovn_client.is_mcast_flood_broken:
746+
if port_type in ("vtep", ovn_const.LSP_TYPE_LOCALPORT,
747+
"router"):
748+
continue
749+
750+
if port_type == ovn_const.LSP_TYPE_LOCALNET:
751+
mcast_flood_value = options.pop(
752+
ovn_const.LSP_OPTIONS_MCAST_FLOOD, None)
753+
if mcast_flood_value:
754+
cmds.append(self._nb_idl.db_remove(
755+
'Logical_Switch_Port', port.name, 'options',
756+
ovn_const.LSP_OPTIONS_MCAST_FLOOD,
757+
if_exists=True))
758+
759+
if mcast_flood_reports_value == 'true':
749760
continue
750-
options.update({ovn_const.LSP_OPTIONS_MCAST_FLOOD: 'false'})
751-
elif ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS in options:
752-
continue
753761

754-
options.update({ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true'})
755-
cmds.append(self._nb_idl.lsp_set_options(port.name, **options))
762+
options.update(
763+
{ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true'})
764+
cmds.append(self._nb_idl.lsp_set_options(port.name, **options))
765+
766+
elif (mcast_flood_reports_value and port_type !=
767+
ovn_const.LSP_TYPE_LOCALNET):
768+
cmds.append(self._nb_idl.db_remove(
769+
'Logical_Switch_Port', port.name, 'options',
770+
ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS, if_exists=True))
756771

757772
if cmds:
758773
with self._nb_idl.transaction(check_error=True) as txn:
@@ -1111,6 +1126,20 @@ def remove_duplicated_chassis_registers(self):
11111126
for table in ('Chassis_Private', 'Chassis'):
11121127
txn.add(self._sb_idl.db_destroy(table, ch.name))
11131128

1129+
@periodics.periodic(spacing=86400, run_immediately=True)
1130+
def cleanup_old_hash_ring_nodes(self):
1131+
"""Daily task to cleanup old stable Hash Ring node entries.
1132+
1133+
Runs once a day and clean up Hash Ring entries that haven't
1134+
been updated in more than 5 days. See LP #2033281 for more
1135+
information.
1136+
1137+
"""
1138+
if not self.has_lock:
1139+
return
1140+
context = n_context.get_admin_context()
1141+
hash_ring_db.cleanup_old_nodes(context, days=5)
1142+
11141143

11151144
class HashRingHealthCheckPeriodics(object):
11161145

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from oslo_log import log
4040
from oslo_utils import excutils
4141
from oslo_utils import timeutils
42+
from oslo_utils import versionutils
4243
from ovsdbapp.backend.ovs_idl import idlutils
4344
import tenacity
4445

@@ -94,6 +95,7 @@ def __init__(self, nb_idl, sb_idl):
9495

9596
self._plugin_property = None
9697
self._l3_plugin_property = None
98+
self._is_mcast_flood_broken = None
9799

98100
# TODO(ralonsoh): handle the OVN client extensions with an ext. manager
99101
self._qos_driver = qos_extension.OVNClientQosExtension(driver=self)
@@ -338,6 +340,20 @@ def update_lsp_host_info(self, context, db_port, up=True):
338340

339341
self._transaction(cmd)
340342

343+
# TODO(lucasagomes): Remove this method and the logic around the broken
344+
# mcast_flood_reports configuration option on any other port that is not
345+
# type "localnet" when the fixed version of OVN becomes the norm.
346+
# The commit in core OVN fixing this issue is the
347+
# https://github.com/ovn-org/ovn/commit/6aeeccdf272bc60630581e46aa42d97f4f56d4fa
348+
@property
349+
def is_mcast_flood_broken(self):
350+
if self._is_mcast_flood_broken is None:
351+
schema_version = self._nb_idl.get_schema_version()
352+
self._is_mcast_flood_broken = (
353+
versionutils.convert_version_to_tuple(schema_version) <
354+
(6, 3, 0))
355+
return self._is_mcast_flood_broken
356+
341357
def _get_port_options(self, port):
342358
context = n_context.get_admin_context()
343359
binding_prof = utils.validate_and_get_data_from_binding_profile(port)
@@ -500,12 +516,8 @@ def _get_port_options(self, port):
500516
if port_type != ovn_const.LSP_TYPE_VIRTUAL:
501517
options[ovn_const.LSP_OPTIONS_REQUESTED_CHASSIS_KEY] = chassis
502518

503-
# TODO(lucasagomes): Enable the mcast_flood_reports by default,
504-
# according to core OVN developers it shouldn't cause any harm
505-
# and will be ignored when mcast_snoop is False. We can revise
506-
# this once https://bugzilla.redhat.com/show_bug.cgi?id=1933990
507-
# (see comment #3) is fixed in Core OVN.
508-
if port_type not in ('vtep', ovn_const.LSP_TYPE_LOCALPORT, 'router'):
519+
if self.is_mcast_flood_broken and port_type not in (
520+
'vtep', ovn_const.LSP_TYPE_LOCALPORT, 'router'):
509521
options.update({ovn_const.LSP_OPTIONS_MCAST_FLOOD_REPORTS: 'true'})
510522

511523
device_owner = port.get('device_owner', '')

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,15 @@ def match_fn(self, event, row, old=None):
275275
{'port': row.logical_port, 'binding': row.uuid})
276276
return False
277277

278+
req_chassis = utils.get_requested_chassis(
279+
row.options.get(ovn_const.LSP_OPTIONS_REQUESTED_CHASSIS_KEY, ''))
280+
if len(req_chassis) > 1:
281+
# This event has been issued during a LSP migration. During this
282+
# process, the LSP will change the port binding but the port status
283+
# will be handled by the ``LogicalSwitchPortUpdateDownEvent`` and
284+
# ``LogicalSwitchPortUpdateUpEvent`` events.
285+
return False
286+
278287
return bool(lsp.up)
279288

280289
def run(self, event, row, old=None):

neutron/services/trunk/drivers/ovn/trunk_driver.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from neutron_lib.callbacks import events
1515
from neutron_lib.callbacks import registry
1616
from neutron_lib.callbacks import resources
17+
from neutron_lib import constants as n_const
1718
from neutron_lib import context as n_context
1819
from neutron_lib.db import api as db_api
1920
from neutron_lib import exceptions as n_exc
@@ -51,7 +52,11 @@ def _set_sub_ports(self, parent_port, subports):
5152
context = n_context.get_admin_context()
5253
db_parent_port = port_obj.Port.get_object(context, id=parent_port)
5354
parent_port_status = db_parent_port.status
54-
parent_port_bindings = db_parent_port.bindings[0]
55+
try:
56+
parent_port_bindings = [pb for pb in db_parent_port.bindings
57+
if pb.status == n_const.ACTIVE][-1]
58+
except IndexError:
59+
parent_port_bindings = None
5560
for subport in subports:
5661
with db_api.CONTEXT_WRITER.using(context), (
5762
txn(check_error=True)) as ovn_txn:
@@ -85,8 +90,10 @@ def _set_binding_profile(self, context, subport, parent_port,
8590
db_port.id, db_port, ovn_const.TYPE_PORTS)
8691
ovn_txn.add(check_rev_cmd)
8792
parent_binding_host = ''
88-
if parent_port_bindings.host:
89-
parent_binding_host = parent_port_bindings.host
93+
if parent_port_bindings and parent_port_bindings.host:
94+
migrating_to = parent_port_bindings.profile.get(
95+
ovn_const.MIGRATING_ATTR)
96+
parent_binding_host = migrating_to or parent_port_bindings.host
9097
try:
9198
# NOTE(flaviof): We expect binding's host to be set. Otherwise,
9299
# sub-port will not transition from DOWN to ACTIVE.

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,9 @@ def _verify_lb(test, protocol, vip_ext_port, vip_int_port):
10381038
m_publish.reset_mock()
10391039
self.pf_plugin.delete_floatingip_port_forwarding(
10401040
self.context, pf_obj['id'], fip_id)
1041-
m_publish.assert_called_once()
1041+
call = mock.call('port_forwarding', 'after_delete', self.pf_plugin,
1042+
payload=mock.ANY)
1043+
m_publish.assert_has_calls([call])
10421044

10431045
# Assert load balancer for port forwarding is stale
10441046
_verify_lb(self, 'udp', 5353, 53)

neutron/tests/functional/services/trunk/drivers/ovn/test_trunk_driver.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,23 @@ def _verify_trunk_info(self, trunk, has_items, host=''):
9797
if trunk.get('status'):
9898
self.assertEqual(trunk_consts.TRUNK_ACTIVE_STATUS, trunk['status'])
9999

100-
def _bind_port(self, port_id, host):
100+
def _bind_port(self, port_id, host_source, host_dest=None):
101101
with db_api.CONTEXT_WRITER.using(self.context):
102-
pb = port_obj.PortBinding.get_object(self.context,
103-
port_id=port_id, host='')
104-
pb.delete()
105-
port_obj.PortBinding(self.context, port_id=port_id, host=host,
106-
vif_type=portbindings.VIF_TYPE_OVS).create()
102+
for pb in port_obj.PortBinding.get_objects(self.context,
103+
port_id=port_id):
104+
pb.delete()
105+
profile = {}
106+
if host_dest:
107+
# When "host_dest" there are 2 port bindings, as in a live
108+
# migration; the second one (destination host) is inactive.
109+
profile[ovn_const.MIGRATING_ATTR] = host_dest
110+
port_obj.PortBinding(
111+
self.context, port_id=port_id, host=host_dest,
112+
vif_type=portbindings.VIF_TYPE_OVS,
113+
status=n_consts.INACTIVE).create()
114+
port_obj.PortBinding(
115+
self.context, port_id=port_id, host=host_source,
116+
profile=profile, vif_type=portbindings.VIF_TYPE_OVS).create()
107117

108118
def test_trunk_create(self):
109119
with self.trunk() as trunk:
@@ -148,6 +158,21 @@ def test_subport_add(self):
148158
self._verify_trunk_info(new_trunk, has_items=True,
149159
host='host1')
150160

161+
def test_subport_add_live_migration_multiple_port_binding(self):
162+
with self.subport() as subport:
163+
with self.trunk() as trunk:
164+
self.trunk_plugin.add_subports(self.context, trunk['id'],
165+
{'sub_ports': [subport]})
166+
new_trunk = self.trunk_plugin.get_trunk(self.context,
167+
trunk['id'])
168+
self._verify_trunk_info(new_trunk, has_items=True)
169+
# Bind parent port. That will trigger the binding of the
170+
# trunk subports too, using the same host ID.
171+
self._bind_port(trunk['port_id'], 'host1', host_dest='host2')
172+
self.mech_driver.set_port_status_up(trunk['port_id'])
173+
self._verify_trunk_info(new_trunk, has_items=True,
174+
host='host2')
175+
151176
def test_subport_delete(self):
152177
with self.subport() as subport:
153178
with self.trunk([subport]) as trunk:

0 commit comments

Comments
 (0)