From 92e8a6a37863d8e0f9bbc8ea96a6440b922c7bf5 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 21 Dec 2021 09:32:36 +0000 Subject: [PATCH] [OVN][QoS] Add external_ids reference to port QoS registers Same as with floating IP QoS OVN registers, port QoSes should have a "external_ids" reference to the port ID this QoS is attached to. Related-Bug: #1947334 Conflicts: neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py Change-Id: If14ccff79f77618133cce4bf95e3f1eea7ba8cf3 (cherry picked from commit ce7f2795384e05b7e086bdab41226a69708dfdc5) --- neutron/common/ovn/constants.py | 1 + .../ovn/mech_driver/ovsdb/extensions/qos.py | 11 ++++-- .../ovn/mech_driver/ovsdb/maintenance.py | 36 +++++++++++++++++++ .../mech_driver/ovsdb/extensions/test_qos.py | 23 +++++++----- .../ovn/mech_driver/ovsdb/test_maintenance.py | 24 +++++++++++++ 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/neutron/common/ovn/constants.py b/neutron/common/ovn/constants.py index e3af2558216..0c06e7816c2 100644 --- a/neutron/common/ovn/constants.py +++ b/neutron/common/ovn/constants.py @@ -21,6 +21,7 @@ OVN_ML2_MECH_DRIVER_NAME = 'ovn' OVN_NETWORK_NAME_EXT_ID_KEY = 'neutron:network_name' OVN_NETWORK_MTU_EXT_ID_KEY = 'neutron:mtu' OVN_PORT_NAME_EXT_ID_KEY = 'neutron:port_name' +OVN_PORT_EXT_ID_KEY = 'neutron:port_id' OVN_PORT_FIP_EXT_ID_KEY = 'neutron:port_fip' OVN_ROUTER_NAME_EXT_ID_KEY = 'neutron:router_name' OVN_AZ_HINTS_EXT_ID_KEY = 'neutron:availability_zone_hints' diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py index ae68e48500a..0070a47815d 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py @@ -150,14 +150,19 @@ class OVNClientQosExtension(object): ovn_qos_rule = {'switch': lswitch_name, 'direction': direction, 'priority': OVN_QOS_DEFAULT_RULE_PRIORITY, 'match': match} - if fip_id: - ovn_qos_rule['external_ids'] = { - ovn_const.OVN_FIP_EXT_ID_KEY: fip_id} if delete: # Any specific rule parameter is left undefined. return ovn_qos_rule + # All OVN QoS rules have an external ID reference to the port or the + # FIP that are attached to. + if fip_id: + key, value = ovn_const.OVN_FIP_EXT_ID_KEY, fip_id + else: + key, value = ovn_const.OVN_PORT_EXT_ID_KEY, port_id + ovn_qos_rule['external_ids'] = {key: value} + for rule_type, rule in rules.items(): if rule_type == qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: ovn_qos_rule['rate'] = rule['max_kbps'] diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py index 200cb4755bd..4cc4e9828a8 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py @@ -16,6 +16,7 @@ import abc import copy import inspect +import re import threading from futurist import periodics @@ -751,6 +752,41 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase): txn.add(cmd) raise periodics.NeverAgain() + # TODO(ralonsoh): Remove this in the Z+2 cycle + # A static spacing value is used here, but this method will only run + # once per lock due to the use of periodics.NeverAgain(). + @periodics.periodic(spacing=600, run_immediately=True) + def update_port_qos_with_external_ids_reference(self): + """Update all OVN QoS registers with the port ID + + This method will only update the OVN QoS registers related to port QoS, + not FIP QoS. FIP QoS have the corresponding "external_ids" reference. + """ + if not self.has_lock: + return + + regex = re.compile( + r'(inport|outport) == \"(?P[a-z0-9\-]{36})\"') + cmds = [] + for ls in self._nb_idl.ls_list().execute(check_error=True): + for qos in self._nb_idl.qos_list(ls.name).execute( + check_error=True): + if qos.external_ids: + continue + match = re.match(regex, qos.match) + if not match: + continue + port_id = match.group('port_id') + external_ids = {ovn_const.OVN_PORT_EXT_ID_KEY: port_id} + cmds.append(self._nb_idl.db_set( + 'QoS', qos.uuid, ('external_ids', external_ids))) + + if cmds: + with self._nb_idl.transaction(check_error=True) as txn: + for cmd in cmds: + txn.add(cmd) + raise periodics.NeverAgain() + class HashRingHealthCheckPeriodics(object): diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py index e04c6ed2ca1..cf7160c7888 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py @@ -188,6 +188,10 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.qos_driver._qos_rules(mock.ANY, mock.ANY)) def _test__ovn_qos_rule_ingress(self, fip_id=None, ip_address=None): + if fip_id: + external_ids = {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id} + else: + external_ids = {ovn_const.OVN_PORT_EXT_ID_KEY: 'port_id'} direction = constants.INGRESS_DIRECTION rule = {qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1} match = self.qos_driver._ovn_qos_rule_match( @@ -195,9 +199,8 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): expected = {'burst': 100, 'rate': 200, 'direction': 'to-lport', 'match': match, 'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY, - 'switch': 'neutron-network_id'} - if fip_id: - expected['external_ids'] = {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id} + 'switch': 'neutron-network_id', + 'external_ids': external_ids} result = self.qos_driver._ovn_qos_rule( direction, rule, 'port_id', 'network_id', fip_id=fip_id, ip_address=ip_address, resident_port='resident_port') @@ -210,15 +213,18 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self._test__ovn_qos_rule_ingress(fip_id='fipid', ip_address='1.2.3.4') def _test__ovn_qos_rule_egress(self, fip_id=None, ip_address=None): + if fip_id: + external_ids = {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id} + else: + external_ids = {ovn_const.OVN_PORT_EXT_ID_KEY: 'port_id'} direction = constants.EGRESS_DIRECTION rule = {qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1} match = self.qos_driver._ovn_qos_rule_match( direction, 'port_id', ip_address, 'resident_port') expected = {'direction': 'from-lport', 'match': match, 'dscp': 16, 'switch': 'neutron-network_id', - 'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY} - if fip_id: - expected['external_ids'] = {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id} + 'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY, + 'external_ids': external_ids} result = self.qos_driver._ovn_qos_rule( direction, rule, 'port_id', 'network_id', fip_id=fip_id, ip_address=ip_address, resident_port='resident_port') @@ -228,9 +234,8 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_2} expected = {'direction': 'from-lport', 'match': match, 'rate': 300, 'dscp': 20, 'switch': 'neutron-network_id', - 'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY} - if fip_id: - expected['external_ids'] = {ovn_const.OVN_FIP_EXT_ID_KEY: fip_id} + 'priority': qos_extension.OVN_QOS_DEFAULT_RULE_PRIORITY, + 'external_ids': external_ids} result = self.qos_driver._ovn_qos_rule( direction, rule, 'port_id', 'network_id', fip_id=fip_id, ip_address=ip_address, resident_port='resident_port') diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py index 1e5f4afc77c..acd30d45ec6 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py @@ -19,6 +19,7 @@ from futurist import periodics from neutron_lib import context from neutron_lib.db import api as db_api from oslo_config import cfg +from oslo_utils import uuidutils from neutron.common.ovn import constants from neutron.common.ovn import utils @@ -535,3 +536,26 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight, mock.call('Logical_Router_Port', 'lrp-port1', ('options', opt))] self.fake_ovn_client._nb_idl.db_set.assert_has_calls( expected_calls) + + def test_update_port_qos_with_external_ids_reference(self): + nb_idl = self.fake_ovn_client._nb_idl + lrs = [fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'name': 'lr%s' % idx}) for idx in range(3)] + uuid1 = uuidutils.generate_uuid() + qoses1 = [fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'external_ids': {}, 'match': 'inport == "%s"' % uuid1})] + qoses2 = [fakes.FakeOvsdbRow.create_one_ovsdb_row( + attrs={'external_ids': {constants.OVN_PORT_EXT_ID_KEY: uuid1}, + 'match': 'inport == "%s"' % uuid1})] + qoses3 = [] + nb_idl.ls_list.return_value.execute.return_value = lrs + nb_idl.qos_list.return_value.execute.side_effect = [qoses1, qoses2, + qoses3] + self.assertRaises( + periodics.NeverAgain, + self.periodic.update_port_qos_with_external_ids_reference) + + external_ids = {constants.OVN_PORT_EXT_ID_KEY: uuid1} + expected_calls = [mock.call('QoS', qoses1[0].uuid, + ('external_ids', external_ids))] + nb_idl.db_set.assert_has_calls(expected_calls)