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.