[OVN] Implement router gateway IP QoS

This patch implements router gateway IP QoS based on meter,
using the existing plugin and extension, only the driver side
is different.

Closes-Bug: #1893625

Co-Authored-By: zhanghao <hao.zhang.am.i@gmail.com>
Co-Authored-By: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>

Change-Id: I46864b9234af64f190f6b6daebfd94d2e3bd0c17
This commit is contained in:
Rodolfo Alonso Hernandez 2022-03-05 18:33:46 +00:00
parent 1b68aebaba
commit 2d1b4fd80f
11 changed files with 241 additions and 19 deletions

View File

@ -26,9 +26,11 @@ OVN_PORT_NAME_EXT_ID_KEY = 'neutron:port_name'
OVN_PORT_EXT_ID_KEY = 'neutron:port_id' OVN_PORT_EXT_ID_KEY = 'neutron:port_id'
OVN_PORT_FIP_EXT_ID_KEY = 'neutron:port_fip' OVN_PORT_FIP_EXT_ID_KEY = 'neutron:port_fip'
OVN_ROUTER_NAME_EXT_ID_KEY = 'neutron:router_name' OVN_ROUTER_NAME_EXT_ID_KEY = 'neutron:router_name'
OVN_ROUTER_ID_EXT_ID_KEY = 'neutron:router_id'
OVN_AZ_HINTS_EXT_ID_KEY = 'neutron:availability_zone_hints' OVN_AZ_HINTS_EXT_ID_KEY = 'neutron:availability_zone_hints'
OVN_ROUTER_IS_EXT_GW = 'neutron:is_ext_gw' OVN_ROUTER_IS_EXT_GW = 'neutron:is_ext_gw'
OVN_GW_PORT_EXT_ID_KEY = 'neutron:gw_port_id' OVN_GW_PORT_EXT_ID_KEY = 'neutron:gw_port_id'
OVN_GW_NETWORK_EXT_ID_KEY = 'neutron:gw_network_id'
OVN_SUBNET_EXT_ID_KEY = 'neutron:subnet_id' OVN_SUBNET_EXT_ID_KEY = 'neutron:subnet_id'
OVN_SUBNET_EXT_IDS_KEY = 'neutron:subnet_ids' OVN_SUBNET_EXT_IDS_KEY = 'neutron:subnet_ids'
OVN_PHYSNET_EXT_ID_KEY = 'neutron:provnet-physical-network' OVN_PHYSNET_EXT_ID_KEY = 'neutron:provnet-physical-network'

View File

@ -52,6 +52,7 @@ from neutron_lib.api.definitions import provider_net
from neutron_lib.api.definitions import qos from neutron_lib.api.definitions import qos
from neutron_lib.api.definitions import qos_bw_limit_direction from neutron_lib.api.definitions import qos_bw_limit_direction
from neutron_lib.api.definitions import qos_default from neutron_lib.api.definitions import qos_default
from neutron_lib.api.definitions import qos_gateway_ip
from neutron_lib.api.definitions import qos_rule_type_details from neutron_lib.api.definitions import qos_rule_type_details
from neutron_lib.api.definitions import qos_rule_type_filter from neutron_lib.api.definitions import qos_rule_type_filter
from neutron_lib.api.definitions import qos_rules_alias from neutron_lib.api.definitions import qos_rules_alias
@ -87,6 +88,7 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
floatingip_pools.ALIAS, floatingip_pools.ALIAS,
pagination.ALIAS, pagination.ALIAS,
'qos-fip', 'qos-fip',
qos_gateway_ip.ALIAS,
sorting.ALIAS, sorting.ALIAS,
project_id.ALIAS, project_id.ALIAS,
dns.ALIAS, dns.ALIAS,

View File

