Merge "[OVN] Implement floating IP network QoS inheritance"
This commit is contained in:
commit
f94226c514
@ -19,6 +19,7 @@ from neutron_lib.objects import common_types
|
|||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy import exists
|
from sqlalchemy import exists
|
||||||
|
|
||||||
|
from neutron.db.models import l3 as models_l3
|
||||||
from neutron.db import models_v2
|
from neutron.db import models_v2
|
||||||
from neutron.db.qos import models as qos_db_model
|
from neutron.db.qos import models as qos_db_model
|
||||||
from neutron.objects import base
|
from neutron.objects import base
|
||||||
@ -101,6 +102,25 @@ class QosPolicyFloatingIPBinding(base.NeutronDbObject, _QosPolicyBindingMixin):
|
|||||||
fields_no_update = ['policy_id', 'fip_id']
|
fields_no_update = ['policy_id', 'fip_id']
|
||||||
_bound_model_id = db_model.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
|
@base.NeutronObjectRegistry.register
|
||||||
class QosPolicyRouterGatewayIPBinding(base.NeutronDbObject,
|
class QosPolicyRouterGatewayIPBinding(base.NeutronDbObject,
|
||||||
|
@ -333,8 +333,8 @@ class QosPolicy(rbac_db.NeutronRbacObject):
|
|||||||
self.id)
|
self.id)
|
||||||
|
|
||||||
def get_bound_floatingips(self):
|
def get_bound_floatingips(self):
|
||||||
return binding.QosPolicyFloatingIPBinding.get_objects(
|
return binding.QosPolicyFloatingIPBinding.get_bound_ids(
|
||||||
self.obj_context, policy_id=self.id)
|
self.obj_context, self.id)
|
||||||
|
|
||||||
def get_bound_routers(self):
|
def get_bound_routers(self):
|
||||||
return binding.QosPolicyRouterGatewayIPBinding.get_objects(
|
return binding.QosPolicyRouterGatewayIPBinding.get_objects(
|
||||||
|
@ -247,16 +247,18 @@ class OVNClientQosExtension(object):
|
|||||||
def update_network(self, txn, network, original_network, reset=False,
|
def update_network(self, txn, network, original_network, reset=False,
|
||||||
qos_rules=None):
|
qos_rules=None):
|
||||||
updated_port_ids = set([])
|
updated_port_ids = set([])
|
||||||
|
updated_fip_ids = set([])
|
||||||
if not reset and not original_network:
|
if not reset and not original_network:
|
||||||
# If there is no information about the previous QoS policy, do not
|
# If there is no information about the previous QoS policy, do not
|
||||||
# make any change.
|
# make any change.
|
||||||
return updated_port_ids
|
return updated_port_ids, updated_fip_ids
|
||||||
|
|
||||||
qos_policy_id = network.get('qos_policy_id')
|
qos_policy_id = network.get('qos_policy_id')
|
||||||
if not reset:
|
if not reset:
|
||||||
original_qos_policy_id = original_network.get('qos_policy_id')
|
original_qos_policy_id = original_network.get('qos_policy_id')
|
||||||
if qos_policy_id == original_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
|
# NOTE(ralonsoh): we don't use the transaction context because some
|
||||||
# ports can belong to other projects.
|
# ports can belong to other projects.
|
||||||
@ -271,14 +273,23 @@ class OVNClientQosExtension(object):
|
|||||||
qos_policy_id, qos_rules)
|
qos_policy_id, qos_rules)
|
||||||
updated_port_ids.add(port['id'])
|
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):
|
def create_floatingip(self, txn, floatingip):
|
||||||
self.update_floatingip(txn, floatingip)
|
self.update_floatingip(txn, floatingip)
|
||||||
|
|
||||||
def update_floatingip(self, txn, floatingip):
|
def update_floatingip(self, txn, floatingip):
|
||||||
router_id = floatingip.get('router_id')
|
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']:
|
if floatingip['floating_network_id']:
|
||||||
lswitch_name = utils.ovn_name(floatingip['floating_network_id'])
|
lswitch_name = utils.ovn_name(floatingip['floating_network_id'])
|
||||||
txn.add(self._driver._nb_idl.qos_del_ext_ids(
|
txn.add(self._driver._nb_idl.qos_del_ext_ids(
|
||||||
@ -319,8 +330,10 @@ class OVNClientQosExtension(object):
|
|||||||
|
|
||||||
def update_policy(self, context, policy):
|
def update_policy(self, context, policy):
|
||||||
updated_port_ids = set([])
|
updated_port_ids = set([])
|
||||||
|
updated_fip_ids = set([])
|
||||||
bound_networks = policy.get_bound_networks()
|
bound_networks = policy.get_bound_networks()
|
||||||
bound_ports = policy.get_bound_ports()
|
bound_ports = policy.get_bound_ports()
|
||||||
|
bound_fips = policy.get_bound_floatingips()
|
||||||
qos_rules = self._qos_rules(context, policy.id)
|
qos_rules = self._qos_rules(context, policy.id)
|
||||||
# TODO(ralonsoh): we need to benchmark this transaction in systems with
|
# 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
|
# 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:
|
with self._driver._nb_idl.transaction(check_error=True) as txn:
|
||||||
for network_id in bound_networks:
|
for network_id in bound_networks:
|
||||||
network = {'qos_policy_id': policy.id, 'id': network_id}
|
network = {'qos_policy_id': policy.id, 'id': network_id}
|
||||||
updated_port_ids.update(
|
port_ids, fip_ids = self.update_network(
|
||||||
self.update_network(txn, network, {}, reset=True,
|
txn, network, {}, reset=True, qos_rules=qos_rules)
|
||||||
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
|
# Update each port bound to this policy, not handled previously in
|
||||||
# the network update loop
|
# the network update loop
|
||||||
port_ids = [p for p in bound_ports if p not in updated_port_ids]
|
port_ids = [p for p in bound_ports if p not in updated_port_ids]
|
||||||
for port in self._plugin.get_ports(context,
|
if port_ids:
|
||||||
filters={'id': port_ids}):
|
for port in self._plugin.get_ports(context,
|
||||||
self.update_port(txn, port, {}, reset=True,
|
filters={'id': port_ids}):
|
||||||
qos_rules=qos_rules)
|
self.update_port(txn, port, {}, reset=True,
|
||||||
|
qos_rules=qos_rules)
|
||||||
|
|
||||||
for fip_binding in policy.get_bound_floatingips():
|
# Update each FIP bound to this policy, not handled previously in
|
||||||
fip = self._plugin_l3.get_floatingip(context,
|
# the network update loop
|
||||||
fip_binding.fip_id)
|
fip_ids = [fip for fip in bound_fips if fip not in updated_fip_ids]
|
||||||
self.update_floatingip(txn, fip)
|
if fip_ids:
|
||||||
|
for fip in self._plugin_l3.get_floatingips(
|
||||||
|
context, filters={'id': fip_ids}):
|
||||||
|
self.update_floatingip(txn, fip)
|
||||||
|
@ -352,7 +352,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
|
|||||||
self.networks[0].qos_policy_id = qos_policy_id
|
self.networks[0].qos_policy_id = qos_policy_id
|
||||||
self.networks[0].update()
|
self.networks[0].update()
|
||||||
original_network = {'qos_policy_id': self.qos_policies[0]}
|
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)
|
mock.ANY, self.networks[0], original_network)
|
||||||
self.assertEqual(reference_ports, reviewed_port_ids)
|
self.assertEqual(reference_ports, reviewed_port_ids)
|
||||||
calls = [mock.call(mock.ANY, self.ports[0].id,
|
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.assert_has_calls(calls)
|
||||||
self.mock_rules.reset_mock()
|
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):
|
def test_update_network_no_policy_change(self):
|
||||||
"""Test update network if the QoS policy is the same.
|
"""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].qos_policy_id = qos_policy_id
|
||||||
self.networks[0].update()
|
self.networks[0].update()
|
||||||
original_network = {'qos_policy_id': qos_policy_id}
|
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)
|
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()
|
self.mock_rules.assert_not_called()
|
||||||
|
|
||||||
def test_update_network_reset(self):
|
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].qos_policy_id = qos_policy_id
|
||||||
self.networks[0].update()
|
self.networks[0].update()
|
||||||
original_network = {'qos_policy_id': self.qos_policies[0]}
|
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)
|
mock.ANY, self.networks[0], original_network, reset=True)
|
||||||
self.assertEqual(reference_ports, reviewed_port_ids)
|
self.assertEqual(reference_ports, reviewed_port_ids)
|
||||||
calls = [mock.call(mock.ANY, self.ports[0].id,
|
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].qos_policy_id = qos_policy_id
|
||||||
self.networks[0].update()
|
self.networks[0].update()
|
||||||
original_network = {'qos_policy_id': self.qos_policies[0]}
|
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)
|
mock.ANY, self.networks[0], original_network, reset=True)
|
||||||
self.assertEqual(reference_ports, reviewed_port_ids)
|
self.assertEqual(reference_ports, reviewed_port_ids)
|
||||||
calls = [mock.call(
|
calls = [mock.call(
|
||||||
@ -524,6 +545,14 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
|
|||||||
nb_idl.qos_add.assert_not_called()
|
nb_idl.qos_add.assert_not_called()
|
||||||
nb_idl.reset_mock()
|
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
|
# Add again another QoS policy
|
||||||
fip.qos_policy_id = self.qos_policies[1].id
|
fip.qos_policy_id = self.qos_policies[1].id
|
||||||
fip.update()
|
fip.update()
|
||||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user