Skip to content

Commit cbe514e

Browse files
authored
Merge pull request #58 from stackhpc/upstream/yoga-2023-07-24
Synchronise yoga with upstream
2 parents 25cc9cd + 47187e1 commit cbe514e

File tree

6 files changed

+179
-17
lines changed

6 files changed

+179
-17
lines changed

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,63 @@ def check_fair_meter_consistency(self):
10531053
from_reload=True)
10541054
raise periodics.NeverAgain()
10551055

1056+
@periodics.periodic(spacing=300, run_immediately=True)
1057+
def remove_duplicated_chassis_registers(self):
1058+
"""Remove the "Chassis" and "Chassis_Private" duplicated registers.
1059+
1060+
When the ovn-controller service of a node is updated and the system-id
1061+
is changed, if the old service is not stopped gracefully, it will leave
1062+
a "Chassis" and a "Chassis_Private" registers on the OVN SB database.
1063+
These leftovers must be removed.
1064+
1065+
NOTE: this method is executed every 5 minutes. If a new chassis is
1066+
added, this method will perform again the clean-up process.
1067+
1068+
NOTE: this method can be executed only if the OVN SB has the
1069+
"Chassis_Private" table. Otherwise, is not possible to find out which
1070+
register is newer and thus must be kept in the database.
1071+
"""
1072+
if not self._sb_idl.is_table_present('Chassis_Private'):
1073+
raise periodics.NeverAgain()
1074+
1075+
if not self.has_lock:
1076+
return
1077+
1078+
# dup_chassis_port_host = {host_name: [(ch1, ch_private1),
1079+
# (ch2, ch_private2), ... ]}
1080+
dup_chassis_port_host = {}
1081+
chassis = self._sb_idl.chassis_list().execute(check_error=True)
1082+
chassis_hostnames = {ch.hostname for ch in chassis}
1083+
# Find the duplicated "Chassis" and "Chassis_Private" registers,
1084+
# comparing the hostname.
1085+
for hostname in chassis_hostnames:
1086+
ch_list = []
1087+
# Find these chassis matching the hostname and create a list.
1088+
for ch in (ch for ch in chassis if ch.hostname == hostname):
1089+
ch_private = self._sb_idl.lookup('Chassis_Private', ch.name,
1090+
default=None)
1091+
if ch_private:
1092+
ch_list.append((ch, ch_private))
1093+
1094+
# If the chassis list > 1, then we have duplicated chassis.
1095+
if len(ch_list) > 1:
1096+
# Order ch_list by Chassis_Private.nb_cfg_timestamp, from newer
1097+
# (greater value) to older.
1098+
ch_list.sort(key=lambda x: x[1].nb_cfg_timestamp, reverse=True)
1099+
dup_chassis_port_host[hostname] = ch_list
1100+
1101+
if not dup_chassis_port_host:
1102+
return
1103+
1104+
# Remove the "Chassis" and "Chassis_Private" registers with the
1105+
# older Chassis_Private.nb_cfg_timestamp.
1106+
with self._sb_idl.transaction(check_error=True) as txn:
1107+
for ch_list in dup_chassis_port_host.values():
1108+
# The first item is skipped, this is the newest element.
1109+
for ch, ch_private in ch_list[1:]:
1110+
for table in ('Chassis_Private', 'Chassis'):
1111+
txn.add(self._sb_idl.db_destroy(table, ch.name))
1112+
10561113

10571114
class HashRingHealthCheckPeriodics(object):
10581115

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from neutron_lib.plugins import directory
3333
from neutron_lib.plugins import utils as p_utils
3434
from neutron_lib.services.logapi import constants as log_const
35+
from neutron_lib.services.qos import constants as qos_consts
3536
from neutron_lib.utils import helpers
3637
from neutron_lib.utils import net as n_net
3738
from oslo_config import cfg
@@ -1163,25 +1164,30 @@ def create_floatingip(self, context, floatingip):
11631164
n_context.get_admin_context(), floatingip['id'],
11641165
const.FLOATINGIP_STATUS_ACTIVE)
11651166