@ -15,6 +15,7 @@
from neutron.objects.qos import binding as qos_binding from neutron.objects.qos import binding as qos_binding
from neutron.objects.qos import policy as qos_policy from neutron.objects.qos import policy as qos_policy
from neutron.objects.qos import rule as qos_rule from neutron.objects.qos import rule as qos_rule
from neutron_lib.api.definitions import l3 as l3_api
from neutron_lib import constants from neutron_lib import constants
from neutron_lib import context as n_context from neutron_lib import context as n_context
from neutron_lib.plugins import constants as plugins_const from neutron_lib.plugins import constants as plugins_const
@ -118,7 +119,7 @@ class OVNClientQosExtension(object):
def _ovn_qos_rule(self, rules_direction, rules, port_id, network_id, def _ovn_qos_rule(self, rules_direction, rules, port_id, network_id,
fip_id=None, ip_address=None, resident_port=None, fip_id=None, ip_address=None, resident_port=None,
delete=False): router_id=None, delete=False):
"""Generate an OVN QoS register based on several Neutron QoS rules """Generate an OVN QoS register based on several Neutron QoS rules
A OVN QoS register can contain "bandwidth" and "action" parameters. A OVN QoS register can contain "bandwidth" and "action" parameters.
@ -142,6 +143,8 @@ class OVNClientQosExtension(object):
:param resident_port: (string) for L3 floating IP bandwidth, this is :param resident_port: (string) for L3 floating IP bandwidth, this is
a logical switch port located in the chassis a logical switch port located in the chassis
where the floating IP traffic is NATed. where the floating IP traffic is NATed.
:param router_id: (string) router ID, for L3 router gateway port
bandwidth limit.
:param delete: (bool) defines if this rule if going to be a partial :param delete: (bool) defines if this rule if going to be a partial
one (without any bandwidth or DSCP information) to be one (without any bandwidth or DSCP information) to be
used only as deletion rule. used only as deletion rule.
@ -168,8 +171,13 @@ class OVNClientQosExtension(object):
# All OVN QoS rules have an external ID reference to the port or the # All OVN QoS rules have an external ID reference to the port or the
# FIP that are attached to. # FIP that are attached to.
# 1) L3 floating IP ports.
if fip_id: if fip_id:
key, value = ovn_const.OVN_FIP_EXT_ID_KEY, fip_id key, value = ovn_const.OVN_FIP_EXT_ID_KEY, fip_id
# 2) L3 router gateway port.
elif router_id:
key, value = ovn_const.OVN_ROUTER_ID_EXT_ID_KEY, router_id
# 3) Fixed IP ports (aka VM ports)
else: else:
key, value = ovn_const.OVN_PORT_EXT_ID_KEY, port_id key, value = ovn_const.OVN_PORT_EXT_ID_KEY, port_id
ovn_qos_rule['external_ids'] = {key: value} ovn_qos_rule['external_ids'] = {key: value}
@ -365,6 +373,49 @@ class OVNClientQosExtension(object):
def disassociate_floatingip(self, txn, floatingip): def disassociate_floatingip(self, txn, floatingip):
self.delete_floatingip(txn, floatingip) self.delete_floatingip(txn, floatingip)
def _delete_gateway_ip_qos_rules(self, txn, router_id, network_id):
if network_id:
lswitch_name = utils.ovn_name(network_id)
txn.add(self.nb_idl.qos_del_ext_ids(
lswitch_name,
{ovn_const.OVN_ROUTER_ID_EXT_ID_KEY: router_id}))
def create_router(self, txn, router):
self.update_router(txn, router)
def update_router(self, txn, router):
gw_info = router.get(l3_api.EXTERNAL_GW_INFO) or {}
qos_policy_id = gw_info.get('qos_policy_id')
router_id = router.get('id')
gw_port_id = router.get('gw_port_id')
gw_network_id = gw_info.get('network_id')
if not (router_id and gw_port_id and gw_network_id):
# NOTE(ralonsoh): when the gateway network is detached, the gateway
# port is deleted. Any QoS policy related to this port_id is
# deleted in "self.update_port()".
LOG.debug('Router %s does not have ID or gateway assigned', router)
return
admin_context = n_context.get_admin_context()
qos_rules = self._qos_rules(admin_context, qos_policy_id)
for direction, rules in qos_rules.items():
# "delete=not rule": that means, when we don't have rules, we
# generate a "ovn_rule" to be used as input in a "qos_del" method.
ovn_rule = self._ovn_qos_rule(
direction, rules, gw_port_id, gw_network_id,
router_id=router_id, delete=not rules)
if rules:
# NOTE(ralonsoh): with "may_exist=True", the "qos_add" will
# create the QoS OVN rule or update the existing one.
txn.add(self.nb_idl.qos_add(**ovn_rule, may_exist=True))
else:
# Delete, if exists, the QoS rule in this direction.
txn.add(self.nb_idl.qos_del(**ovn_rule, if_exists=True))
def delete_router(self, txn, router):
self._delete_gateway_ip_qos_rules(txn, router['id'],
router['gw_network_id'])
def update_policy(self, context, policy): def update_policy(self, context, policy):
updated_port_ids = set([]) updated_port_ids = set([])
updated_fip_ids = set([]) updated_fip_ids = set([])
@ -399,3 +450,8 @@ class OVNClientQosExtension(object):
for fip in self._plugin_l3.get_floatingips( for fip in self._plugin_l3.get_floatingips(
context, filters={'id': fip_ids}): context, filters={'id': fip_ids}):
self.update_floatingip(txn, fip) self.update_floatingip(txn, fip)
for router_binding in policy.get_bound_routers():
router = self._plugin_l3.get_router(context,
router_binding.router_id)
self.update_router(txn, router)

