From 8dfe5cc95ba8d27ff59f710a34418d46a0d25144 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Fri, 12 Nov 2021 14:31:58 +0000 Subject: [PATCH] [OVN] Implement floating IP network QoS inheritance Floating IP now have information of the QoS policy of the external network. The OVN QoS extension will use this network QoS policy if there is no floating IP QoS policy. Partial-Bug: #1950454 Change-Id: I380a130d97e8bfe54caa5f3a129877507d1ce2a6 --- neutron/objects/qos/binding.py | 20 ++++++++ neutron/objects/qos/policy.py | 4 +- .../ovn/mech_driver/ovsdb/extensions/qos.py | 48 +++++++++++++------ .../mech_driver/ovsdb/extensions/test_qos.py | 39 +++++++++++++-- ...tance-support-in-ovn-1d68b54c42c865da.yaml | 7 +++ 5 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/qos-fip-network-inheritance-support-in-ovn-1d68b54c42c865da.yaml diff --git a/neutron/objects/qos/binding.py b/neutron/objects/qos/binding.py index b0e63549f1f..034c000a667 100644 --- a/neutron/objects/qos/binding.py +++ b/neutron/objects/qos/binding.py @@ -19,6 +19,7 @@ from neutron_lib.objects import common_types from sqlalchemy import and_ from sqlalchemy import exists +from neutron.db.models import l3 as models_l3 from neutron.db import models_v2 from neutron.db.qos import models as qos_db_model from neutron.objects import base @@ -101,6 +102,25 @@ class QosPolicyFloatingIPBinding(base.NeutronDbObject, _QosPolicyBindingMixin): fields_no_update = ['policy_id', 'fip_id'] _bound_model_id = db_model.fip_id + @classmethod + def get_fips_by_network_id(cls, context, network_id, policy_id=None): + """Return the FIP belonging to a network, filtered by a QoS policy + + This method returns the floating IPs belonging to a network, with a + QoS policy associated. If no QoS policy is passed, this method returns + all floating IPs without any QoS policy associated. + """ + query = context.session.query(models_l3.FloatingIP).filter( + models_l3.FloatingIP.floating_network_id == network_id) + if policy_id: + query = query.filter(exists().where(and_( + cls.db_model.fip_id == models_l3.FloatingIP.id, + cls.db_model.policy_id == policy_id))) + else: + query = query.filter(~exists().where( + cls.db_model.fip_id == models_l3.FloatingIP.id)) + return query.all() + @base.NeutronObjectRegistry.register class QosPolicyRouterGatewayIPBinding(base.NeutronDbObject, diff --git a/neutron/objects/qos/policy.py b/neutron/objects/qos/policy.py index 1f98d8f0d74..abb0318f6ec 100644 --- a/neutron/objects/qos/policy.py +++ b/neutron/objects/qos/policy.py @@ -333,8 +333,8 @@ class QosPolicy(rbac_db.NeutronRbacObject): self.id) def get_bound_floatingips(self): - return binding.QosPolicyFloatingIPBinding.get_objects( - self.obj_context, policy_id=self.id) + return binding.QosPolicyFloatingIPBinding.get_bound_ids( + self.obj_context, self.id) def get_bound_routers(self): return binding.QosPolicyRouterGatewayIPBinding.get_objects( 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 7d04a85132d..cf57878d5cd 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 @@ -247,16 +247,18 @@ class OVNClientQosExtension(object): def update_network(self, txn, network, original_network, reset=False, qos_rules=None): updated_port_ids = set([]) + updated_fip_ids = set([]) if not reset and not original_network: # If there is no information about the previous QoS policy, do not # make any change. - return updated_port_ids + return updated_port_ids, updated_fip_ids qos_policy_id = network.get('qos_policy_id') if not reset: original_qos_policy_id = original_network.get('qos_policy_id') if qos_policy_id == original_qos_policy_id: - return updated_port_ids # No QoS policy change + # No QoS policy change + return updated_port_ids, updated_fip_ids # NOTE(ralonsoh): we don't use the transaction context because some # ports can belong to other projects. @@ -271,14 +273,23 @@ class OVNClientQosExtension(object): qos_policy_id, qos_rules) updated_port_ids.add(port['id']) - return updated_port_ids + fips = qos_binding.QosPolicyFloatingIPBinding.get_fips_by_network_id( + admin_context, network['id']) + fip_ids = [fip.id for fip in fips] + for floatingip in self._plugin_l3.get_floatingips( + admin_context, filters={'id': fip_ids}): + self.update_floatingip(txn, floatingip) + updated_fip_ids.add(floatingip['id']) + + return updated_port_ids, updated_fip_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') + qos_policy_id = (floatingip.get('qos_policy_id') or + floatingip.get('qos_network_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( @@ -319,8 +330,10 @@ class OVNClientQosExtension(object): def update_policy(self, context, policy): updated_port_ids = set([]) + updated_fip_ids = set([]) bound_networks = policy.get_bound_networks() bound_ports = policy.get_bound_ports() + bound_fips = policy.get_bound_floatingips() qos_rules = self._qos_rules(context, policy.id) # TODO(ralonsoh): we need to benchmark this transaction in systems with # a huge amount of ports. This can take a while and could block other @@ -328,19 +341,24 @@ class OVNClientQosExtension(object): with self._driver._nb_idl.transaction(check_error=True) as txn: for network_id in bound_networks: network = {'qos_policy_id': policy.id, 'id': network_id} - updated_port_ids.update( - self.update_network(txn, network, {}, reset=True, - qos_rules=qos_rules)) + port_ids, fip_ids = self.update_network( + txn, network, {}, reset=True, qos_rules=qos_rules) + updated_port_ids.update(port_ids) + updated_fip_ids.update(fip_ids) # Update each port bound to this policy, not handled previously in # the network update loop port_ids = [p for p in bound_ports if p not in updated_port_ids] - for port in self._plugin.get_ports(context, - filters={'id': port_ids}): - self.update_port(txn, port, {}, reset=True, - qos_rules=qos_rules) + if port_ids: + for port in self._plugin.get_ports(context, + 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) + # Update each FIP bound to this policy, not handled previously in + # the network update loop + fip_ids = [fip for fip in bound_fips if fip not in updated_fip_ids] + if fip_ids: + for fip in self._plugin_l3.get_floatingips( + context, filters={'id': fip_ids}): + self.update_floatingip(txn, fip) 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 26a82a458bf..5df6fedfc71 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 @@ -352,7 +352,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.networks[0].qos_policy_id = qos_policy_id self.networks[0].update() original_network = {'qos_policy_id': self.qos_policies[0]} - reviewed_port_ids = self.qos_driver.update_network( + reviewed_port_ids, _ = self.qos_driver.update_network( mock.ANY, self.networks[0], original_network) self.assertEqual(reference_ports, reviewed_port_ids) calls = [mock.call(mock.ANY, self.ports[0].id, @@ -361,6 +361,26 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.mock_rules.assert_has_calls(calls) self.mock_rules.reset_mock() + def test_update_external_network(self): + """Test update external network (floating IPs). + + - fip0: qos_policy0 + - fip1: no QoS FIP policy (inherits from external network QoS) + """ + network_policies = [ + (self.qos_policies[1].id, {self.fips[1].id}), + (None, {self.fips[1].id})] + + self.fips[0].qos_policy_id = self.qos_policies[0].id + self.fips[0].update() + for qos_policy_id, reference_fips in network_policies: + self.fips_network.qos_policy_id = qos_policy_id + self.fips_network.update() + original_network = {'qos_policy_id': self.qos_policies[0]} + _, reviewed_fips_ids = self.qos_driver.update_network( + mock.Mock(), self.fips_network, original_network) + self.assertEqual(reference_fips, reviewed_fips_ids) + def test_update_network_no_policy_change(self): """Test update network if the QoS policy is the same. @@ -371,9 +391,10 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.networks[0].qos_policy_id = qos_policy_id self.networks[0].update() original_network = {'qos_policy_id': qos_policy_id} - reviewed_port_ids = self.qos_driver.update_network( + port_ids, fip_ids = self.qos_driver.update_network( mock.ANY, self.networks[0], original_network) - self.assertEqual(set([]), reviewed_port_ids) + self.assertEqual(set([]), port_ids) + self.assertEqual(set([]), fip_ids) self.mock_rules.assert_not_called() def test_update_network_reset(self): @@ -397,7 +418,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.networks[0].qos_policy_id = qos_policy_id self.networks[0].update() original_network = {'qos_policy_id': self.qos_policies[0]} - reviewed_port_ids = self.qos_driver.update_network( + reviewed_port_ids, _ = self.qos_driver.update_network( mock.ANY, self.networks[0], original_network, reset=True) self.assertEqual(reference_ports, reviewed_port_ids) calls = [mock.call(mock.ANY, self.ports[0].id, @@ -427,7 +448,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): self.networks[0].qos_policy_id = qos_policy_id self.networks[0].update() original_network = {'qos_policy_id': self.qos_policies[0]} - reviewed_port_ids = self.qos_driver.update_network( + reviewed_port_ids, _ = self.qos_driver.update_network( mock.ANY, self.networks[0], original_network, reset=True) self.assertEqual(reference_ports, reviewed_port_ids) calls = [mock.call( @@ -524,6 +545,14 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): nb_idl.qos_add.assert_not_called() nb_idl.reset_mock() + # Add network QoS policy + fip.qos_network_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() + # Add again another QoS policy fip.qos_policy_id = self.qos_policies[1].id fip.update() diff --git a/releasenotes/notes/qos-fip-network-inheritance-support-in-ovn-1d68b54c42c865da.yaml b/releasenotes/notes/qos-fip-network-inheritance-support-in-ovn-1d68b54c42c865da.yaml new file mode 100644 index 00000000000..8942ff2d00b --- /dev/null +++ b/releasenotes/notes/qos-fip-network-inheritance-support-in-ovn-1d68b54c42c865da.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Floating IP QoS network inheritance is now available for OVN L3 plugin + QoS extension. If a network, hosting a floating IP, has a QoS associated, + the floating IP addresses will inherit the network QoS policy and will + apply on the OVN backend.