From e7e71b2ca67169e6de4cdad71f2c82059132325d Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 21 Apr 2020 14:53:34 +0000 Subject: [PATCH] [OVN] Implement floating IP QoS in OVN backend This patch implements in the OVN backend the existing floating IP QoS extension. The OVN client, using the existing QoS extension, will retrieve the QoS rules attached to each floating IP, the router where the floating IP lives and the router gateway port. The QoS rules will be applied on the router gateway port. The OVN NB QoS rules for floating IP addresses have a "match" field containing a tuple of parameters: - The direction of the flow: 'inport == "src"' or 'outport == "dst"' - The IP address to match: 'ip4.src == 1.2.3.4' or 'ip4.dst == 1.2.3.4' - The chassis where the port is located: 'is_chassis_resident("chassis")' Closes-Bug: #1877408 Related-Bug: #1596611 Depends-On: https://review.opendev.org/#/c/727847/ Change-Id: Ib65d8edcb0a415f6d698c952334d3b4bb0d9fff6 --- lower-constraints.txt | 2 +- neutron/common/ovn/constants.py | 1 + neutron/common/ovn/extensions.py | 13 ++ neutron/common/ovn/utils.py | 6 + neutron/extensions/qos_fip.py | 4 +- neutron/objects/qos/policy.py | 6 +- .../ovn/mech_driver/ovsdb/extensions/qos.py | 90 ++++++++- .../ovn/mech_driver/ovsdb/ovn_client.py | 39 ++-- neutron/services/ovn_l3/plugin.py | 38 ++-- .../mech_driver/ovsdb/extensions/test_qos.py | 51 ++++- neutron/tests/unit/extensions/test_qos_fip.py | 3 +- neutron/tests/unit/fake_resources.py | 1 + .../mech_driver/ovsdb/extensions/test_qos.py | 181 +++++++++++++++--- .../tests/unit/services/ovn_l3/test_plugin.py | 40 +++- requirements.txt | 2 +- 15 files changed, 380 insertions(+), 97 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index f671fa08af7..a5d28d7daa1 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -80,7 +80,7 @@ oslo.versionedobjects==1.35.1 oslotest==3.2.0 osprofiler==2.3.0 ovs==2.8.0 -ovsdbapp==1.3.0 +ovsdbapp==1.4.0 Paste==2.0.2 PasteDeploy==1.5.0 pbr==4.0.0 diff --git a/neutron/common/ovn/constants.py b/neutron/common/ovn/constants.py index cc557542b2c..7cb122c0302 100644 --- a/neutron/common/ovn/constants.py +++ b/neutron/common/ovn/constants.py @@ -39,6 +39,7 @@ OVN_CIDRS_EXT_ID_KEY = 'neutron:cidrs' OVN_FIP_EXT_ID_KEY = 'neutron:fip_id' OVN_FIP_PORT_EXT_ID_KEY = 'neutron:fip_port_id' OVN_FIP_EXT_MAC_KEY = 'neutron:fip_external_mac' +OVN_FIP_NET_ID = 'neutron:fip_network_id' OVN_REV_NUM_EXT_ID_KEY = 'neutron:revision_number' OVN_QOS_POLICY_EXT_ID_KEY = 'neutron:qos_policy_id' OVN_SG_IDS_EXT_ID_KEY = 'neutron:security_group_ids' diff --git a/neutron/common/ovn/extensions.py b/neutron/common/ovn/extensions.py index ab88c7332f3..c1dbc6a08ca 100644 --- a/neutron/common/ovn/extensions.py +++ b/neutron/common/ovn/extensions.py @@ -17,6 +17,12 @@ from neutron_lib.api.definitions import availability_zone as az_def from neutron_lib.api.definitions import expose_port_forwarding_in_fip from neutron_lib.api.definitions import fip_pf_description from neutron_lib.api.definitions import floating_ip_port_forwarding +from neutron_lib.api.definitions import port_resource_request +from neutron_lib.api.definitions import qos +from neutron_lib.api.definitions import qos_bw_limit_direction +from neutron_lib.api.definitions import qos_default +from neutron_lib.api.definitions import qos_rule_type_details +from neutron_lib.api.definitions import qos_rules_alias from neutron_lib.api.definitions import router_availability_zone as raz_def from neutron_lib.api.definitions import segment as seg_def @@ -32,6 +38,7 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [ 'ext-gw-mode', 'fip-port-details', 'pagination', + 'qos-fip', 'sorting', 'project-id', 'dns-integration', @@ -54,6 +61,12 @@ ML2_SUPPORTED_API_EXTENSIONS = [ 'network-ip-availability', 'port-security', 'provider', + port_resource_request.ALIAS, + qos.ALIAS, + qos_bw_limit_direction.ALIAS, + qos_default.ALIAS, + qos_rule_type_details.ALIAS, + qos_rules_alias.ALIAS, 'quotas', 'rbac-address-scope', 'rbac-policies', diff --git a/neutron/common/ovn/utils.py b/neutron/common/ovn/utils.py index 6567573b1f7..981324c6566 100644 --- a/neutron/common/ovn/utils.py +++ b/neutron/common/ovn/utils.py @@ -74,6 +74,12 @@ def ovn_lrouter_port_name(id): return constants.LRP_PREFIX + '%s' % id +def ovn_cr_lrouter_port_name(_id): + # The name of the OVN chassisredirect lrouter port entry will be + # cr-lrp- + return 'cr-lrp-%s' % _id + + def ovn_provnet_port_name(network_id): # The name of OVN lswitch provider network port entry will be # provnet-. The port is created for network having diff --git a/neutron/extensions/qos_fip.py b/neutron/extensions/qos_fip.py index 27b17dd33ce..cbd42148343 100644 --- a/neutron/extensions/qos_fip.py +++ b/neutron/extensions/qos_fip.py @@ -12,6 +12,7 @@ # under the License. from neutron_lib.api.definitions import l3 +from neutron_lib.api.definitions import qos from neutron_lib.api import extensions from neutron_lib.services.qos import constants as qos_consts @@ -26,6 +27,7 @@ EXTENDED_ATTRIBUTES_2_0 = { 'validate': {'type:uuid_or_none': None}} } } +REQUIRED_EXTENSIONS = [l3.ALIAS, qos.ALIAS] class Qos_fip(extensions.ExtensionDescriptor): @@ -48,7 +50,7 @@ class Qos_fip(extensions.ExtensionDescriptor): return "2017-07-20T00:00:00-00:00" def get_required_extensions(self): - return ["router", "qos"] + return REQUIRED_EXTENSIONS def get_extended_resources(self, version): if version == "2.0": diff --git a/neutron/objects/qos/policy.py b/neutron/objects/qos/policy.py index 14c22feb464..a0651c4501a 100644 --- a/neutron/objects/qos/policy.py +++ b/neutron/objects/qos/policy.py @@ -335,12 +335,12 @@ class QosPolicy(rbac_db.NeutronRbacObject): self.id) def get_bound_floatingips(self): - return binding.QosPolicyFloatingIPBinding.get_objects(self.obj_context, - self.id) + return binding.QosPolicyFloatingIPBinding.get_objects( + self.obj_context, policy_id=self.id) def get_bound_routers(self): return binding.QosPolicyRouterGatewayIPBinding.get_objects( - self.obj_context, self.id) + self.obj_context, policy_id=self.id) @classmethod def _get_bound_tenant_ids(cls, session, binding_db, bound_db, 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 38ee1e9a20a..fda6a3d6756 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 @@ -19,10 +19,12 @@ from neutron.objects.qos import policy as qos_policy from neutron.objects.qos import rule as qos_rule from neutron_lib import constants from neutron_lib import context as n_context +from neutron_lib.plugins import constants as plugins_const from neutron_lib.plugins import directory from neutron_lib.services.qos import constants as qos_consts from oslo_log import log as logging +from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import utils @@ -38,6 +40,7 @@ class OVNClientQosExtension(object): super(OVNClientQosExtension, self).__init__() self._driver = driver self._plugin_property = None + self._plugin_l3_property = None @property def _plugin(self): @@ -45,6 +48,12 @@ class OVNClientQosExtension(object): self._plugin_property = directory.get_plugin() return self._plugin_property + @property + def _plugin_l3(self): + if self._plugin_l3_property is None: + self._plugin_l3_property = directory.get_plugin(plugins_const.L3) + return self._plugin_l3_property + @staticmethod def _qos_rules(context, policy_id): """QoS Neutron rules classified per direction and type @@ -81,8 +90,25 @@ class OVNClientQosExtension(object): 'policy_id': policy_id}) return qos_rules + @staticmethod + def _ovn_qos_rule_match(direction, port_id, ip_address): + if direction == constants.EGRESS_DIRECTION: + in_or_out = 'inport' + src_or_dst = 'src' + else: + in_or_out = 'outport' + src_or_dst = 'dst' + + match = '%s == "%s"' % (in_or_out, port_id) + if ip_address: + match += (' && ip4.%s == %s && is_chassis_resident("%s")' % + (src_or_dst, ip_address, + utils.ovn_cr_lrouter_port_name(port_id))) + + return match + def _ovn_qos_rule(self, rules_direction, rules, port_id, network_id, - delete=False): + fip_id=None, ip_address=None, delete=False): """Generate an OVN QoS register based on several Neutron QoS rules A OVN QoS register can contain "bandwidth" and "action" parameters. @@ -96,8 +122,13 @@ class OVNClientQosExtension(object): :param rules_direction: (string) rules direction (egress, ingress). :param rules: (dict) {bw_limit: {max_kbps, max_burst_kbps}, dscp: {dscp_mark}} - :param port_id: (string) port ID. + :param port_id: (string) port ID; for L3 floating IP bandwidth + limit this is the router gateway port ID. :param network_id: (string) network ID. + :param fip_id: (string) floating IP ID, for L3 floating IP bandwidth + limit. + :param ip_address: (string) IP address, for L3 floating IP bandwidth + limit. :param delete: (bool) defines if this rule if going to be a partial one (without any bandwidth or DSCP information) to be used only as deletion rule. @@ -108,17 +139,17 @@ class OVNClientQosExtension(object): return lswitch_name = utils.ovn_name(network_id) - - if rules_direction == constants.EGRESS_DIRECTION: - direction = 'from-lport' - match = 'inport == "{}"'.format(port_id) - else: - direction = 'to-lport' - match = 'outport == "{}"'.format(port_id) + direction = ( + 'from-lport' if rules_direction == constants.EGRESS_DIRECTION else + 'to-lport') + match = self._ovn_qos_rule_match(rules_direction, port_id, ip_address) 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. @@ -234,6 +265,42 @@ class OVNClientQosExtension(object): return updated_port_ids + def create_floatingip(self, txn, floatingip): + self.update_floatingip(txn, floatingip) + + def update_floatingip(self, txn, floatingip): + router_id = floatingip.get('router_id') + qos_policy_id = floatingip.get('qos_policy_id') + if floatingip['floating_network_id']: + lswitch_name = utils.ovn_name(floatingip['floating_network_id']) + txn.add(self._driver._nb_idl.qos_del_ext_ids( + lswitch_name, + {ovn_const.OVN_FIP_EXT_ID_KEY: floatingip['id']})) + + if not (router_id and qos_policy_id): + return + + admin_context = n_context.get_admin_context() + router_db = self._plugin_l3._get_router(admin_context, router_id) + gw_port_id = router_db.get('gw_port_id') + if not gw_port_id: + return + + qos_rules = self._qos_rules(admin_context, qos_policy_id) + for direction, rules in qos_rules.items(): + ovn_rule = self._ovn_qos_rule( + direction, rules, gw_port_id, + floatingip['floating_network_id'], fip_id=floatingip['id'], + ip_address=floatingip['floating_ip_address']) + if ovn_rule: + txn.add(self._driver._nb_idl.qos_add(**ovn_rule)) + + def delete_floatingip(self, txn, floatingip): + self.update_floatingip(txn, floatingip) + + def disassociate_floatingip(self, txn, floatingip): + self.delete_floatingip(txn, floatingip) + def update_policy(self, context, policy): updated_port_ids = set([]) bound_networks = policy.get_bound_networks() @@ -256,3 +323,8 @@ class OVNClientQosExtension(object): filters={'id': port_ids}): self.update_port(txn, port, {}, reset=True, qos_rules=qos_rules) + + for fip_binding in policy.get_bound_floatingips(): + fip = self._plugin_l3.get_floatingip(context, + fip_binding.fip_id) + self.update_floatingip(txn, fip) diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index 0594ac50713..bd5b51c0581 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -648,7 +648,8 @@ class OVNClient(object): floatingip, ovn_const.TYPE_FLOATINGIPS)), ovn_const.OVN_FIP_PORT_EXT_ID_KEY: floatingip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: gw_lrouter_name, - ovn_const.OVN_FIP_EXT_MAC_KEY: port_db['mac_address']} + ovn_const.OVN_FIP_EXT_MAC_KEY: port_db['mac_address'], + ovn_const.OVN_FIP_NET_ID: floatingip['floating_network_id']} columns = {'type': 'dnat_and_snat', 'logical_ip': floatingip['fixed_ip_address'], 'external_ip': floatingip['floating_ip_address'], @@ -884,7 +885,9 @@ class OVNClient(object): def create_floatingip(self, context, floatingip): try: - self._create_or_update_floatingip(floatingip) + with self._nb_idl.transaction(check_error=True) as txn: + self._create_or_update_floatingip(floatingip, txn=txn) + self._qos_driver.create_floatingip(txn, floatingip) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error('Unable to create floating ip in gateway ' @@ -901,21 +904,11 @@ class OVNClient(object): n_context.get_admin_context(), floatingip['id'], const.FLOATINGIP_STATUS_ACTIVE) - # TODO(lucasagomes): The ``fip_object`` parameter was added to - # keep things backward compatible since old FIPs might not have - # the OVN_FIP_EXT_ID_KEY in their external_ids field. Remove it - # in the Rocky release. - def update_floatingip(self, context, floatingip, fip_object=None): + def update_floatingip(self, context, floatingip): fip_status = None router_id = None ovn_fip = self._nb_idl.get_floatingip(floatingip['id']) - if not ovn_fip and fip_object: - router_id = fip_object.get('router_id') - ovn_fip = self._nb_idl.get_floatingip_by_ips( - router_id, fip_object['fixed_ip_address'], - fip_object['floating_ip_address']) - check_rev_cmd = self._nb_idl.check_revision_number( floatingip['id'], floatingip, ovn_const.TYPE_FLOATINGIPS) with self._nb_idl.transaction(check_error=True) as txn: @@ -931,6 +924,8 @@ class OVNClient(object): self._create_or_update_floatingip(floatingip, txn=txn) fip_status = const.FLOATINGIP_STATUS_ACTIVE + self._qos_driver.update_floatingip(txn, floatingip) + if check_rev_cmd.result == ovn_const.TXN_COMMITTED: db_rev.bump_revision( context, floatingip, ovn_const.TYPE_FLOATINGIPS) @@ -939,26 +934,20 @@ class OVNClient(object): self._l3_plugin.update_floatingip_status( context, floatingip['id'], fip_status) - # TODO(lucasagomes): The ``fip_object`` parameter was added to - # keep things backward compatible since old FIPs might not have - # the OVN_FIP_EXT_ID_KEY in their external_ids field. Remove it - # in the Rocky release. - def delete_floatingip(self, context, fip_id, fip_object=None): + def delete_floatingip(self, context, fip_id): router_id = None ovn_fip = self._nb_idl.get_floatingip(fip_id) - if not ovn_fip and fip_object: - router_id = fip_object.get('router_id') - ovn_fip = self._nb_idl.get_floatingip_by_ips( - router_id, fip_object['fixed_ip_address'], - fip_object['floating_ip_address']) - if ovn_fip: lrouter = ovn_fip['external_ids'].get( ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY, utils.ovn_name(router_id)) + fip_net_id = ovn_fip['external_ids'].get(ovn_const.OVN_FIP_NET_ID) + fip_dict = {'floating_network_id': fip_net_id, 'id': fip_id} try: - self._delete_floatingip(ovn_fip, lrouter) + with self._nb_idl.transaction(check_error=True) as txn: + self._delete_floatingip(ovn_fip, lrouter, txn=txn) + self._qos_driver.delete_floatingip(txn, fip_dict) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error('Unable to delete floating ip in gateway ' diff --git a/neutron/services/ovn_l3/plugin.py b/neutron/services/ovn_l3/plugin.py index fddf66cbc5b..63c8122e964 100644 --- a/neutron/services/ovn_l3/plugin.py +++ b/neutron/services/ovn_l3/plugin.py @@ -20,6 +20,7 @@ from neutron.quota import resource_registry from neutron_lib.api.definitions import external_net from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import provider_net as pnet +from neutron_lib.api.definitions import qos as qos_api from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources @@ -30,6 +31,7 @@ from neutron_lib.exceptions import availability_zone as az_exc from neutron_lib.plugins import constants as plugin_constants from neutron_lib.plugins import directory from neutron_lib.services import base as service_base +from oslo_config import cfg from oslo_log import log from oslo_utils import excutils @@ -38,7 +40,9 @@ from neutron.common.ovn import extensions from neutron.common.ovn import utils from neutron.db.availability_zone import router as router_az_db from neutron.db import l3_fip_port_details +from neutron.db import l3_fip_qos from neutron.db import ovn_revision_numbers_db as db_rev +from neutron.extensions import qos_fip as qos_fip_api from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client from neutron.scheduler import l3_ovn_scheduler from neutron.services.portforwarding.drivers.ovn import driver \ @@ -56,7 +60,8 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase, l3_gwmode_db.L3_NAT_db_mixin, dns_db.DNSDbMixin, l3_fip_port_details.Fip_port_details_db_mixin, - router_az_db.RouterAvailabilityZoneMixin): + router_az_db.RouterAvailabilityZoneMixin, + l3_fip_qos.FloatingQoSDbMixin): """Implementation of the OVN L3 Router Service Plugin. This class implements a L3 service plugin that provides @@ -66,7 +71,7 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase, # TODO(mjozefcz): Start consuming it from neutron-lib # once available. - supported_extension_aliases = ( + _supported_extension_aliases = ( extensions.ML2_SUPPORTED_API_EXTENSIONS_OVN_L3) @resource_registry.tracked_resources(router=l3_models.Router, @@ -89,6 +94,19 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase, self.create_floatingip_precommit, resources.FLOATING_IP, events.PRECOMMIT_CREATE) + @staticmethod + def disable_qos_fip_extension_by_extension_drivers(aliases): + if (qos_api.ALIAS not in cfg.CONF.ml2.extension_drivers and + qos_fip_api.FIP_QOS_ALIAS in aliases): + aliases.remove(qos_fip_api.FIP_QOS_ALIAS) + + @property + def supported_extension_aliases(self): + if not hasattr(self, '_aliases'): + self._aliases = self._supported_extension_aliases[:] + self.disable_qos_fip_extension_by_extension_drivers(self._aliases) + return self._aliases + @property def _ovn_client(self): if self._ovn_client_inst is None: @@ -240,25 +258,13 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase, return fip def delete_floatingip(self, context, id): - # TODO(lucasagomes): Passing ``original_fip`` object as a - # parameter to the OVNClient's delete_floatingip() method is done - # for backward-compatible reasons. Remove it in the Rocky release - # of OpenStack. - original_fip = self.get_floatingip(context, id) super(OVNL3RouterPlugin, self).delete_floatingip(context, id) - self._ovn_client.delete_floatingip(context, id, - fip_object=original_fip) + self._ovn_client.delete_floatingip(context, id) def update_floatingip(self, context, id, floatingip): - # TODO(lucasagomes): Passing ``original_fip`` object as a - # parameter to the OVNClient's update_floatingip() method is done - # for backward-compatible reasons. Remove it in the Rocky release - # of OpenStack. - original_fip = self.get_floatingip(context, id) fip = super(OVNL3RouterPlugin, self).update_floatingip(context, id, floatingip) - self._ovn_client.update_floatingip(context, fip, - fip_object=original_fip) + self._ovn_client.update_floatingip(context, fip) return fip def update_floatingip_status(self, context, floatingip_id, status): diff --git a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py index 32a159ca92e..7ee7801af20 100644 --- a/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py +++ b/neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py @@ -12,12 +12,14 @@ # License for the specific language governing permissions and limitations # under the License. +import copy from unittest import mock from neutron_lib import constants from neutron_lib.services.qos import constants as qos_constants from neutron.common.ovn import utils as ovn_utils +from neutron.db import l3_db from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \ import qos as qos_extension from neutron.tests.functional import base @@ -63,19 +65,32 @@ class TestOVNClientQosExtension(base.TestOVNFunctionalBase): self._add_logical_switch() _ovn_client = _OVNClient(self.nb_api) self.qos_driver = qos_extension.OVNClientQosExtension(_ovn_client) + self.gw_port_id = 'gw_port_id' + self._mock_get_router = mock.patch.object(l3_db.L3_NAT_dbonly_mixin, + '_get_router') + self.mock_get_router = self._mock_get_router.start() + self.mock_get_router.return_value = {'gw_port_id': self.gw_port_id} + self._mock_qos_rules = mock.patch.object(self.qos_driver, + '_qos_rules') + self.mock_qos_rules = self._mock_qos_rules.start() + self.fip = {'router_id': 'router_id', 'qos_policy_id': 'qos_policy_id', + 'floating_network_id': self.network_1, + 'id': 'fip_id', 'floating_ip_address': '1.2.3.4'} def _add_logical_switch(self): self.network_1 = 'network_1' with self.nb_api.transaction(check_error=True) as txn: txn.add(self.nb_api.ls_add(ovn_utils.ovn_name(self.network_1))) - def _check_rules(self, rules, port_id, network_id): + def _check_rules(self, rules, port_id, network_id, fip_id=None, + ip_address=None): egress_ovn_rule = self.qos_driver._ovn_qos_rule( constants.EGRESS_DIRECTION, rules.get(constants.EGRESS_DIRECTION), - port_id, network_id) + port_id, network_id, fip_id=fip_id, ip_address=ip_address) ingress_ovn_rule = self.qos_driver._ovn_qos_rule( constants.INGRESS_DIRECTION, - rules.get(constants.INGRESS_DIRECTION), port_id, network_id) + rules.get(constants.INGRESS_DIRECTION), port_id, network_id, + fip_id=fip_id, ip_address=ip_address) with self.nb_api.transaction(check_error=True): ls = self.qos_driver._driver._nb_idl.lookup( @@ -100,10 +115,8 @@ class TestOVNClientQosExtension(base.TestOVNFunctionalBase): port = 'port1' def update_and_check(qos_rules): - with self.nb_api.transaction(check_error=True) as txn, \ - mock.patch.object(self.qos_driver, - '_qos_rules') as mock_rules: - mock_rules.return_value = qos_rules + with self.nb_api.transaction(check_error=True) as txn: + self.mock_qos_rules.return_value = qos_rules self.qos_driver._update_port_qos_rules( txn, port, self.network_1, 'qos1', None) self._check_rules(qos_rules, port, self.network_1) @@ -112,3 +125,27 @@ class TestOVNClientQosExtension(base.TestOVNFunctionalBase): update_and_check(QOS_RULES_2) update_and_check(QOS_RULES_3) update_and_check({}) + + def _update_fip_and_check(self, fip, qos_rules): + with self.nb_api.transaction(check_error=True) as txn: + self.mock_qos_rules.return_value = qos_rules + self.qos_driver.update_floatingip(txn, fip) + self._check_rules(qos_rules, self.gw_port_id, self.network_1, + fip_id='fip_id', ip_address='1.2.3.4') + + def test_create_floatingip(self): + self._update_fip_and_check(self.fip, QOS_RULES_1) + + def test_update_floatingip(self): + fip_updated = copy.deepcopy(self.fip) + fip_updated['qos_policy_id'] = 'another_qos_policy' + self._update_fip_and_check(self.fip, QOS_RULES_1) + self._update_fip_and_check(fip_updated, QOS_RULES_2) + self._update_fip_and_check(fip_updated, QOS_RULES_3) + self._update_fip_and_check(fip_updated, {}) + + def test_delete_floatingip(self): + self._update_fip_and_check(self.fip, QOS_RULES_1) + fip_dict = {'floating_network_id': self.fip['floating_network_id'], + 'id': self.fip['id']} + self._update_fip_and_check(fip_dict, {}) diff --git a/neutron/tests/unit/extensions/test_qos_fip.py b/neutron/tests/unit/extensions/test_qos_fip.py index da545d0bf83..eaab2b158f9 100644 --- a/neutron/tests/unit/extensions/test_qos_fip.py +++ b/neutron/tests/unit/extensions/test_qos_fip.py @@ -51,7 +51,8 @@ class TestFloatingIPQoSIntPlugin( class TestFloatingIPQoSL3NatServicePlugin( test_l3.TestL3NatServicePlugin, l3_fip_qos.FloatingQoSDbMixin): - supported_extension_aliases = [l3_apidef.ALIAS, qos_fip.FIP_QOS_ALIAS] + supported_extension_aliases = [l3_apidef.ALIAS, 'qos', + qos_fip.FIP_QOS_ALIAS] class FloatingIPQoSDBTestCaseBase(object): diff --git a/neutron/tests/unit/fake_resources.py b/neutron/tests/unit/fake_resources.py index 86157d31c2a..e53ff170b58 100644 --- a/neutron/tests/unit/fake_resources.py +++ b/neutron/tests/unit/fake_resources.py @@ -146,6 +146,7 @@ class FakeOvsdbNbOvnIdl(object): self.ls_get = mock.Mock() self.check_liveness = mock.Mock() self.ha_chassis_group_get = mock.Mock() + self.qos_del_ext_ids = mock.Mock() class FakeOvsdbSbOvnIdl(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 28886d46e28..5ebc7a3c1c5 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 @@ -15,18 +15,21 @@ from unittest import mock import netaddr +from neutron_lib.api.definitions import qos as qos_api from neutron_lib import constants from neutron_lib import context from neutron_lib.services.qos import constants as qos_constants from oslo_config import cfg from oslo_utils import uuidutils +from neutron.api import extensions +from neutron.common.ovn import constants as ovn_const from neutron.core_extensions import qos as core_qos -from neutron import manager from neutron.objects import network as network_obj from neutron.objects import ports as port_obj from neutron.objects.qos import policy as policy_obj from neutron.objects.qos import rule as rule_obj +from neutron.objects import router as router_obj from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \ import qos as qos_extension from neutron.tests.unit.plugins.ml2 import test_plugin @@ -51,19 +54,20 @@ class _Context(object): class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): CORE_PLUGIN_CLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin' - _extension_drivers = ['qos'] + _extension_drivers = [qos_api.ALIAS] + l3_plugin = ('neutron.tests.unit.extensions.test_qos_fip.' + 'TestFloatingIPQoSL3NatServicePlugin') def setUp(self): cfg.CONF.set_override('extension_drivers', self._extension_drivers, group='ml2') - cfg.CONF.set_override('service_plugins', self._extension_drivers) + extensions.register_custom_supported_check(qos_api.ALIAS, lambda: True, + plugin_agnostic=True) super(TestOVNClientQosExtension, self).setUp() self.setup_coreplugin(self.CORE_PLUGIN_CLASS, load_plugins=True) - manager.init() self._mock_qos_loaded = mock.patch.object( core_qos.QosCoreResourceExtension, 'plugin_loaded') self.mock_qos_loaded = self._mock_qos_loaded.start() - self.txn = _Context() mock_driver = mock.Mock() mock_driver._nb_idl.transaction.return_value = self.txn @@ -81,10 +85,34 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): return obj_cls.modify_fields_to_db( self.get_random_object_fields(obj_cls)) + def _create_one_port(self, mac_address_int, network_id): + mac_address = netaddr.EUI(mac_address_int) + port = port_obj.Port( + self.ctx, project_id=self.project_id, + network_id=network_id, device_owner='', + admin_state_up=True, status='DOWN', device_id='2', + mac_address=mac_address) + port.create() + return port + + def _create_one_router(self): + self.router_gw_port = self._create_one_port(2000, self.fips_network.id) + self.router = router_obj.Router(self.ctx, id=uuidutils.generate_uuid(), + gw_port_id=self.router_gw_port.id) + self.router.create() + def _initialize_objs(self): self.qos_policies = [] self.ports = [] self.networks = [] + self.fips = [] + self.fips_network = network_obj.Network( + self.ctx, id=uuidutils.generate_uuid(), project_id=self.project_id) + self.fips_network.create() + self._create_one_router() + self.fips_ports = [] + fip_cidr = netaddr.IPNetwork('10.10.0.0/24') + for net_idx in range(2): qos_policy = policy_obj.QosPolicy( self.ctx, id=uuidutils.generate_uuid(), @@ -96,10 +124,21 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): # the port dictionary extended with the QoS policy information; see # QoSPlugin._extend_port_resource_request qos_rule = rule_obj.QosDscpMarkingRule( - self.ctx, dscp=20, id=uuidutils.generate_uuid(), + self.ctx, dscp_mark=20, id=uuidutils.generate_uuid(), qos_policy_id=qos_policy.id) qos_rule.create() + self.fips_ports.append(self._create_one_port(1000 + net_idx, + self.fips_network.id)) + fip_ip = str(netaddr.IPAddress(fip_cidr.ip + net_idx + 1)) + fip = router_obj.FloatingIP( + self.ctx, id=uuidutils.generate_uuid(), + project_id=self.project_id, floating_ip_address=fip_ip, + floating_network_id=self.fips_network.id, + floating_port_id=self.fips_ports[-1].id) + fip.create() + self.fips.append(fip) + network = network_obj.Network( self.ctx, id=uuidutils.generate_uuid(), project_id=self.project_id) @@ -107,14 +146,8 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.networks.append(network) for port_idx in range(3): - mac_address = netaddr.EUI(net_idx * 16 + port_idx) - port = port_obj.Port( - self.ctx, project_id=self.project_id, - network_id=network.id, device_owner='', - admin_state_up=True, status='DOWN', device_id='2', - mac_address=mac_address) - port.create() - self.ports.append(port) + self.ports.append( + self._create_one_port(net_idx * 16 + port_idx, network.id)) @mock.patch.object(qos_extension.LOG, 'warning') @mock.patch.object(rule_obj, 'get_rules') @@ -150,36 +183,59 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.assertEqual(expected, self.qos_driver._qos_rules(mock.ANY, mock.ANY)) - def test__ovn_qos_rule_ingress(self): + def _test__ovn_qos_rule_ingress(self, fip_id=None, ip_address=None): direction = constants.INGRESS_DIRECTION rule = {qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1} + match = self.qos_driver._ovn_qos_rule_match( + direction, 'port_id', ip_address) expected = {'burst': 100, 'rate': 200, 'direction': 'to-lport', - 'match': 'outport == "port_id"', + '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} result = self.qos_driver._ovn_qos_rule( - direction, rule, 'port_id', 'network_id') + direction, rule, 'port_id', 'network_id', fip_id=fip_id, + ip_address=ip_address) self.assertEqual(expected, result) - def test__ovn_qos_rule_egress(self): + def test__ovn_qos_rule_ingress(self): + self._test__ovn_qos_rule_ingress() + + def test__ovn_qos_rule_ingress_fip(self): + 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): direction = constants.EGRESS_DIRECTION rule = {qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1} - expected = {'direction': 'from-lport', 'match': 'inport == "port_id"', + match = self.qos_driver._ovn_qos_rule_match( + direction, 'port_id', ip_address) + 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} result = self.qos_driver._ovn_qos_rule( - direction, rule, 'port_id', 'network_id') + direction, rule, 'port_id', 'network_id', fip_id, ip_address) self.assertEqual(expected, result) rule = {qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_2, qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_2} - expected = {'direction': 'from-lport', 'match': 'inport == "port_id"', + 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} result = self.qos_driver._ovn_qos_rule( - direction, rule, 'port_id', 'network_id') + direction, rule, 'port_id', 'network_id', fip_id, ip_address) self.assertEqual(expected, result) + def test__ovn_qos_rule_egress(self): + self._test__ovn_qos_rule_egress() + + def test__ovn_qos_rule_egress_fip(self): + self._test__ovn_qos_rule_egress(fip_id='fipid', ip_address='1.2.3.4') + def test__port_effective_qos_policy_id(self): port = {'qos_policy_id': 'qos1'} self.assertEqual(('qos1', 'port'), @@ -349,6 +405,8 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): - port21: qos_policy0 --> handled during "update_network", not updated handled during "update_port" and updated - port22: qos_policy1 --> handled during "update_network", not updated + fip1: qos_policy0 + fip2: qos_policy1 """ self.ports[1].qos_policy_id = self.qos_policies[0].id self.ports[1].update() @@ -360,9 +418,15 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.ports[5].update() self.networks[1].qos_policy_id = self.qos_policies[0].id self.networks[1].update() + self.fips[0].qos_policy_id = self.qos_policies[0].id + self.fips[0].update() + self.fips[1].qos_policy_id = self.qos_policies[1].id + self.fips[1].update() mock_qos_rules = mock.Mock() with mock.patch.object(self.qos_driver, '_qos_rules', - return_value=mock_qos_rules): + return_value=mock_qos_rules), \ + mock.patch.object(self.qos_driver, 'update_floatingip') as \ + mock_update_fip: self.qos_driver.update_policy(self.ctx, self.qos_policies[0]) updated_ports = [self.ports[1], self.ports[3], self.ports[4]] calls = [mock.call(self.txn, port.id, port.network_id, @@ -371,3 +435,74 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): # We can't ensure the call order because we are not enforcing any order # when retrieving the port and the network list. self.mock_rules.assert_has_calls(calls, any_order=True) + fip = self.qos_driver._plugin_l3._make_floatingip_dict(self.fips[0]) + mock_update_fip.asssert_called_once_with(self.txn, fip, reset=True) + + def test_update_floatingip(self): + nb_idl = self.qos_driver._driver._nb_idl + fip = self.fips[0] + original_fip = self.fips[1] + txn = mock.Mock() + + # Update FIP, no QoS policy nor port/router + self.qos_driver.update_floatingip(txn, fip) + nb_idl.qos_del_ext_ids.assert_called_once() + nb_idl.qos_add.assert_not_called() + nb_idl.reset_mock() + + # Attach a port and a router, not QoS policy + fip.router_id = self.router.id + fip.fixed_port_id = self.fips_ports[0].id + fip.update() + self.qos_driver.update_floatingip(txn, fip) + nb_idl.qos_del_ext_ids.assert_called_once() + nb_idl.qos_add.assert_not_called() + nb_idl.reset_mock() + + # Add a QoS policy + fip.qos_policy_id = self.qos_policies[0].id + fip.update() + self.qos_driver.update_floatingip(txn, fip) + nb_idl.qos_del_ext_ids.assert_called_once() + nb_idl.qos_add.assert_called_once() + nb_idl.reset_mock() + + # Remove QoS + fip.qos_policy_id = None + fip.update() + original_fip.qos_policy_id = self.qos_policies[0].id + original_fip.update() + self.qos_driver.update_floatingip(txn, fip) + nb_idl.qos_del_ext_ids.assert_called_once() + nb_idl.qos_add.assert_not_called() + nb_idl.reset_mock() + + # Add again another QoS policy + fip.qos_policy_id = self.qos_policies[1].id + fip.update() + original_fip.qos_policy_id = None + original_fip.update() + self.qos_driver.update_floatingip(txn, fip) + nb_idl.qos_del_ext_ids.assert_called_once() + nb_idl.qos_add.assert_called_once() + nb_idl.reset_mock() + + # Detach the port and the router + fip.router_id = None + fip.fixed_port_id = None + fip.update() + original_fip.router_id = self.router.id + original_fip.fixed_port_id = self.fips_ports[0].id + original_fip.qos_policy_id = self.qos_policies[1].id + original_fip.update() + self.qos_driver.update_floatingip(txn, fip) + nb_idl.qos_del_ext_ids.assert_called_once() + nb_idl.qos_add.assert_not_called() + nb_idl.reset_mock() + + # Force reset (delete any QoS) + fip_dict = {'floating_network_id': fip.floating_network_id, + 'id': fip.id} + self.qos_driver.update_floatingip(txn, fip_dict) + nb_idl.qos_del_ext_ids.assert_called_once() + nb_idl.qos_add.assert_not_called() diff --git a/neutron/tests/unit/services/ovn_l3/test_plugin.py b/neutron/tests/unit/services/ovn_l3/test_plugin.py index e44ed75fcd5..486294f7a58 100644 --- a/neutron/tests/unit/services/ovn_l3/test_plugin.py +++ b/neutron/tests/unit/services/ovn_l3/test_plugin.py @@ -911,7 +911,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-router-id', type='dnat_and_snat', @@ -937,7 +939,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05'} + ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10', external_ip='192.168.0.10', external_mac='00:01:02:03:04:05', @@ -963,7 +967,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05'} + ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-router-id', type='dnat_and_snat', logical_ip='10.0.0.10', external_ip='192.168.0.10', @@ -984,7 +990,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-router-id', type='dnat_and_snat', @@ -1010,7 +1018,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-router-id', type='dnat_and_snat', @@ -1167,7 +1177,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip_new['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', @@ -1191,7 +1203,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip_new['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', @@ -1225,7 +1239,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip_new['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05'} + ovn_const.OVN_FIP_EXT_MAC_KEY: '00:01:02:03:04:05', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', logical_ip='10.10.10.10', external_ip='192.168.0.10', @@ -1254,7 +1270,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip_new['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', @@ -1287,7 +1305,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase): self.fake_floating_ip_new['port_id'], ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: utils.ovn_name( self.fake_floating_ip_new['router_id']), - ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa'} + ovn_const.OVN_FIP_EXT_MAC_KEY: 'aa:aa:aa:aa:aa:aa', + ovn_const.OVN_FIP_NET_ID: + self.fake_floating_ip['floating_network_id']} self.l3_inst._ovn.add_nat_rule_in_lrouter.assert_called_once_with( 'neutron-new-router-id', type='dnat_and_snat', diff --git a/requirements.txt b/requirements.txt index f476931b35a..cf11313897e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,7 +45,7 @@ oslo.versionedobjects>=1.35.1 # Apache-2.0 osprofiler>=2.3.0 # Apache-2.0 os-ken >= 0.3.0 # Apache-2.0 ovs>=2.8.0 # Apache-2.0 -ovsdbapp>=1.3.0 # Apache-2.0 +ovsdbapp>=1.4.0 # Apache-2.0 psutil>=3.2.2 # BSD pyroute2>=0.5.13;sys_platform!='win32' # Apache-2.0 (+ dual licensed GPL2) pyOpenSSL>=17.1.0 # Apache-2.0