View File

@ -734,6 +734,39 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
txn.add(cmd) txn.add(cmd)
raise periodics.NeverAgain() raise periodics.NeverAgain()
# TODO(ralonsoh): Remove this in the Z+3 cycle. This method adds the
# "external_ids:OVN_GW_NETWORK_EXT_ID_KEY" to each router that has
# a gateway (that means, that has "external_ids:OVN_GW_PORT_EXT_ID_KEY").
# A static spacing value is used here, but this method will only run
# once per lock due to the use of periodics.NeverAgain().
@periodics.periodic(spacing=600, run_immediately=True)
def update_logical_router_with_gateway_network_id(self):
"""Update all OVN logical router registers with the GW network ID"""
if not self.has_lock:
return
cmds = []
context = n_context.get_admin_context()
for lr in self._nb_idl.lr_list().execute(check_error=True):
gw_port = lr.external_ids.get(ovn_const.OVN_GW_PORT_EXT_ID_KEY)
gw_net = lr.external_ids.get(ovn_const.OVN_GW_NETWORK_EXT_ID_KEY)
if not gw_port or (gw_port and gw_net):
# This router does not have a gateway network assigned yet or
# it has a gateway port and its corresponding network.
continue
port = self._ovn_client._plugin.get_port(context, gw_port)
external_ids = {
ovn_const.OVN_GW_NETWORK_EXT_ID_KEY: port['network_id']}
cmds.append(self._nb_idl.db_set(
'Logical_Router', lr.uuid, ('external_ids', external_ids)))
if cmds:
with self._nb_idl.transaction(check_error=True) as txn:
for cmd in cmds:
txn.add(cmd)
raise periodics.NeverAgain()
class HashRingHealthCheckPeriodics(object): class HashRingHealthCheckPeriodics(object):

View File

