[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:
parent
1b68aebaba
commit
2d1b4fd80f
@ -26,9 +26,11 @@ OVN_PORT_NAME_EXT_ID_KEY = 'neutron:port_name'
|
||||
OVN_PORT_EXT_ID_KEY = 'neutron:port_id'
|
||||
OVN_PORT_FIP_EXT_ID_KEY = 'neutron:port_fip'
|
||||
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_ROUTER_IS_EXT_GW = 'neutron:is_ext_gw'
|
||||
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_IDS_KEY = 'neutron:subnet_ids'
|
||||
OVN_PHYSNET_EXT_ID_KEY = 'neutron:provnet-physical-network'
|
||||
|
@ -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_bw_limit_direction
|
||||
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_filter
|
||||
from neutron_lib.api.definitions import qos_rules_alias
|
||||
@ -87,6 +88,7 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
|
||||
floatingip_pools.ALIAS,
|
||||
pagination.ALIAS,
|
||||
'qos-fip',
|
||||
qos_gateway_ip.ALIAS,
|
||||
sorting.ALIAS,
|
||||
project_id.ALIAS,
|
||||
dns.ALIAS,
|
||||
|
@ -15,6 +15,7 @@
|
||||
from neutron.objects.qos import binding as qos_binding
|
||||
from neutron.objects.qos import policy as qos_policy
|
||||
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 context as n_context
|
||||
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,
|
||||
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
|
||||
|
||||
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
|
||||
a logical switch port located in the chassis
|
||||
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
|
||||
one (without any bandwidth or DSCP information) to be
|
||||
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
|
||||
# FIP that are attached to.
|
||||
# 1) L3 floating IP ports.
|
||||
if 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:
|
||||
key, value = ovn_const.OVN_PORT_EXT_ID_KEY, port_id
|
||||
ovn_qos_rule['external_ids'] = {key: value}
|
||||
@ -365,6 +373,49 @@ class OVNClientQosExtension(object):
|
||||
def disassociate_floatingip(self, 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):
|
||||
updated_port_ids = set([])
|
||||
updated_fip_ids = set([])
|
||||
@ -399,3 +450,8 @@ class OVNClientQosExtension(object):
|
||||
for fip in self._plugin_l3.get_floatingips(
|
||||
context, filters={'id': fip_ids}):
|
||||
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)
|
||||
|
@ -734,6 +734,39 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
|
||||
txn.add(cmd)
|
||||
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):
|
||||
|
||||
|
@ -1278,6 +1278,8 @@ class OVNClient(object):
|
||||
return networks
|
||||
|
||||
def _gen_router_ext_ids(self, router):
|
||||
gw_net_id = (router.get('external_gateway_info') or
|
||||
{}).get('network_id') or ''
|
||||
return {
|
||||
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY:
|
||||
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(
|
||||
router, ovn_const.TYPE_ROUTERS)),
|
||||
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):
|
||||
"""Create a logical router."""
|
||||
@ -1311,6 +1315,8 @@ class OVNClient(object):
|
||||
added_gw_port = self._add_router_ext_gw(
|
||||
router, networks, txn)
|
||||
|
||||
self._qos_driver.create_router(txn, router)
|
||||
|
||||
if added_gw_port:
|
||||
db_rev.bump_revision(context, added_gw_port,
|
||||
ovn_const.TYPE_ROUTER_PORTS)
|
||||
@ -1381,6 +1387,7 @@ class OVNClient(object):
|
||||
old_routes, routes)
|
||||
self.update_router_routes(
|
||||
context, router_id, added, removed, txn=txn)
|
||||
self._qos_driver.update_router(txn, new_router)
|
||||
|
||||
if check_rev_cmd.result == ovn_const.TXN_COMMITTED:
|
||||
db_rev.bump_revision(context, new_router,
|
||||
@ -1403,8 +1410,13 @@ class OVNClient(object):
|
||||
def delete_router(self, context, router_id):
|
||||
"""Delete a logical router."""
|
||||
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:
|
||||
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)
|
||||
|
||||
def get_candidates_for_scheduling(self, physnet, cms=None,
|
||||
|
@ -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 provider_net as pnet
|
||||
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 registry
|
||||
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_port_details
|
||||
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.models import l3 as l3_models
|
||||
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,
|
||||
router_az_db.RouterAvailabilityZoneMixin,
|
||||
l3_fip_qos.FloatingQoSDbMixin,
|
||||
l3_gateway_ip_qos.L3_gw_ip_qos_db_mixin,
|
||||
l3_fip_pools_db.FloatingIPPoolsMixin,
|
||||
):
|
||||
"""Implementation of the OVN L3 Router Service Plugin.
|
||||
@ -98,17 +101,20 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
events.PRECOMMIT_CREATE)
|
||||
|
||||
@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_aliases = qos_fip_apidef.ALIAS in aliases
|
||||
if not qos_service_plugin and qos_aliases:
|
||||
qos_fip_in_aliases = qos_fip_apidef.ALIAS in 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)
|
||||
if not qos_service_plugin and qos_gwip_in_aliases:
|
||||
aliases.remove(qos_gateway_ip_apidef.ALIAS)
|
||||
|
||||
@property
|
||||
def supported_extension_aliases(self):
|
||||
if not hasattr(self, '_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
|
||||
|
||||
@property
|
||||
|
@ -152,6 +152,7 @@ class FakeOvsdbNbOvnIdl(object):
|
||||
self.ls_get = mock.Mock()
|
||||
self.check_liveness = mock.Mock()
|
||||
self.ha_chassis_group_get = mock.Mock()
|
||||
self.qos_del = mock.Mock()
|
||||
self.qos_del_ext_ids = mock.Mock()
|
||||
self.meter_add = mock.Mock()
|
||||
self.meter_del = mock.Mock()
|
||||
|
@ -12,11 +12,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import random
|
||||
from unittest import mock
|
||||
|
||||
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 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 context
|
||||
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.common.ovn import constants as ovn_const
|
||||
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 ports as port_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.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
|
||||
import qos as qos_extension
|
||||
from neutron.tests.unit.extensions import test_l3
|
||||
from neutron.tests.unit.plugins.ml2 import test_plugin
|
||||
|
||||
|
||||
@ -53,12 +59,21 @@ class _Context(object):
|
||||
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):
|
||||
|
||||
CORE_PLUGIN_CLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||
_extension_drivers = [qos_api.ALIAS]
|
||||
l3_plugin = ('neutron.tests.unit.extensions.test_qos_fip.'
|
||||
'TestFloatingIPQoSL3NatServicePlugin')
|
||||
l3_plugin = ('neutron.tests.unit.plugins.ml2.drivers.ovn.'
|
||||
'mech_driver.ovsdb.extensions.'
|
||||
'test_qos.TestFloatingIPQoSL3NatServicePlugin')
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override('extension_drivers', self._extension_drivers,
|
||||
@ -101,21 +116,37 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
|
||||
return port
|
||||
|
||||
def _create_one_router(self):
|
||||
self.router_gw_port = self._create_one_port(2000, self.fips_network.id)
|
||||
self.router = router_obj.Router(self.ctx, id=uuidutils.generate_uuid(),
|
||||
gw_port_id=self.router_gw_port.id)
|
||||
self.router.create()
|
||||
network = network_obj.Network(
|
||||
self.ctx, id=uuidutils.generate_uuid(), project_id=self.project_id)
|
||||
network.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):
|
||||
self.qos_policies = []
|
||||
self.ports = []
|
||||
self.networks = []
|
||||
self.fips = []
|
||||
self.fips_network = network_obj.Network(
|
||||
self.ctx, id=uuidutils.generate_uuid(), project_id=self.project_id)
|
||||
self.fips_network.create()
|
||||
self._create_one_router()
|
||||
self.router_fips, self.fips_network = self._create_one_router()
|
||||
self.fips_ports = []
|
||||
self.routers = []
|
||||
self.router_networks = []
|
||||
fip_cidr = netaddr.IPNetwork('10.10.0.0/24')
|
||||
|
||||
for net_idx in range(2):
|
||||
@ -154,6 +185,10 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
|
||||
self.ports.append(
|
||||
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(rule_obj, 'get_rules')
|
||||
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
|
||||
fip1: qos_policy0
|
||||
fip2: qos_policy1
|
||||
router1: qos_policy0
|
||||
router2: qos_policy1
|
||||
"""
|
||||
self.ports[1].qos_policy_id = self.qos_policies[0].id
|
||||
self.ports[1].update()
|
||||
@ -494,11 +531,15 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
|
||||
self.fips[0].update()
|
||||
self.fips[1].qos_policy_id = self.qos_policies[1].id
|
||||
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()
|
||||
with mock.patch.object(self.qos_driver, '_qos_rules',
|
||||
return_value=mock_qos_rules), \
|
||||
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])
|
||||
updated_ports = [self.ports[1], self.ports[3], self.ports[4]]
|
||||
calls = [mock.call(self.txn, port.id, port.network_id,
|
||||
@ -512,6 +553,11 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
|
||||
self.fips[0].id)
|
||||
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):
|
||||
# NOTE(ralonsoh): this rule will always apply:
|
||||
# - 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()
|
||||
|
||||
# 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.update()
|
||||
self.qos_driver.update_floatingip(txn, fip)
|
||||
@ -587,7 +633,7 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
|
||||
fip.router_id = None
|
||||
fip.fixed_port_id = None
|
||||
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.qos_policy_id = self.qos_policies[1].id
|
||||
original_fip.update()
|
||||
@ -604,3 +650,30 @@ class TestOVNClientQosExtension(test_plugin.Ml2PluginV2TestCase):
|
||||
nb_idl.qos_del_ext_ids.assert_called_once()
|
||||
nb_idl.qos_add.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)
|
||||
|
@ -589,3 +589,29 @@ class TestDBInconsistenciesPeriodics(testlib_api.SqlTestCaseLight,
|
||||
mock.call('Logical_Router_Port', 'lrp-port1', ('options', opt))]
|
||||
self.fake_ovn_client._nb_idl.db_set.assert_has_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)
|
||||
|
@ -432,6 +432,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with(
|
||||
'neutron-router-id', enabled=True, external_ids={
|
||||
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_ROUTER_NAME_EXT_ID_KEY: 'router',
|
||||
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',
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
ovn_const.OVN_GW_PORT_EXT_ID_KEY: '',
|
||||
ovn_const.OVN_GW_NETWORK_EXT_ID_KEY: '',
|
||||
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''})
|
||||
|
||||
@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',
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
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: ''}
|
||||
self.l3_inst._nb_ovn.create_lrouter.assert_called_once_with(
|
||||
'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):
|
||||
self.get_router.return_value = self.fake_router_with_ext_gw
|
||||
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')
|
||||
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user