1166-
def update_floatingip(self, context, floatingip):
1167+
def update_floatingip(self, context, floatingip, fip_request=None):
11671168
fip_status = None
11681169
router_id = None
11691170
ovn_fip = self._nb_idl.get_floatingip(floatingip['id'])
1171+
fip_request = fip_request[l3.FLOATINGIP] if fip_request else {}
1172+
qos_update_only = (len(fip_request.keys()) == 1 and
1173+
qos_consts.QOS_POLICY_ID in fip_request)
11701174

11711175
check_rev_cmd = self._nb_idl.check_revision_number(
11721176
floatingip['id'], floatingip, ovn_const.TYPE_FLOATINGIPS)
11731177
with self._nb_idl.transaction(check_error=True) as txn:
11741178
txn.add(check_rev_cmd)
1175-
if ovn_fip:
1176-
lrouter = ovn_fip['external_ids'].get(
1177-
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY,
1178-
utils.ovn_name(router_id))
1179-
self._delete_floatingip(ovn_fip, lrouter, txn=txn)
1180-
fip_status = const.FLOATINGIP_STATUS_DOWN
1181-
1182-
if floatingip.get('port_id'):
1183-
self._create_or_update_floatingip(floatingip, txn=txn)
1184-
fip_status = const.FLOATINGIP_STATUS_ACTIVE
1179+
# If FIP updates the QoS policy only, skip the OVN NAT rules update
1180+
if not qos_update_only:
1181+
if ovn_fip:
1182+
lrouter = ovn_fip['external_ids'].get(
1183+
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY,
1184+
utils.ovn_name(router_id))
1185+
self._delete_floatingip(ovn_fip, lrouter, txn=txn)
1186+
fip_status = const.FLOATINGIP_STATUS_DOWN
1187+
1188+
if floatingip.get('port_id'):
1189+
self._create_or_update_floatingip(floatingip, txn=txn)
1190+
fip_status = const.FLOATINGIP_STATUS_ACTIVE
11851191

11861192
self._qos_driver.update_floatingip(txn, floatingip)
11871193

neutron/services/ovn_l3/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ def delete_floatingip(self, context, id):
278278
def update_floatingip(self, context, id, floatingip):
279279
fip = super(OVNL3RouterPlugin, self).update_floatingip(context, id,
280280
floatingip)
281-
self._ovn_client.update_floatingip(context, fip)
281+
self._ovn_client.update_floatingip(context, fip, floatingip)
282282
return fip
283283

284284
def update_floatingip_status(self, context, floatingip_id, status):

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,64 @@ def _verify_lb(test, protocol, vip_ext_port, vip_int_port):
10491049
# Assert load balancer for port forwarding is gone
10501050
self.assertFalse(self._find_pf_lb(router_id, fip_id))
10511051