@ -1278,6 +1278,8 @@ class OVNClient(object):
return networks return networks
def _gen_router_ext_ids(self, router): def _gen_router_ext_ids(self, router):
gw_net_id = (router.get('external_gateway_info') or
{}).get('network_id') or ''
return { return {
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
router.get('name', 'no_router_name'), router.get('name', 'no_router_name'),
@ -1286,7 +1288,9 @@ class OVNClient(object):
ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number( ovn_const.OVN_REV_NUM_EXT_ID_KEY: str(utils.get_revision_number(
router, ovn_const.TYPE_ROUTERS)), router, ovn_const.TYPE_ROUTERS)),
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ovn_const.OVN_AZ_HINTS_EXT_ID_KEY:
','.join(common_utils.get_az_hints(router))} ','.join(common_utils.get_az_hints(router)),
ovn_const.OVN_GW_NETWORK_EXT_ID_KEY: gw_net_id,
}
def create_router(self, context, router, add_external_gateway=True): def create_router(self, context, router, add_external_gateway=True):
"""Create a logical router.""" """Create a logical router."""
@ -1311,6 +1315,8 @@ class OVNClient(object):
added_gw_port = self._add_router_ext_gw( added_gw_port = self._add_router_ext_gw(
router, networks, txn) router, networks, txn)
self._qos_driver.create_router(txn, router)
if added_gw_port: if added_gw_port:
db_rev.bump_revision(context, added_gw_port, db_rev.bump_revision(context, added_gw_port,
ovn_const.TYPE_ROUTER_PORTS) ovn_const.TYPE_ROUTER_PORTS)
@ -1381,6 +1387,7 @@ class OVNClient(object):
old_routes, routes) old_routes, routes)
self.update_router_routes( self.update_router_routes(
context, router_id, added, removed, txn=txn) context, router_id, added, removed, txn=txn)
self._qos_driver.update_router(txn, new_router)
if check_rev_cmd.result == ovn_const.TXN_COMMITTED: if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
db_rev.bump_revision(context, new_router, db_rev.bump_revision(context, new_router,
@ -1403,8 +1410,13 @@ class OVNClient(object):
def delete_router(self, context, router_id): def delete_router(self, context, router_id):
"""Delete a logical router.""" """Delete a logical router."""
lrouter_name = utils.ovn_name(router_id) lrouter_name = utils.ovn_name(router_id)
ovn_router = self._nb_idl.get_lrouter(lrouter_name)
gw_network_id = ovn_router.external_ids.get(
ovn_const.OVN_GW_NETWORK_EXT_ID_KEY) if ovn_router else None
router_dict = {'id': router_id, 'gw_network_id': gw_network_id}
with self._nb_idl.transaction(check_error=True) as txn: with self._nb_idl.transaction(check_error=True) as txn:
txn.add(self._nb_idl.delete_lrouter(lrouter_name)) txn.add(self._nb_idl.delete_lrouter(lrouter_name))
self._qos_driver.delete_router(txn, router_dict)
db_rev.delete_revision(context, router_id, ovn_const.TYPE_ROUTERS) db_rev.delete_revision(context, router_id, ovn_const.TYPE_ROUTERS)
def get_candidates_for_scheduling(self, physnet, cms=None, def get_candidates_for_scheduling(self, physnet, cms=None,

View File

@ -16,6 +16,7 @@ from neutron_lib.api.definitions import external_net
from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import provider_net as pnet from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.api.definitions import qos_fip as qos_fip_apidef from neutron_lib.api.definitions import qos_fip as qos_fip_apidef
from neutron_lib.api.definitions import qos_gateway_ip as qos_gateway_ip_apidef
from neutron_lib.callbacks import events from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources from neutron_lib.callbacks import resources
@ -40,6 +41,7 @@ from neutron.db import extraroute_db
from neutron.db import l3_fip_pools_db from neutron.db import l3_fip_pools_db
from neutron.db import l3_fip_port_details from neutron.db import l3_fip_port_details
from neutron.db import l3_fip_qos from neutron.db import l3_fip_qos
from neutron.db import l3_gateway_ip_qos
from neutron.db import l3_gwmode_db from neutron.db import l3_gwmode_db
from neutron.db.models import l3 as l3_models from neutron.db.models import l3 as l3_models
from neutron.db import ovn_revision_numbers_db as db_rev from neutron.db import ovn_revision_numbers_db as db_rev
@ -62,6 +64,7 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
l3_fip_port_details.Fip_port_details_db_mixin, l3_fip_port_details.Fip_port_details_db_mixin,
router_az_db.RouterAvailabilityZoneMixin, router_az_db.RouterAvailabilityZoneMixin,
l3_fip_qos.FloatingQoSDbMixin, l3_fip_qos.FloatingQoSDbMixin,
l3_gateway_ip_qos.L3_gw_ip_qos_db_mixin,
l3_fip_pools_db.FloatingIPPoolsMixin, l3_fip_pools_db.FloatingIPPoolsMixin,
): ):
"""Implementation of the OVN L3 Router Service Plugin. """Implementation of the OVN L3 Router Service Plugin.
@ -98,17 +101,20 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
events.PRECOMMIT_CREATE) events.PRECOMMIT_CREATE)
@staticmethod @staticmethod
def disable_qos_fip_extension_by_extension_drivers(aliases): def _disable_qos_extensions_by_extension_drivers(aliases):
qos_service_plugin = directory.get_plugin(plugin_constants.QOS) qos_service_plugin = directory.get_plugin(plugin_constants.QOS)
qos_aliases = qos_fip_apidef.ALIAS in aliases qos_fip_in_aliases = qos_fip_apidef.ALIAS in aliases
if not qos_service_plugin and qos_aliases: qos_gwip_in_aliases = qos_gateway_ip_apidef.ALIAS in aliases
if not qos_service_plugin and qos_fip_in_aliases:
aliases.remove(qos_fip_apidef.ALIAS) aliases.remove(qos_fip_apidef.ALIAS)
if not qos_service_plugin and qos_gwip_in_aliases:
aliases.remove(qos_gateway_ip_apidef.ALIAS)
@property @property
def supported_extension_aliases(self): def supported_extension_aliases(self):
if not hasattr(self, '_aliases'): if not hasattr(self, '_aliases'):
self._aliases = self._supported_extension_aliases[:] self._aliases = self._supported_extension_aliases[:]
self.disable_qos_fip_extension_by_extension_drivers(self._aliases) self._disable_qos_extensions_by_extension_drivers(self._aliases)
return self._aliases return self._aliases
@property @property

