[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
This commit is contained in:
Rodolfo Alonso Hernandez 2021-11-12 14:31:58 +00:00
parent 7cbec53543
commit 8dfe5cc95b
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.