1052+
def test_remove_duplicated_chassis_registers(self):
1053+
hostnames = ['host1', 'host2']
1054+
for hostname in hostnames:
1055+
for _ in range(3):
1056+
self.add_fake_chassis(hostname)
1057+
1058+
chassis = self.sb_api.chassis_list().execute(check_error=True)
1059+
self.assertEqual(6, len(chassis))
1060+
# Make the chassis private timestamp different
1061+
for idx, ch in enumerate(chassis):
1062+
self.sb_api.db_set('Chassis_Private', ch.name,
1063+
('nb_cfg_timestamp', idx)).execute()
1064+
1065+
ch_private_dict = {} # host: [ch_private1, ch_private2, ...]
1066+
for hostname in hostnames:
1067+
ch_private_list = []
1068+
for ch in (ch for ch in chassis if ch.hostname == hostname):
1069+
ch_private = self.sb_api.lookup('Chassis_Private', ch.name,
1070+
default=None)
1071+
if ch_private:
1072+
# One of the "Chassis_Private" has been deleted on purpose
1073+
# in this test.
1074+
ch_private_list.append(ch_private)
1075+
ch_private_list.sort(key=lambda x: x.nb_cfg_timestamp,
1076+
reverse=True)
1077+
ch_private_dict[hostname] = ch_private_list
1078+
1079+
self.maint.remove_duplicated_chassis_registers()
1080+
chassis_result = self.sb_api.chassis_list().execute(check_error=True)
1081+
self.assertEqual(2, len(chassis_result))
1082+
for ch in chassis_result:
1083+
self.assertIn(ch.hostname, hostnames)
1084+
hostnames.remove(ch.hostname)
1085+
# From ch_private_dict[ch.hostname], we retrieve the first
1086+
# "Chassis_Private" register because these are ordered by
1087+
# timestamp. The newer one (bigger timestamp) should remain in the
1088+
# system.
1089+
ch_expected = ch_private_dict[ch.hostname][0].chassis[0]
1090+
self.assertEqual(ch_expected.name, ch.name)
1091+
1092+
def test_remove_duplicated_chassis_registers_no_ch_private_register(self):
1093+
for _ in range(2):
1094+
self.add_fake_chassis('host1')
1095+
1096+
chassis = self.sb_api.chassis_list().execute(check_error=True)
1097+
self.assertEqual(2, len(chassis))
1098+
# Make the chassis private timestamp different
1099+
# Delete on of the "Chassis_Private" registers.
1100+
self.sb_api.db_destroy('Chassis_Private', chassis[0].name).execute()
1101+
self.sb_api.db_set('Chassis_Private', chassis[1].name,
1102+
('nb_cfg_timestamp', 1)).execute()
1103+
1104+
self.maint.remove_duplicated_chassis_registers()
1105+
chassis_result = self.sb_api.chassis_list().execute(check_error=True)
1106+
# Both "Chassis" registers are still in the DB because one
1107+
# "Chassis_Private" register was missing.
1108+
self.assertEqual(2, len(chassis_result))
1109+
10521110

10531111
class TestLogMaintenance(_TestMaintenanceHelper,
10541112
test_log_driver.LogApiTestCaseBase):

neutron/tests/unit/services/ovn_l3/test_plugin.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from unittest import mock
1717

1818
from neutron_lib.api.definitions import external_net
19+
from neutron_lib.api.definitions import l3 as l3_def
1920
from neutron_lib.api.definitions import portbindings
2021
from neutron_lib.api.definitions import provider_net as pnet
2122
from neutron_lib.callbacks import events
@@ -26,12 +27,14 @@
2627
from neutron_lib.exceptions import l3 as l3_exc
2728
from neutron_lib.plugins import constants as plugin_constants
2829
from neutron_lib.plugins import directory
30+
from neutron_lib.services.qos import constants as qos_consts
2931
from oslo_config import cfg
3032
from oslo_utils import uuidutils
3133

3234
from neutron.common.ovn import constants as ovn_const
3335
from neutron.common.ovn import utils
3436
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config
37+
from neutron.db import extraroute_db
3538
from neutron.services.revisions import revision_plugin
3639
from neutron.tests.unit.api import test_extensions
3740
from neutron.tests.unit.extensions import test_extraroute
@@ -1210,7 +1213,8 @@ def test_update_floatingip(self, uf):
12101213
uf.return_value = self.fake_floating_ip_new
12111214
nb_ovn.get_floatingip.return_value = (
12121215
self.fake_ovn_nat_rule)
1213-
self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
1216+
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
1217+
self.l3_inst.update_floatingip(self.context, 'id', fip)
12141218
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
12151219
'neutron-router-id',
12161220
type='dnat_and_snat',
@@ -1234,13 +1238,30 @@ def test_update_floatingip(self, uf):
12341238
logical_port='new-port_id',
12351239
external_ids=expected_ext_ids)
12361240