View File

@ -152,6 +152,7 @@ class FakeOvsdbNbOvnIdl(object):
self.ls_get = mock.Mock() self.ls_get = mock.Mock()
self.check_liveness = mock.Mock() self.check_liveness = mock.Mock()
self.ha_chassis_group_get = mock.Mock() self.ha_chassis_group_get = mock.Mock()
self.qos_del = mock.Mock()
self.qos_del_ext_ids = mock.Mock() self.qos_del_ext_ids = mock.Mock()
self.meter_add = mock.Mock() self.meter_add = mock.Mock()
self.meter_del = mock.Mock() self.meter_del = mock.Mock()

View File

@ -12,11 +12,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import random
from unittest import mock from unittest import mock
import netaddr import netaddr
from neutron_lib.api.definitions import l3 as l3_apidef
from neutron_lib.api.definitions import portbindings as portbindings_api from neutron_lib.api.definitions import portbindings as portbindings_api
from neutron_lib.api.definitions import qos as qos_api from neutron_lib.api.definitions import qos as qos_api
from neutron_lib.api.definitions import qos_fip as qos_fip_apidef
from neutron_lib import constants from neutron_lib import constants
from neutron_lib import context from neutron_lib import context
from neutron_lib.db import api as db_api from neutron_lib.db import api as db_api
@ -27,6 +30,8 @@ from oslo_utils import uuidutils
from neutron.api import extensions from neutron.api import extensions
from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import constants as ovn_const
from neutron.core_extensions import qos as core_qos from neutron.core_extensions import qos as core_qos
from neutron.db import l3_fip_qos
from neutron.db import l3_gateway_ip_qos
from neutron.objects import network as network_obj from neutron.objects import network as network_obj
from neutron.objects import ports as port_obj from neutron.objects import ports as port_obj
from neutron.objects.qos import policy as policy_obj from neutron.objects.qos import policy as policy_obj
@ -34,6 +39,7 @@ from neutron.objects.qos import rule as rule_obj
from neutron.objects import router as router_obj from neutron.objects import router as router_obj
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \ from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
import qos as qos_extension import qos as qos_extension
from neutron.tests.unit.extensions import test_l3
from neutron.tests.unit.plugins.ml2 import test_plugin from neutron.tests.unit.plugins.ml2 import test_plugin
@ -53,12 +59,21 @@ class _Context(object):
return return
class TestFloatingIPQoSL3NatServicePlugin(
test_l3.TestL3NatServicePlugin,
l3_fip_qos.FloatingQoSDbMixin,
l3_gateway_ip_qos.L3_gw_ip_qos_db_mixin):
supported_extension_aliases = [l3_apidef.ALIAS, 'qos',
qos_fip_apidef.ALIAS]
class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase): class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
CORE_PLUGIN_CLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin' CORE_PLUGIN_CLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin'
_extension_drivers = [qos_api.ALIAS] _extension_drivers = [qos_api.ALIAS]
l3_plugin = ('neutron.tests.unit.extensions.test_qos_fip.' l3_plugin = ('neutron.tests.unit.plugins.ml2.drivers.ovn.'
'TestFloatingIPQoSL3NatServicePlugin') 'mech_driver.ovsdb.extensions.'
'test_qos.TestFloatingIPQoSL3NatServicePlugin')
def setUp(self): def setUp(self):
cfg.CONF.set_override('extension_drivers', self._extension_drivers, cfg.CONF.set_override('extension_drivers', self._extension_drivers,
@ -101,21 +116,37 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
return port return port
def _create_one_router(self): def _create_one_router(self):
self.router_gw_port = self._create_one_port(2000, self.fips_network.id) network = network_obj.Network(
self.router = router_obj.Router(self.ctx, id=uuidutils.generate_uuid(), self.ctx, id=uuidutils.generate_uuid(), project_id=self.project_id)
gw_port_id=self.router_gw_port.id) network.create()
self.router.create() router_gw_port = self._create_one_port(random.randint(1, 10000),
network.id)
router = router_obj.Router(self.ctx, id=uuidutils.generate_uuid(),
gw_port_id=router_gw_port.id)
router.create()
return router, network
def _update_router_qos(self, router_id, qos_policy_id, attach=True):
# NOTE(ralonsoh): router QoS policy is not yet implemented in Router
# OVO. Once we have this feature, this method can be removed.
qos = policy_obj.QosPolicy.get_policy_obj(self.ctx, qos_policy_id)
if attach:
qos.attach_router(router_id)
else:
qos.detach_router(router_id)
def _get_router(self, router_id):
return self.qos_driver._plugin_l3.get_router(self.ctx, router_id)
def _initialize_objs(self): def _initialize_objs(self):
self.qos_policies = [] self.qos_policies = []
self.ports = [] self.ports = []
self.networks = [] self.networks = []
self.fips = [] self.fips = []
self.fips_network = network_obj.Network( self.router_fips, self.fips_network = self._create_one_router()
self.ctx, id=uuidutils.generate_uuid(), project_id=self.project_id)
self.fips_network.create()
self._create_one_router()
self.fips_ports = [] self.fips_ports = []
self.routers = []
self.router_networks = []
fip_cidr = netaddr.IPNetwork('10.10.0.0/24') fip_cidr = netaddr.IPNetwork('10.10.0.0/24')
for net_idx in range(2): for net_idx in range(2):
@ -154,6 +185,10 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
self.ports.append( self.ports.append(
self._create_one_port(net_idx * 16 + port_idx, network.id)) self._create_one_port(net_idx * 16 + port_idx, network.id))
router, router_network = self._create_one_router()
self.routers.append(router)
self.router_networks.append(router_network)
@mock.patch.object(qos_extension.LOG, 'warning') @mock.patch.object(qos_extension.LOG, 'warning')
@mock.patch.object(rule_obj, 'get_rules') @mock.patch.object(rule_obj, 'get_rules')
def test__qos_rules(self, mock_get_rules, mock_warning): def test__qos_rules(self, mock_get_rules, mock_warning):
@ -479,6 +514,8 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
- port22: qos_policy1 --> handled during "update_network", not updated - port22: qos_policy1 --> handled during "update_network", not updated
fip1: qos_policy0 fip1: qos_policy0
fip2: qos_policy1 fip2: qos_policy1
router1: qos_policy0
router2: qos_policy1
""" """
self.ports[1].qos_policy_id = self.qos_policies[0].id self.ports[1].qos_policy_id = self.qos_policies[0].id
self.ports[1].update() self.ports[1].update()
@ -494,11 +531,15 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
self.fips[0].update() self.fips[0].update()
self.fips[1].qos_policy_id = self.qos_policies[1].id self.fips[1].qos_policy_id = self.qos_policies[1].id
self.fips[1].update() self.fips[1].update()
self._update_router_qos(self.routers[0].id, self.qos_policies[0].id)
self._update_router_qos(self.routers[1].id, self.qos_policies[1].id)
mock_qos_rules = mock.Mock() mock_qos_rules = mock.Mock()
with mock.patch.object(self.qos_driver, '_qos_rules', with mock.patch.object(self.qos_driver, '_qos_rules',
return_value=mock_qos_rules), \ return_value=mock_qos_rules), \
mock.patch.object(self.qos_driver, 'update_floatingip') as \ mock.patch.object(self.qos_driver, 'update_floatingip') as \
mock_update_fip: mock_update_fip, \
mock.patch.object(self.qos_driver, 'update_router') as \
mock_update_router:
self.qos_driver.update_policy(self.ctx, self.qos_policies[0]) self.qos_driver.update_policy(self.ctx, self.qos_policies[0])
updated_ports = [self.ports[1], self.ports[3], self.ports[4]] updated_ports = [self.ports[1], self.ports[3], self.ports[4]]
calls = [mock.call(self.txn, port.id, port.network_id, calls = [mock.call(self.txn, port.id, port.network_id,
@ -512,6 +553,11 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
self.fips[0].id) self.fips[0].id)
mock_update_fip.assert_called_once_with(self.txn, fip) mock_update_fip.assert_called_once_with(self.txn, fip)
with db_api.CONTEXT_READER.using(self.ctx):
router = self.qos_driver._plugin_l3.get_router(self.ctx,
self.routers[0].id)
mock_update_router.assert_called_once_with(self.txn, router)
def test_update_floatingip(self): def test_update_floatingip(self):
# NOTE(ralonsoh): this rule will always apply: # NOTE(ralonsoh): this rule will always apply:
# - If the FIP is being deleted, "qos_del_ext_ids" is called; # - If the FIP is being deleted, "qos_del_ext_ids" is called;
@ -531,7 +577,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
nb_idl.reset_mock() nb_idl.reset_mock()
# Attach a port and a router, not QoS policy # Attach a port and a router, not QoS policy
fip.router_id = self.router.id fip.router_id = self.router_fips.id
fip.fixed_port_id = self.fips_ports[0].id fip.fixed_port_id = self.fips_ports[0].id
fip.update() fip.update()
self.qos_driver.update_floatingip(txn, fip) self.qos_driver.update_floatingip(txn, fip)
@ -587,7 +633,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
fip.router_id = None fip.router_id = None
fip.fixed_port_id = None fip.fixed_port_id = None
fip.update() fip.update()
original_fip.router_id = self.router.id original_fip.router_id = self.router_fips.id
original_fip.fixed_port_id = self.fips_ports[0].id original_fip.fixed_port_id = self.fips_ports[0].id
original_fip.qos_policy_id = self.qos_policies[1].id original_fip.qos_policy_id = self.qos_policies[1].id
original_fip.update() original_fip.update()
@ -604,3 +650,30 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
nb_idl.qos_del_ext_ids.assert_called_once() nb_idl.qos_del_ext_ids.assert_called_once()
nb_idl.qos_add.assert_not_called() nb_idl.qos_add.assert_not_called()
nb_idl.qos_del.assert_not_called() nb_idl.qos_del.assert_not_called()
def test_update_router(self):
nb_idl = self.qos_driver._driver._nb_idl
txn = mock.Mock()
# Update router, no QoS policy set.
router = self._get_router(self.routers[0].id)
self.qos_driver.update_router(txn, router)
nb_idl.qos_add.assert_not_called()
self.assertEqual(2, nb_idl.qos_del.call_count)
nb_idl.reset_mock()
# Add QoS policy.
self._update_router_qos(router['id'], self.qos_policies[0].id)
router = self._get_router(self.routers[0].id)
self.qos_driver.update_router(txn, router)
nb_idl.qos_add.assert_called_once()
nb_idl.qos_del.assert_called_once()
nb_idl.reset_mock()
# Remove QoS
self._update_router_qos(router['id'], self.qos_policies[0].id,
attach=False)
router = self._get_router(self.routers[0].id)
self.qos_driver.update_router(txn, router)
nb_idl.qos_add.assert_not_called()
self.assertEqual(2, nb_idl.qos_del.call_count)

View File

@ -589,3 +589,29 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
mock.call('Logical_Router_Port', 'lrp-port1', ('options', opt))] mock.call('Logical_Router_Port', 'lrp-port1', ('options', opt))]
self.fake_ovn_client._nb_idl.db_set.assert_has_calls( self.fake_ovn_client._nb_idl.db_set.assert_has_calls(
expected_calls) expected_calls)
def test_update_logical_router_with_gateway_network_id(self):
nb_idl = self.fake_ovn_client._nb_idl
# lr0: GW port ID, not GW network ID --> we need to add network ID.
lr0 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
'name': 'lr0',
'external_ids': {constants.OVN_GW_PORT_EXT_ID_KEY: 'port0'}})
# lr1: GW port ID and not GW network ID --> register already updated.
lr1 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
'name': 'lr1',
'external_ids': {constants.OVN_GW_PORT_EXT_ID_KEY: 'port1',
constants.OVN_GW_NETWORK_EXT_ID_KEY: 'net1'}})
# lr2: no GW port ID (nor GW network ID) --> no QoS.
lr2 = fakes.FakeOvsdbRow.create_one_ovsdb_row(attrs={
'name': 'lr2', 'external_ids': {}})
nb_idl.lr_list.return_value.execute.return_value = (lr0, lr1, lr2)
self.fake_ovn_client._plugin.get_port.return_value = {
'network_id': 'net0'}
self.assertRaises(
periodics.NeverAgain,
self.periodic.update_logical_router_with_gateway_network_id)
ext_ids = {constants.OVN_GW_NETWORK_EXT_ID_KEY: 'net0'}
expected_calls = [mock.call('Logical_Router', lr0.uuid,
('external_ids', ext_ids))]
nb_idl.db_set.assert_has_calls(expected_calls)

