Merge "[OVN] Implement floating IP network QoS inheritance"

This commit is contained in:
Zuul 2022-02-02 20:38:26 +00:00 committed by Gerrit Code Review
commit f94226c514
5 changed files with 96 additions and 22 deletions

View File

@ -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,

View File

@ -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(

View File

@ -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)

View File

@ -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()

View File

@ -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.