1241+
@mock.patch.object(extraroute_db.ExtraRoute_dbonly_mixin,
1242+
'update_floatingip')
1243+
def test_update_floatingip_qos(self, uf):
1244+
nb_ovn = self.l3_inst._nb_ovn
1245+
nb_ovn.is_col_present.return_value = True
1246+
uf.return_value = self.fake_floating_ip_new
1247+
nb_ovn.get_floatingip.return_value = (
1248+
self.fake_ovn_nat_rule)
1249+
fip = {l3_def.FLOATINGIP: {qos_consts.QOS_POLICY_ID: 'qos_id_1'}}
1250+
with mock.patch.object(self.l3_inst._ovn_client._qos_driver,
1251+
'update_floatingip') as ufip:
1252+
self.l3_inst.update_floatingip(self.context, 'id', fip)
1253+
nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
1254+
nb_ovn.add_nat_rule_in_lrouter.assert_not_called()
1255+
ufip.assert_called_once_with(mock.ANY, self.fake_floating_ip_new)
1256+
12371257
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
12381258
'update_floatingip')
12391259
def test_update_floatingip_associate(self, uf):
12401260
self.l3_inst._nb_ovn.is_col_present.return_value = True
12411261
self.fake_floating_ip.update({'fixed_port_id': None})
12421262
uf.return_value = self.fake_floating_ip_new
1243-
self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
1263+
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
1264+
self.l3_inst.update_floatingip(self.context, 'id', fip)
12441265
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
12451266
expected_ext_ids = {
12461267
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
@@ -1276,7 +1297,8 @@ def test_update_floatingip_associate_distributed(self, uf, gn):
12761297

12771298
config.cfg.CONF.set_override(
12781299
'enable_distributed_floating_ip', True, group='ovn')
1279-
self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
1300+
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
1301+
self.l3_inst.update_floatingip(self.context, 'id', fip)
12801302
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
12811303
expected_ext_ids = {
12821304
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
@@ -1304,7 +1326,8 @@ def test_update_floatingip_association_empty_update(self, uf):
13041326
self.fake_floating_ip.update({'fixed_port_id': 'foo'})
13051327
self.fake_floating_ip_new.update({'port_id': 'foo'})
13061328
uf.return_value = self.fake_floating_ip_new
1307-
self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
1329+
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
1330+
self.l3_inst.update_floatingip(self.context, 'id', fip)
13081331
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
13091332
'neutron-router-id',
13101333
type='dnat_and_snat',
@@ -1339,7 +1362,8 @@ def test_update_floatingip_reassociate_to_same_port_diff_fixed_ip(
13391362
self.fake_floating_ip_new.update({'port_id': 'port_id',
13401363
'fixed_port_id': 'port_id'})
13411364
uf.return_value = self.fake_floating_ip_new
1342-
self.l3_inst.update_floatingip(self.context, 'id', 'floatingip')
1365+
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
1366+
self.l3_inst.update_floatingip(self.context, 'id', fip)
13431367

13441368
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
13451369
'neutron-router-id',
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
issues:
3+
- |
4+
When using ML2/OVN, during an upgrade procedure, the OVS system-id stored
5+
value can be changed. The ovn-controller service will create the "Chassis"
6+
and "Chassis_Private" registers based on this OVS system-id. If the
7+
ovn-controller process is not gracefully stopped, that could lead to the
8+
existence of duplicated "Chassis" and "Chassis_Private" registers in the
9+
OVN Southbound database.
10+
fixes:
11+
- |
12+
A new OVN maintenance method ``remove_duplicated_chassis_registers`` is
13+
added. This method will periodically check the OVN Southbound "Chassis"
14+
and "Chassis_Private" tables looking for duplicated registers. The older
15+
ones (based on the "Chassis_Private.nb_cfg_timestamp" value) will be
16+
removed when more than one register has the same hostname, that should
17+
be unique.

0 commit comments

Comments
 (0)