View File

@ -432,6 +432,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with( self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with(
'neutron-router-id', enabled=True, external_ids={ 'neutron-router-id', enabled=True, external_ids={
ovn_const.OVN_GW_PORT_EXT_ID_KEY: '', ovn_const.OVN_GW_PORT_EXT_ID_KEY: '',
ovn_const.OVN_GW_NETWORK_EXT_ID_KEY: '',
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router', ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router',
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''}) ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''})
@ -453,6 +454,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
external_ids={ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'test', external_ids={ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'test',
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
ovn_const.OVN_GW_PORT_EXT_ID_KEY: '', ovn_const.OVN_GW_PORT_EXT_ID_KEY: '',
ovn_const.OVN_GW_NETWORK_EXT_ID_KEY: '',
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''}) ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''})
@mock.patch.object(utils, 'get_lrouter_non_gw_routes') @mock.patch.object(utils, 'get_lrouter_non_gw_routes')
@ -547,6 +549,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
external_ids = {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router', external_ids = {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router',
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
ovn_const.OVN_GW_PORT_EXT_ID_KEY: 'gw-port-id', ovn_const.OVN_GW_PORT_EXT_ID_KEY: 'gw-port-id',
ovn_const.OVN_GW_NETWORK_EXT_ID_KEY: 'ext-network-id',
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''} ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''}
self.l3_inst._nb_ovn.create_lrouter.assert_called_once_with( self.l3_inst._nb_ovn.create_lrouter.assert_called_once_with(
'neutron-router-id', external_ids=external_ids, 'neutron-router-id', external_ids=external_ids,
@ -581,6 +584,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
def test_delete_router_with_ext_gw(self, gprs): def test_delete_router_with_ext_gw(self, gprs):
self.get_router.return_value = self.fake_router_with_ext_gw self.get_router.return_value = self.fake_router_with_ext_gw
self.get_subnet.return_value = self.fake_ext_subnet self.get_subnet.return_value = self.fake_ext_subnet
self.l3_inst._nb_ovn.get_lrouter.return_value = (
fake_resources.FakeOVNRouter.from_neutron_router(
self.fake_router_with_ext_gw))
self.l3_inst.delete_router(self.context, 'router-id') self.l3_inst.delete_router(self.context, 'router-id')

View File

@ -0,0 +1,5 @@
---
features:
- |
Added support for router gateway IP QoS in OVN backend. The L3 OVN router
plugin now can apply router QoS policy rules on the router gateway port.