Router flavors and service type for OVN

Support is added to the OVN L3 service plugin for the router
flavors and service type framework

Partial-Bug: #2020823
Change-Id: If40d7b39e7b59a39ff7622bd823dbdb14bfc69d2
This commit is contained in:
Miguel Lavalle 2023-05-23 19:48:49 -05:00
parent b28bf2d3a1
commit 49366ecada
23 changed files with 1211 additions and 329 deletions

View File

@ -0,0 +1,148 @@
.. _config-router-flavor-ovn:
===================================================
Creating a L3 OVN router with a user-defined flavor
===================================================
In this section we describe the steps necessary to create a router with a user
defined flavor.
.. note::
The following example refers to a dummy user-defined service provider,
which in a real situation must be replaced with user provided code.
#. Add the service provider to neutron.conf:
.. code-block:: console
[service_providers]
service_provider = L3_ROUTER_NAT:user-defined:neutron.services.ovn_l3.service_providers.user_defined.UserDefined
#. Re-start the neutron server and verify the user-defined provider has been
loaded:
.. code-block:: console
$ openstack network service provider list
+---------------+--------------+---------+
| Service Type | Name | Default |
+---------------+--------------+---------+
| L3_ROUTER_NAT | user-defined | False |
| L3_ROUTER_NAT | ovn | True |
+---------------+--------------+---------+
#. Create a service profile for the router flavor:
.. code-block:: console
$ openstack network flavor profile create --description "User-defined router flavor profile" --enable --driver neutron.services.ovn_l3.service_providers.user_defined.UserDefined
+-------------+--------------------------------------------------------------------+
| Field | Value |
+-------------+--------------------------------------------------------------------+
| description | User-defined router flavor profile |
| driver | neutron.services.ovn_l3.service_providers.user_defined.UserDefined |
| enabled | True |
| id | a717c92c-63f7-47e8-9efb-6ad0d61c4875 |
| meta_info | |
| project_id | None |
+-------------+--------------------------------------------------------------------+
#. Create the router flavor:
.. code-block:: console
$ openstack network flavor create --service-type L3_ROUTER_NAT --description "User-defined flavor for routers in the L3 OVN plugin" user-defined-router-flavor
+---------------------+------------------------------------------------------+
| Field | Value |
+---------------------+------------------------------------------------------+
| description | User-defined flavor for routers in the L3 OVN plugin |
| enabled | True |
| id | e47c1c5c-629b-4c48-b49a-78abe6ac7696 |
| name | user-defined-router-flavor |
| service_profile_ids | [] |
| service_type | L3_ROUTER_NAT |
+---------------------+------------------------------------------------------+
#. Add service profile to router flavor:
.. code-block:: console
$ openstack network flavor add profile user-defined-router-flavor a717c92c-63f7-47e8-9efb-6ad0d61c4875
#. Create router specifying user-defined flavor:
.. code-block:: console
$ openstack router create router-of-user-defined-flavor --external-gateway public --flavor-id e47c1c5c-629b-4c48-b49a-78abe6ac7696 --max-width 100
+-------------------------+------------------------------------------------------------------------+
| Field | Value |
+-------------------------+------------------------------------------------------------------------+
| admin_state_up | UP |
| availability_zone_hints | |
| availability_zones | |
| created_at | 2023-05-25T22:34:16Z |
| description | |
| enable_ndp_proxy | None |
| external_gateway_info | {"network_id": "ba485dc9-2459-41c1-9d4f-71914a7fba2a", |
| | "external_fixed_ips": [{"subnet_id": |
| | "2e3adb94-c544-4916-a9fb-27a9dea21820", "ip_address": "172.24.8.69"}, |
| | {"subnet_id": "996ed143-917b-4783-8349-03c6a6d9603e", "ip_address": |
| | "2001:db8::261"}], "enable_snat": true} |
| flavor_id | e47c1c5c-629b-4c48-b49a-78abe6ac7696 |
| id | 9f5fec56-1829-4bad-abe5-7b4221649c8e |
| name | router-of-user-defined-flavor |
| project_id | b807321af03f44dc808ff06bbc845804 |
| revision_number | 3 |
| routes | |
| status | ACTIVE |
| tags | |
| tenant_id | b807321af03f44dc808ff06bbc845804 |
| updated_at | 2023-05-25T22:34:16Z |
+-------------------------+------------------------------------------------------------------------+
#. Create an OVN flavor router to verify they co-exist with the user-defined
flavor:
.. code-block:: console
$ openstack router create ovn-flavor-router --external-gateway public --max-width 100
+-------------------------+------------------------------------------------------------------------+
| Field | Value |
+-------------------------+------------------------------------------------------------------------+
| admin_state_up | UP |
| availability_zone_hints | |
| availability_zones | |
| created_at | 2023-05-25T23:34:20Z |
| description | |
| enable_ndp_proxy | None |
| external_gateway_info | {"network_id": "ba485dc9-2459-41c1-9d4f-71914a7fba2a", |
| | "external_fixed_ips": [{"subnet_id": |
| | "2e3adb94-c544-4916-a9fb-27a9dea21820", "ip_address": "172.24.8.195"}, |
| | {"subnet_id": "996ed143-917b-4783-8349-03c6a6d9603e", "ip_address": |
| | "2001:db8::263"}], "enable_snat": true} |
| flavor_id | None |
| id | 21889ed3-b8df-4b0e-9a64-92ba9fab655d |
| name | ovn-flavor-router |
| project_id | b807321af03f44dc808ff06bbc845804 |
| revision_number | 3 |
| routes | |
| status | ACTIVE |
| tags | |
| tenant_id | e6d6b109d16b4e5e857a10034f4ba558 |
| updated_at | 2023-07-20T23:34:21Z |
+-------------------------+------------------------------------------------------------------------+
#. List routers to verify:
.. code-block:: console
$ openstack router list
+--------------------------------------+-------------------------------+--------+-------+----------------------------------+
| ID | Name | Status | State | Project |
+--------------------------------------+-------------------------------+--------+-------+----------------------------------+
| 21889ed3-b8df-4b0e-9a64-92ba9fab655d | ovn-flavor-router | ACTIVE | UP | b807321af03f44dc808ff06bbc845804 |
| 9f5fec56-1829-4bad-abe5-7b4221649c8e | router-of-user-defined-flavor | ACTIVE | UP | b807321af03f44dc808ff06bbc845804 |
| e9f25566-ff73-4a76-aeb4-969c819f9c47 | router1 | ACTIVE | UP | 1bf97e3957654c0182a48727d619e00f |
+--------------------------------------+-------------------------------+--------+-------+----------------------------------+

View File

@ -38,6 +38,7 @@ Configuration
config-qos-min-pps
config-rbac
config-routed-networks
config-router-flavor-ovn
config-sriov
config-sfc
config-service-subnets

View File

@ -36,10 +36,12 @@ from neutron_lib.api.definitions import fip_pf_port_range
from neutron_lib.api.definitions import fip_port_details
from neutron_lib.api.definitions import firewall_v2
from neutron_lib.api.definitions import firewall_v2_stdattrs
from neutron_lib.api.definitions import flavors
from neutron_lib.api.definitions import floating_ip_port_forwarding
from neutron_lib.api.definitions import floatingip_pools
from neutron_lib.api.definitions import l3
from neutron_lib.api.definitions import l3_ext_gw_mode
from neutron_lib.api.definitions import l3_flavors
from neutron_lib.api.definitions import logging
from neutron_lib.api.definitions import multiprovidernet
from neutron_lib.api.definitions import network_availability_zone
@ -114,6 +116,8 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
agent_def.ALIAS,
az_def.ALIAS,
raz_def.ALIAS,
flavors.ALIAS,
l3_flavors.ALIAS,
]
ML2_SUPPORTED_API_EXTENSIONS = [
address_group.ALIAS,

View File

@ -1215,3 +1215,8 @@ def is_additional_chassis_supported(idl):
def is_nat_gateway_port_supported(idl):
return idl.is_col_present('NAT', 'gateway_port')
def is_ovn_provider_router(router):
flavor_id = router.get('flavor_id')
return flavor_id is None or flavor_id is const.ATTR_NOT_SPECIFIED

View File

@ -1216,20 +1216,21 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
gw_ips = [x['ip_address'] for x in router.gw_port.fixed_ips]
cidrs = [x['cidr'] for x in subnets]
subnet_ids = [subnet['id'] for subnet in subnets]
metadata = {'interface_info': interface_info,
'port': port, 'gateway_ips': gw_ips,
'network_id': gw_network_id, 'cidrs': cidrs}
'network_id': gw_network_id, 'cidrs': cidrs,
'subnet_ids': subnet_ids}
registry.publish(resources.ROUTER_INTERFACE,
events.AFTER_DELETE, self,
payload=events.DBEventPayload(
context, metadata=metadata,
resource_id=router_id))
resource_id=router_id,
states=(router,)))
return self._make_router_interface_info(router_id, port['tenant_id'],
port['id'], port['network_id'],
subnets[0]['id'],
[subnet['id'] for subnet in
subnets])
subnets[0]['id'], subnet_ids)
def _get_floatingip(self, context, id):
floatingip = l3_obj.FloatingIP.get_object(context, id=id)
@ -1571,7 +1572,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
payload=events.DBEventPayload(
context, states=(floatingip_dict,),
resource_id=floatingip_obj.id,
metadata={'association_event': assoc_result}))
metadata={'association_event': assoc_result},
request_body=floatingip))
if assoc_result:
LOG.info(FIP_ASSOC_MSG,
{'fip_id': floatingip_obj.id,
@ -1637,7 +1639,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
payload=events.DBEventPayload(
context, states=(old_floatingip, floatingip_dict),
resource_id=floatingip_obj.id,
metadata={'association_event': assoc_result}))
metadata={'association_event': assoc_result},
request_body=floatingip))
if assoc_result is not None:
port_id = old_fixed_port_id or floatingip_obj.fixed_port_id
assoc = 'associated' if assoc_result else 'disassociated'

View File

@ -57,6 +57,7 @@ from neutron.db import ovn_hash_ring_db
from neutron.db import ovn_revision_numbers_db
from neutron.db import provisioning_blocks
from neutron.extensions import securitygroup as ext_sg
from neutron.objects import router
from neutron.plugins.ml2 import db as ml2_db
from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent as n_agent
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
@ -689,12 +690,18 @@ class OVNMechanismDriver(api.MechanismDriver):
# in the case of router ports we also need to
# track the creation and update of the LRP OVN objects
if ovn_utils.is_lsp_router_port(port):
if (ovn_utils.is_lsp_router_port(port) and
self._is_ovn_router_flavor_port(context, port)):
ovn_revision_numbers_db.create_initial_revision(
context.plugin_context, port['id'],
ovn_const.TYPE_ROUTER_PORTS,
std_attr_id=context.current['standard_attr_id'])
def _is_ovn_router_flavor_port(self, context, port):
router_obj = router.Router.get_object(context.plugin_context,
id=port['device_id'])
return ovn_utils.is_ovn_provider_router(router_obj)
def _is_port_provisioning_required(self, port, host, original_host=None):
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
if vnic_type not in self.supported_vnic_types:
@ -829,7 +836,8 @@ class OVNMechanismDriver(api.MechanismDriver):
self._insert_port_provisioning_block(context.plugin_context,
port['id'])
if ovn_utils.is_lsp_router_port(port):
if (ovn_utils.is_lsp_router_port(port) and
self._is_ovn_router_flavor_port(context, port)):
# handle the case when an existing port is added to a
# logical router so we need to track the creation of the lrp
if not ovn_utils.is_lsp_router_port(original_port):

View File

@ -391,7 +391,7 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
def _create_lrouter_port(self, context, port):
router_id = port['device_id']
iface_info = self._ovn_client._l3_plugin._add_neutron_router_interface(
context, router_id, {'port_id': port['id']}, may_exist=True)
context, router_id, {'port_id': port['id']})
self._ovn_client.create_router_port(context, router_id, iface_info)
def _check_subnet_global_dhcp_opts(self):

View File

@ -51,6 +51,7 @@ from neutron.conf.agent import ovs_conf
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.db import segments_db
from neutron.objects import router
from neutron.plugins.ml2 import db as ml2_db
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
import placement as placement_extension
@ -646,7 +647,10 @@ class OVNClient(object):
# LogicalSwitchPortUpdateDownEvent, that will most likely
# cause a revision conflict.
# https://bugs.launchpad.net/neutron/+bug/1955578
columns_dict['type'] = ovn_const.LSP_TYPE_ROUTER
router_obj = router.Router.get_object(context,
id=port['device_id'])
if utils.is_ovn_provider_router(router_obj):
columns_dict['type'] = ovn_const.LSP_TYPE_ROUTER
port_info.options.update(
self._nb_idl.get_router_port_options(port['id']))
else:

View File

@ -87,7 +87,7 @@ class DriverController(object):
router = payload.latest_state
router_db = payload.metadata['router_db']
router_id = payload.resource_id
if _flavor_specified(router):
if flavor_specified(router):
router_db.flavor_id = router['flavor_id']
drv = self._get_provider_for_create(context, router)
self._stm.add_resource_association(context, plugin_constants.L3,
@ -127,7 +127,7 @@ class DriverController(object):
drv = self.get_provider_for_router(payload.context,
payload.resource_id)
new_drv = None
if _flavor_specified(payload.request_body):
if flavor_specified(payload.request_body):
if (payload.request_body['flavor_id'] !=
payload.states[0]['flavor_id']):
# TODO(kevinbenton): this is currently disallowed by the API
@ -210,7 +210,7 @@ class DriverController(object):
def _get_provider_for_create(self, context, router):
"""Get provider based on flavor or ha/distributed flags."""
if not _flavor_specified(router):
if not flavor_specified(router):
return self._attrs_to_driver(router)
return self._get_l3_driver_by_flavor(context, router['flavor_id'])
@ -293,7 +293,7 @@ def _is_ha(ha_attr):
return True
def _flavor_specified(router):
def flavor_specified(router):
return ('flavor_id' in router and
router['flavor_id'] != lib_const.ATTR_NOT_SPECIFIED)

View File

@ -13,6 +13,7 @@
#
from neutron_lib.api.definitions import external_net
from neutron_lib.api.definitions import l3 as l3_apidef
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
@ -23,19 +24,18 @@ from neutron_lib.callbacks import resources
from neutron_lib import constants as n_const
from neutron_lib import context as n_context
from neutron_lib.db import api as db_api
from neutron_lib.db import resource_extend
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import availability_zone as az_exc
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from neutron_lib.services import base as service_base
from oslo_log import log
from oslo_utils import excutils
from neutron._i18n import _
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import extensions
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.db.availability_zone import router as router_az_db
from neutron.db import dns_db
from neutron.db import extraroute_db
@ -46,11 +46,11 @@ 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
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
from neutron.quota import resource_registry
from neutron.scheduler import l3_ovn_scheduler
from neutron.services.ovn_l3 import exceptions as ovn_l3_exc
from neutron.services.ovn_l3.service_providers import driver_controller
from neutron.services.portforwarding.drivers.ovn import driver \
as port_forwarding
@ -94,15 +94,7 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
self._ovn_client_inst = None
self.scheduler = l3_ovn_scheduler.get_scheduler()
self.port_forwarding = port_forwarding.OVNPortForwarding(self)
self._register_precommit_callbacks()
def _register_precommit_callbacks(self):
registry.subscribe(
self.create_router_precommit, resources.ROUTER,
events.PRECOMMIT_CREATE)
registry.subscribe(
self.create_floatingip_precommit, resources.FLOATING_IP,
events.PRECOMMIT_CREATE)
self.l3_driver_controller = driver_controller.DriverController(self)
@staticmethod
def _disable_qos_extensions_by_extension_drivers(aliases):
@ -170,67 +162,13 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
return ("L3 Router Service Plugin for basic L3 forwarding"
" using OVN")
def create_router_precommit(self, resource, event, trigger, payload):
context = payload.context
context.session.flush()
router_id = payload.resource_id
router_db = payload.metadata['router_db']
# NOTE(ralonsoh): the "distributed" flag is a static configuration
# parameter that needs to be defined only during the router creation.
extra_attr = router_db['extra_attributes']
extra_attr.distributed = ovn_conf.is_ovn_distributed_floating_ip()
db_rev.create_initial_revision(
context, router_id, ovn_const.TYPE_ROUTERS,
std_attr_id=router_db.standard_attr.id)
def create_router(self, context, router):
router = super(OVNL3RouterPlugin, self).create_router(context, router)
try:
self._ovn_client.create_router(context, router)
except Exception:
with excutils.save_and_reraise_exception():
# Delete the logical router
LOG.exception('Unable to create lrouter %s', router['id'])
super(OVNL3RouterPlugin, self).delete_router(context,
router['id'])
return router
def update_router(self, context, id, router):
original_router = self.get_router(context, id)
result = super(OVNL3RouterPlugin, self).update_router(context, id,
router)
try:
self._ovn_client.update_router(context, result,
original_router)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to update lrouter %s', id)
revert_router = {'router': original_router}
super(OVNL3RouterPlugin, self).update_router(context, id,
revert_router)
return result
def delete_router(self, context, id):
original_router = self.get_router(context, id)
super(OVNL3RouterPlugin, self).delete_router(context, id)
try:
self._ovn_client.delete_router(context, id)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to delete lrouter %s', id)
super(OVNL3RouterPlugin, self).create_router(
context, {'router': original_router})
def _add_neutron_router_interface(self, context, router_id,
interface_info, may_exist=False):
interface_info):
try:
router_interface_info = (
super(OVNL3RouterPlugin, self).add_router_interface(
context, router_id, interface_info))
except n_exc.PortInUse:
if not may_exist:
raise
# NOTE(lucasagomes): If the port is already being used it means
# the interface has been created already, let's just fetch it from
# the database. Perhaps the code below should live in Neutron
@ -247,70 +185,24 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
return router_interface_info
def add_router_interface(self, context, router_id, interface_info=None):
router_interface_info = self._add_neutron_router_interface(
context, router_id, interface_info)
try:
self._ovn_client.create_router_port(
context, router_id, router_interface_info)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(
'Unable to add router interface to lrouter %s. '
'Interface info: %s', router_id, interface_info)
super(OVNL3RouterPlugin, self).remove_router_interface(
context, router_id, router_interface_info)
return router_interface_info
def remove_router_interface(self, context, router_id, interface_info):
router_interface_info = (
super(OVNL3RouterPlugin, self).remove_router_interface(
context, router_id, interface_info))
try:
port_id = router_interface_info['port_id']
subnet_ids = router_interface_info.get('subnet_ids')
self._ovn_client.delete_router_port(
context, port_id, router_id=router_id, subnet_ids=subnet_ids)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(
'Unable to remove router interface from lrouter %s. '
'Interface info: %s', router_id, interface_info)
super(OVNL3RouterPlugin, self).add_router_interface(
context, router_id, interface_info)
return router_interface_info
def create_floatingip_precommit(self, resource, event, trigger, payload):
context = payload.context
floatingip_id = payload.resource_id
floatingip_db = payload.desired_state
db_rev.create_initial_revision(
context, floatingip_id, ovn_const.TYPE_FLOATINGIPS,
std_attr_id=floatingip_db.standard_attr.id)
def create_floatingip(self, context, floatingip,
initial_status=n_const.FLOATINGIP_STATUS_DOWN):
fip = super(OVNL3RouterPlugin, self).create_floatingip(
# The OVN L3 plugin creates floating IPs in down status by default,
# whereas the L3 DB layer creates them in active status. So we keep
# this method to create the floating IP in the DB with status down,
# while the flavor drivers are responsible for calling the correct
# backend to instatiate the floating IP in the data plane
return super(OVNL3RouterPlugin, self).create_floatingip(
context, floatingip, initial_status)
self._ovn_client.create_floatingip(context, fip)
return fip
def delete_floatingip(self, context, id):
super(OVNL3RouterPlugin, self).delete_floatingip(context, id)
self._ovn_client.delete_floatingip(context, id)
def update_floatingip(self, context, id, floatingip):
fip = super(OVNL3RouterPlugin, self).update_floatingip(context, id,
floatingip)
self._ovn_client.update_floatingip(context, fip, floatingip)
return fip
def update_floatingip_status(self, context, floatingip_id, status):
fip = self.update_floatingip_status_retry(
context, floatingip_id, status)
self._ovn_client.update_floatingip_status(context, fip)
registry.publish(
resources.FLOATING_IP, events.AFTER_STATUS_UPDATE, self,
payload=events.DBEventPayload(
context, states=(fip,),
resource_id=floatingip_id))
return fip
@db_api.retry_if_session_inactive()
@ -319,30 +211,6 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
return super(OVNL3RouterPlugin, self).update_floatingip_status(
context, floatingip_id, status)
def disassociate_floatingips(self, context, port_id, do_notify=True):
fips = self.get_floatingips(context.elevated(),
filters={'port_id': [port_id]})
router_ids = super(OVNL3RouterPlugin, self).disassociate_floatingips(
context, port_id, do_notify)
for fip in fips:
router_id = fip.get('router_id')
fixed_ip_address = fip.get('fixed_ip_address')
if router_id and fixed_ip_address:
update_fip = {
'id': fip['id'],
'logical_ip': fixed_ip_address,
'external_ip': fip['floating_ip_address'],
'floating_network_id': fip['floating_network_id']}
try:
self._ovn_client.disassociate_floatingip(update_fip,
router_id)
self.update_floatingip_status(
context, fip['id'], n_const.FLOATINGIP_STATUS_DOWN)
except Exception as e:
LOG.error('Error in disassociating floatingip %(id)s: '
'%(error)s', {'id': fip['id'], 'error': e})
return router_ids
def _get_gateway_port_physnet_mapping(self):
# This function returns all gateway ports with corresponding
# external network's physnet
@ -359,8 +227,10 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
net_physnet_dict[net['id']] = net.get(pnet.PHYSICAL_NETWORK)
for port in l3plugin._plugin.get_ports(context, filters={
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW]}):
port_physnet_dict[port['id']] = net_physnet_dict.get(
port['network_id'])
if utils.is_ovn_provider_router(
l3plugin.get_router(context, port['device_id'])):
port_physnet_dict[port['id']] = net_physnet_dict.get(
port['network_id'])
return port_physnet_dict
def update_router_gateway_port_bindings(self, router, host):
@ -474,7 +344,9 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW],
'fixed_ips': {'subnet_id': [orig['id']]},
})
router_ids = {port['device_id'] for port in gw_ports}
router_ids = {port['device_id'] for port in gw_ports
if utils.is_ovn_provider_router(
l3plugin.get_router(context, port['device_id']))}
remove = [{'destination': '0.0.0.0/0', 'nexthop': orig_gw_ip}
] if orig_gw_ip else []
add = [{'destination': '0.0.0.0/0', 'nexthop': current_gw_ip}
@ -508,11 +380,16 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
# https://bugs.launchpad.net/neutron/+bug/1948457
if (event == events.BEFORE_UPDATE and
'fixed_ips' in current and not current['fixed_ips'] and
utils.is_lsp_router_port(original)):
utils.is_lsp_router_port(original) and
utils.is_ovn_provider_router(
l3plugin.get_router(context, original['device_id']))):
reason = _("Router port must have at least one IP.")
raise n_exc.ServicePortInUse(port_id=original['id'], reason=reason)
if event == events.AFTER_UPDATE and utils.is_lsp_router_port(current):
if (event == events.AFTER_UPDATE and
utils.is_lsp_router_port(current) and
utils.is_ovn_provider_router(
l3plugin.get_router(context, current['device_id']))):
# We call the update_router port with if_exists, because neutron,
# internally creates the port, and then calls update, which will
# trigger this callback even before we had the chance to create
@ -541,3 +418,8 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
if diff:
raise az_exc.AvailabilityZoneNotFound(
availability_zone=', '.join(diff))
@staticmethod
@resource_extend.extends([l3_apidef.ROUTERS])
def add_flavor_id(router_res, router_db):
router_res['flavor_id'] = router_db['flavor_id']

View File

@ -0,0 +1,65 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib import exceptions as lib_exc
from neutron_lib.plugins import constants as plugin_constants
from oslo_log import log
from neutron.db import servicetype_db as st_db
from neutron.services.l3_router.service_providers import driver_controller
from neutron.services import provider_configuration
LOG = log.getLogger(__name__)
class DriverController(driver_controller.DriverController):
"""Driver controller for the OVN L3 service plugin.
This component is responsible for dispatching router requests to L3
service providers and for performing the bookkeeping about which
driver is associated with a given router.
This is not intended to be accessed by the drivers or the l3 plugin.
All of the methods are marked as private to reflect this.
"""
def __init__(self, l3_plugin):
self.l3_plugin = l3_plugin
self._stm = st_db.ServiceTypeManager.get_instance()
self._stm.add_provider_configuration(
plugin_constants.L3, _OvnPlusProviderConfiguration())
self._load_drivers()
def _get_provider_for_create(self, context, router):
"""Get provider based on flavor or default provider."""
if not driver_controller.flavor_specified(router):
return self.drivers[self.default_provider]
return self._get_l3_driver_by_flavor(context, router['flavor_id'])
class _OvnPlusProviderConfiguration(
provider_configuration.ProviderConfiguration):
def __init__(self):
# loads up the OVN provider automatically and sets it as default.
super(_OvnPlusProviderConfiguration, self).__init__(
svc_type=plugin_constants.L3)
path = 'neutron.services.ovn_l3.service_providers.ovn.OvnDriver'
try:
self.add_provider({'service_type': plugin_constants.L3,
'name': 'ovn', 'driver': path, 'default': True})
except lib_exc.Invalid:
LOG.debug("Could not add L3 provider ovn, it may have "
"already been explicitly defined.")

View File

@ -0,0 +1,252 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants
from neutron_lib.db import api as db_api
from oslo_log import log as logging
from oslo_utils import excutils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.extensions import revisions
from neutron.objects import router as l3_obj
from neutron.services.l3_router.service_providers import base
from neutron.services.portforwarding import constants as pf_consts
LOG = logging.getLogger(__name__)
@registry.has_registry_receivers
class OvnDriver(base.L3ServiceProvider):
ha_support = base.MANDATORY
dvr_support = base.MANDATORY
@registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE])
def _process_router_create_precommit(self, resource, event, trigger,
payload):
context = payload.context
context.session.flush()
router_id = payload.resource_id
router_db = payload.metadata['router_db']
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
# NOTE(ralonsoh): the "distributed" flag is a static configuration
# parameter that needs to be defined only during the router creation.
extra_attr = router_db['extra_attributes']
extra_attr.distributed = ovn_conf.is_ovn_distributed_floating_ip()
db_rev.create_initial_revision(
context, router_id, ovn_const.TYPE_ROUTERS,
std_attr_id=router_db.standard_attr.id)
@registry.receives(resources.ROUTER, [events.AFTER_CREATE])
def _process_router_create(self, resource, event, trigger, payload):
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
context = payload.context
try:
self.l3plugin._ovn_client.create_router(context, router)
except Exception:
with excutils.save_and_reraise_exception():
# Delete the logical router
LOG.exception('Unable to create lrouter %s', router['id'])
self.l3plugin.delete_router(context, router['id'])
@registry.receives(resources.ROUTER, [events.AFTER_UPDATE])
def _process_router_update(self, resource, event, trigger, payload):
router_id = payload.resource_id
original = payload.states[0]
updated = payload.states[1]
if not utils.is_ovn_provider_router(original):
# flavor_id attribute is not allowed in router PUTs, so we only
# need to check the original router
return
context = payload.context
try:
self.l3plugin._ovn_client.update_router(context, updated, original)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to update lrouter %s', router_id)
revert_router = {'router': original}
self.l3plugin.update_router(context, router_id, revert_router)
@registry.receives(resources.ROUTER, [events.AFTER_DELETE])
def _process_router_delete(self, resource, event, trigger, payload):
router_id = payload.resource_id
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
context = payload.context
try:
self.l3plugin._ovn_client.delete_router(context, router_id)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to delete lrouter %s', router['id'])
self.l3plugin.create_router(context, {'router': router})
@registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_CREATE])
def _process_add_router_interface(self, resource, event, trigger, payload):
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
context = payload.context
port = payload.metadata['port']
subnets = payload.metadata['subnets']
router_interface_info = self.l3plugin._make_router_interface_info(
router.id, port['tenant_id'], port['id'], port['network_id'],
subnets[-1]['id'], [subnet['id'] for subnet in subnets])
try:
self.l3plugin._ovn_client.create_router_port(context, router.id,
router_interface_info)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to add router interface to lrouter %s. '
'Interface info: %s', router['id'],
router_interface_info)
self.l3plugin.remove_router_interface(context, router.id,
router_interface_info)
@registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_DELETE])
def _process_remove_router_interface(self, resource, event, trigger,
payload):
router = payload.states[0]
if not utils.is_ovn_provider_router(router):
return
context = payload.context
port = payload.metadata['port']
subnet_ids = payload.metadata['subnet_ids']
router_interface_info = self.l3plugin._make_router_interface_info(
router.id, port['tenant_id'], port['id'], port['network_id'],
subnet_ids[0], subnet_ids)
try:
self.l3plugin._ovn_client.delete_router_port(context, port['id'],
router_id=router.id,
subnet_ids=subnet_ids)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Unable to remove router interface from lrouter '
'%s. Interface info: %s', router['id'],
router_interface_info)
self.l3plugin.add_router_interface(
context, router.id, payload.metadata['interface_info'])
def _create_floatingip_initial_revision(self, context, floatingip_db):
if not floatingip_db.router_id:
return
# We get the router with elevated context because floating IPs may be
# created to be associated with a router created by a different
# project. Please see
# https://review.opendev.org/c/openstack/neutron/+/2727B09
router = self.l3plugin.get_router(context.elevated(),
floatingip_db.router_id)
if not utils.is_ovn_provider_router(router):
return
db_rev.create_initial_revision(
context, floatingip_db.id, ovn_const.TYPE_FLOATINGIPS,
may_exist=True, std_attr_id=floatingip_db.standard_attr.id)
@registry.receives(resources.FLOATING_IP,
[events.PRECOMMIT_CREATE, events.PRECOMMIT_UPDATE])
def _process_floatingip_create_update_precommit(self, resource, event,
trigger, payload):
context = payload.context
floatingip_db = payload.desired_state
self._create_floatingip_initial_revision(context, floatingip_db)
@registry.receives(pf_consts.PORT_FORWARDING, [events.AFTER_CREATE])
def _process_portforwarding_create(self, resource, event, trigger,
payload):
context = payload.context
pf_obj = payload.states[0]
with db_api.CONTEXT_WRITER.using(context):
fip_db = l3_obj.FloatingIP.get_object(
context, id=pf_obj.floatingip_id).db_obj
self._create_floatingip_initial_revision(context, fip_db)
@registry.receives(resources.FLOATING_IP, [events.AFTER_CREATE])
def _process_floatingip_create(self, resource, event, trigger, payload):
revision_row = db_rev.get_revision_row(payload.context,
payload.resource_id)
if not revision_row:
return
# The floating ip dictionary that is sent by the L3 DB plugin in the
# notification doesn't include the revision number yet. We add it here
# to the dictionary that is passed to the ovn client
floatingip = copy.deepcopy(payload.states[0])
floatingip[revisions.REVISION] = revision_row.revision_number
qos_policy_id = payload.request_body['floatingip'].get('qos_policy_id')
if qos_policy_id and 'qos_policy_id' not in floatingip:
floatingip['qos_policy_id'] = qos_policy_id
self.l3plugin._ovn_client.create_floatingip(payload.context,
floatingip)
@registry.receives(resources.FLOATING_IP, [events.AFTER_UPDATE])
def _process_floatingip_update(self, resource, event, trigger, payload):
if not db_rev.get_revision_row(payload.context, payload.resource_id):
return
fip = payload.states[1]
old_fip = payload.states[0]
fip_request = payload.request_body
if fip_request:
self.l3plugin._ovn_client.update_floatingip(payload.context, fip,
fip_request)
else:
router_id = old_fip.get('router_id')
fixed_ip_address = old_fip.get('fixed_ip_address')
if router_id and fixed_ip_address:
update_fip = {
'id': old_fip['id'],
'logical_ip': fixed_ip_address,
'external_ip': old_fip['floating_ip_address'],
'floating_network_id': old_fip['floating_network_id']
}
try:
self.l3plugin._ovn_client.disassociate_floatingip(
update_fip, router_id)
self.l3plugin.update_floatingip_status(
payload.context, old_fip['id'],
constants.FLOATINGIP_STATUS_DOWN)
except Exception as e:
LOG.error('Error in disassociating floatingip %(id)s: '
'%(error)s', {'id': old_fip['id'], 'error': e})
if not fip['router_id']:
db_rev.delete_revision(payload.context, payload.resource_id,
ovn_const.TYPE_FLOATINGIPS)
@registry.receives(resources.FLOATING_IP, [events.AFTER_DELETE])
def _process_floatingip_delete(self, resource, event, trigger, payload):
if not db_rev.get_revision_row(payload.context, payload.resource_id):
return
self.l3plugin._ovn_client.delete_floatingip(payload.context,
payload.resource_id)
@registry.receives(resources.FLOATING_IP, [events.AFTER_STATUS_UPDATE])
def _process_floatingip_status_update(self, resource, event, trigger,
payload):
if not db_rev.get_revision_row(payload.context, payload.resource_id):
return
self.l3plugin._ovn_client.update_floatingip_status(payload.context,
payload.states[0])

View File

@ -0,0 +1,170 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from oslo_log import log as logging
from neutron.services.l3_router.service_providers import base
LOG = logging.getLogger(__name__)
@registry.has_registry_receivers
class UserDefined(base.L3ServiceProvider):
def __init__(self, l3_plugin):
super(UserDefined, self).__init__(l3_plugin)
self._user_defined_provider = __name__ + "." + self.__class__.__name__
@property
def _flavor_plugin(self):
try:
return self._flavor_plugin_ref
except AttributeError:
self._flavor_plugin_ref = directory.get_plugin(
plugin_constants.FLAVORS)
return self._flavor_plugin_ref
def _is_user_defined_provider(self, context, router):
flavor_id = router.get('flavor_id')
if flavor_id is None:
return False
flavor = self._flavor_plugin.get_flavor(context, flavor_id)
provider = self._flavor_plugin.get_flavor_next_provider(
context, flavor['id'])[0]
return str(provider['driver']) == self._user_defined_provider
@registry.receives(resources.ROUTER_CONTROLLER,
[events.PRECOMMIT_ADD_ASSOCIATION])
def _process_router_add_association(self, resource, event, trigger,
payload=None):
router = payload.states[0]
context = payload.context
if not self._is_user_defined_provider(context, router):
return
LOG.debug('Got request to associate user defined flavor to router %s',
router)
@registry.receives(resources.ROUTER, [events.AFTER_CREATE])
def _process_router_create(self, resource, event, trigger, payload=None):
router = payload.states[0]
context = payload.context
if not self._is_user_defined_provider(context, router):
return
LOG.debug('Got request to create a user defined flavor router %s',
router)
@registry.receives(resources.ROUTER, [events.AFTER_UPDATE])
def _process_router_update(self, resource, event, trigger, payload=None):
original = payload.states[0]
updated = payload.states[1]
context = payload.context
if not self._is_user_defined_provider(context, original):
# flavor_id attribute is not allowed in router PUTs, so we only
# need to check the original router
return
router_id = payload.resource_id
LOG.debug('Got request to update a user defined flavor router with id '
'%s. Original: %s. Updated: %s', router_id, original,
updated)
@registry.receives(resources.ROUTER, [events.AFTER_DELETE])
def _process_router_delete(self, resource, event, trigger, payload=None):
router = payload.states[0]
context = payload.context
if not self._is_user_defined_provider(context, router):
return
router_id = payload.resource_id
LOG.debug('Got request to delete a user defined flavor router with ',
'id %s:', router_id)
@registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_CREATE])
def _process_add_router_interface(self, resource, event, trigger, payload):
router = payload.states[0]
context = payload.context
if not self._is_user_defined_provider(context, router):
return
port = payload.metadata['port']
subnets = payload.metadata['subnets']
router_interface_info = self.l3plugin._make_router_interface_info(
router.id, port['tenant_id'], port['id'], port['network_id'],
subnets[-1]['id'], [subnet['id'] for subnet in subnets])
LOG.debug('Got request to add interface %s to a user defined flavor '
'router with id %s', router_interface_info, router.id)
@registry.receives(resources.ROUTER_INTERFACE, [events.AFTER_DELETE])
def _process_remove_router_interface(self, resource, event, trigger,
payload):
router = payload.states[0]
context = payload.context
if not self._is_user_defined_provider(context, router):
return
subnet_ids = payload.metadata['subnet_ids']
LOG.debug('Got request to remove interface to subnets %s from a user '
'defined flavor router with id %s', subnet_ids, router.id)
@registry.receives(resources.FLOATING_IP, [events.AFTER_CREATE])
def _process_floatingip_create(self, resource, event, trigger, payload):
context = payload.context
fip = payload.states[0]
if not fip['router_id']:
return
router = self.l3plugin.get_router(context, fip['router_id'])
if not self._is_user_defined_provider(context, router):
return
LOG.debug('Got request to create a floating ip associated to a router '
'of user defined flavor %s', fip)
@registry.receives(resources.FLOATING_IP, [events.AFTER_UPDATE])
def _process_floatingip_update(self, resource, event, trigger, payload):
context = payload.context
fip = payload.states[1]
if not fip['router_id']:
return
router = self.l3plugin.get_router(context, fip['router_id'])
if not self._is_user_defined_provider(context, router):
return
LOG.debug('Got request to update a floating ip associated to a router '
'of user defined flavor %s', fip)
@registry.receives(resources.FLOATING_IP, [events.AFTER_DELETE])
def _process_floatingip_delete(self, resource, event, trigger, payload):
context = payload.context
fip = payload.states[0]
if not fip['router_id']:
return
router = self.l3plugin.get_router(context, fip['router_id'])
if not self._is_user_defined_provider(context, router):
return
LOG.debug('Got request to delete a floating ip associated to a router '
'of user defined flavor %s', fip)
@registry.receives(resources.FLOATING_IP, [events.AFTER_STATUS_UPDATE])
def _process_floatingip_status_update(self, resource, event, trigger,
payload):
context = payload.context
fip = payload.states[0]
if not fip['router_id']:
return
router = self.l3plugin.get_router(context, fip['router_id'])
if not self._is_user_defined_provider(context, router):
return
LOG.debug('Got request to update the status of a floating ip '
'associated to a router of user defined flavor %s', fip)

View File

@ -21,6 +21,8 @@ from futurist import periodics
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib.api.definitions import floating_ip_port_forwarding as pf_def
from neutron_lib.api.definitions import provider_net as provnet_apidef
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib import constants as n_const
from neutron_lib import context as n_context
from neutron_lib.exceptions import l3 as lib_l3_exc
@ -30,6 +32,7 @@ from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as ovn_config
from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance
from neutron.services.portforwarding import constants as pf_consts
from neutron.tests.functional import base
from neutron.tests.functional.services.logapi.drivers.ovn \
import test_driver as test_log_driver
@ -925,65 +928,69 @@ class TestMaintenance(_TestMaintenanceHelper):
p1 = self._create_port('testp1', net1['id'])
p1_ip = p1['fixed_ips'][0]['ip_address']
with mock.patch('neutron_lib.callbacks.registry.publish') as m_publish:
# > Create
fip_pf_args = {
pf_def.EXTERNAL_PORT: 2222,
pf_def.INTERNAL_PORT: 22,
pf_def.INTERNAL_PORT_ID: p1['id'],
pf_def.PROTOCOL: 'tcp',
pf_def.INTERNAL_IP_ADDRESS: p1_ip}
pf_obj = self.pf_plugin.create_floatingip_port_forwarding(
self.context, fip_id, **fip_attrs(fip_pf_args))
call = mock.call('port_forwarding', 'after_create', self.pf_plugin,
payload=mock.ANY)
m_publish.assert_has_calls([call])
callbacks = registry._get_callback_manager()._callbacks
pf_cb = callbacks[pf_consts.PORT_FORWARDING]
key = list(pf_cb[events.AFTER_UPDATE][0][1].keys())[0]
pf_cb[events.AFTER_CREATE][0][1][key] = mock.MagicMock()
# Assert load balancer for port forwarding was not created
self.assertFalse(self._find_pf_lb(router_id, fip_id))
# > Create
fip_pf_args = {
pf_def.EXTERNAL_PORT: 2222,
pf_def.INTERNAL_PORT: 22,
pf_def.INTERNAL_PORT_ID: p1['id'],
pf_def.PROTOCOL: 'tcp',
pf_def.INTERNAL_IP_ADDRESS: p1_ip}
pf_obj = self.pf_plugin.create_floatingip_port_forwarding(
self.context, fip_id, **fip_attrs(fip_pf_args))
call = mock.call('port_forwarding', 'after_create', self.pf_plugin,
payload=mock.ANY)
pf_cb[events.AFTER_CREATE][0][1][key].assert_has_calls([call])
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert load balancer for port forwarding was not created
self.assertFalse(self._find_pf_lb(router_id, fip_id))
# Assert load balancer for port forwarding was created
_verify_lb(self, 'tcp', 2222, 22)
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# > Update
fip_pf_args = {pf_def.EXTERNAL_PORT: 5353,
pf_def.INTERNAL_PORT: 53,
pf_def.PROTOCOL: 'udp'}
m_publish.reset_mock()
self.pf_plugin.update_floatingip_port_forwarding(
self.context, pf_obj['id'], fip_id, **fip_attrs(fip_pf_args))
call = mock.call('port_forwarding', 'after_update', self.pf_plugin,
payload=mock.ANY)
m_publish.assert_has_calls([call])
# Assert load balancer for port forwarding was created
_verify_lb(self, 'tcp', 2222, 22)
# Assert load balancer for port forwarding is stale
_verify_lb(self, 'tcp', 2222, 22)
# > Update
fip_pf_args = {pf_def.EXTERNAL_PORT: 5353,
pf_def.INTERNAL_PORT: 53,
pf_def.PROTOCOL: 'udp'}
pf_cb[events.AFTER_UPDATE][0][1][key] = mock.MagicMock()
self.pf_plugin.update_floatingip_port_forwarding(
self.context, pf_obj['id'], fip_id, **fip_attrs(fip_pf_args))
call = mock.call('port_forwarding', 'after_update', self.pf_plugin,
payload=mock.ANY)
pf_cb[events.AFTER_UPDATE][0][1][key].assert_has_calls([call])
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert load balancer for port forwarding is stale
_verify_lb(self, 'tcp', 2222, 22)
# Assert load balancer for port forwarding was updated
_verify_lb(self, 'udp', 5353, 53)
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# > Delete
m_publish.reset_mock()
self.pf_plugin.delete_floatingip_port_forwarding(
self.context, pf_obj['id'], fip_id)
call = mock.call('port_forwarding', 'after_delete', self.pf_plugin,
payload=mock.ANY)
m_publish.assert_has_calls([call])
# Assert load balancer for port forwarding was updated
_verify_lb(self, 'udp', 5353, 53)
# Assert load balancer for port forwarding is stale
_verify_lb(self, 'udp', 5353, 53)
# > Delete
pf_cb[events.AFTER_DELETE][0][1][key] = mock.MagicMock()
self.pf_plugin.delete_floatingip_port_forwarding(
self.context, pf_obj['id'], fip_id)
call = mock.call('port_forwarding', 'after_delete', self.pf_plugin,
payload=mock.ANY)
pf_cb[events.AFTER_DELETE][0][1][key].assert_has_calls([call])
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert load balancer for port forwarding is stale
_verify_lb(self, 'udp', 5353, 53)
# Assert load balancer for port forwarding is gone
self.assertFalse(self._find_pf_lb(router_id, fip_id))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert load balancer for port forwarding is gone
self.assertFalse(self._find_pf_lb(router_id, fip_id))
def test_check_for_ha_chassis_group(self):
net1 = self._create_network('network1test', external=False)

View File

@ -620,7 +620,8 @@ class TestExternalPorts(base.TestOVNFunctionalBase):
'ports', port_upt_data, port['id'], self.fmt)
port_res = port_req.get_response(self.api)
def test_add_external_port_avoid_flapping(self):
@mock.patch('neutron.objects.router.Router.get_object')
def test_add_external_port_avoid_flapping(self, gr):
class LogicalSwitchPortUpdateUpEventTest(event.RowEvent):
def __init__(self):
self.count = 0
@ -651,6 +652,7 @@ class TestExternalPorts(base.TestOVNFunctionalBase):
def get_count(self):
return self.count
gr.return_value = {'flavor_id': None}
og_up_event = ovsdb_monitor.LogicalSwitchPortUpdateUpEvent(None)
og_down_event = ovsdb_monitor.LogicalSwitchPortUpdateDownEvent(None)
test_down_event = LogicalSwitchPortUpdateDownEventTest()
@ -666,7 +668,8 @@ class TestExternalPorts(base.TestOVNFunctionalBase):
# status as up, triggering only a LogicalSwitchPortUpdateUpEvent.
self._create_router_port(portbindings.VNIC_DIRECT)
self.assertEqual(test_down_event.get_count(), 0)
self.assertEqual(test_up_event.get_count(), 1)
n_utils.wait_until_true(lambda: test_up_event.get_count() == 1,
timeout=10)
def test_external_port_create_vnic_direct(self):
self._test_external_port_create(portbindings.VNIC_DIRECT)

View File

@ -2224,7 +2224,9 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
self.assertEqual(webob.exc.HTTPClientError.code,
res.status_int)
def test_requested_fixed_ip_address_v6_slaac_router_iface(self):
@mock.patch('neutron.objects.router.Router.get_object')
def test_requested_fixed_ip_address_v6_slaac_router_iface(self, gr):
gr.return_value = {'flavor_id': ''}
with self.subnet(gateway_ip='fe80::1',
cidr='fe80::/64',
ip_version=constants.IP_VERSION_6,
@ -2284,7 +2286,9 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
self.assertIn({'ip_address': eui_addr,
'subnet_id': subnet2['subnet']['id']}, ips)
def test_create_router_port_ipv4_and_ipv6_slaac_no_fixed_ips(self):
@mock.patch('neutron.objects.router.Router.get_object')
def test_create_router_port_ipv4_and_ipv6_slaac_no_fixed_ips(self, gr):
gr.return_value = {'flavor_id': ''}
with self.network() as network:
# Create an IPv4 and an IPv6 SLAAC subnet on the network
with self.subnet(network) as subnet_v4,\
@ -3764,8 +3768,10 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
sport = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(0, len(sport['port']['fixed_ips']))
def test_delete_subnet_ipv6_slaac_router_port_exists(self):
@mock.patch('neutron.objects.router.Router.get_object')
def test_delete_subnet_ipv6_slaac_router_port_exists(self, gr):
"""Test IPv6 SLAAC subnet delete with a router port using the subnet"""
gr.return_value = {'flavor_id': ''}
subnet, port = self._create_slaac_subnet_and_port(
constants.DEVICE_OWNER_ROUTER_INTF)
# Delete the subnet and assert that we get a HTTP 409 error
@ -4502,7 +4508,9 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
ipv6_ra_mode=constants.IPV6_SLAAC,
ipv6_address_mode=constants.IPV6_SLAAC)
def test_create_subnet_ipv6_first_ip_owned_by_router(self):
@mock.patch('neutron.objects.router.Router.get_object')
def test_create_subnet_ipv6_first_ip_owned_by_router(self, gr):
gr.return_value = {'flavor_id': ''}
cidr = '2001::/64'
with self.network() as network:
net_id = network['network']['id']
@ -4598,10 +4606,12 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
self.assertEqual(webob.exc.HTTPClientError.code,
ctx_manager.exception.code)
@mock.patch('neutron.objects.router.Router.get_object')
def _test_create_subnet_ipv6_auto_addr_with_port_on_network(
self, addr_mode, device_owner=DEVICE_OWNER_COMPUTE,
self, addr_mode, gr, device_owner=DEVICE_OWNER_COMPUTE,
insert_db_reference_error=False, insert_port_not_found=False,
insert_address_allocated=False):
gr.return_value = {'flavor_id': ''}
# Create a network with one IPv4 subnet and one port
with self.network() as network,\
self.subnet(network=network) as v4_subnet,\

View File

@ -2495,9 +2495,12 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
def test__update_dnat_entry_if_needed_down_no_dvr(self):
self._test__update_dnat_entry_if_needed(up=False, dvr=False)
@mock.patch('neutron.objects.router.Router.get_object')
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient._get_router_ports')
def _test_update_network_fragmentation(self, new_mtu, expected_opts, grps):
def _test_update_network_fragmentation(self, new_mtu, expected_opts, grps,
gr):
gr.return_value = {'flavor_id': ''}
network_attrs = {external_net.EXTERNAL: True}
network = self._make_network(
self.fmt, 'net1', True, as_admin=True,

View File

@ -1344,15 +1344,19 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
with self.port(device_owner="fake:test"):
self.assertTrue(cp.called)
@mock.patch('neutron.objects.router.Router.get_object')
def _test_no_dhcp_provisioning_blocks_removed_empty_device_owner(
self, device_owner):
self, device_owner, gr):
gr.return_value = {'flavor_id': ''}
with mock.patch.object(provisioning_blocks,
'remove_provisioning_component') as cp:
with self.port(device_owner=device_owner):
self.assertFalse(cp.called)
@mock.patch('neutron.objects.router.Router.get_object')
def _test_no_dhcp_provisioning_blocks_added_empty_device_owner(
self, device_owner):
self, device_owner, gr):
gr.return_value = {'flavor_id': ''}
with mock.patch.object(provisioning_blocks,
'add_provisioning_component') as cp:
with self.port(device_owner=device_owner):

View File

@ -0,0 +1,124 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from unittest import mock
from neutron_lib.callbacks import events
from neutron.db.models import l3
from neutron.services.ovn_l3.service_providers import user_defined
from neutron.tests.unit import testlib_api
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
class TestUserDefined(testlib_api.SqlTestCase):
def setUp(self):
super(TestUserDefined, self).setUp()
self.setup_coreplugin(DB_PLUGIN_KLASS)
self.fake_l3 = mock.MagicMock()
self.fake_l3._make_router_interface_info = mock.MagicMock(
return_value='router_interface_info')
self.provider = user_defined.UserDefined(self.fake_l3)
self.context = 'fake-context'
self.router = l3.Router(id='fake-uuid',
flavor_id='fake-uuid')
self.fake_l3.get_router = mock.MagicMock(return_value=self.router)
self.fip = {'router_id': 'fake-uuid'}
mock_flavor_plugin = mock.MagicMock()
mock_flavor_plugin.get_flavor = mock.MagicMock(
return_value={'id': 'fake-uuid'})
mock_flavor_plugin.get_flavor_next_provider = mock.MagicMock(
return_value=[{'driver': self.provider._user_defined_provider}])
self.provider._flavor_plugin_ref = mock_flavor_plugin
def test__is_user_defined_provider(self):
# test the positive case
self.assertTrue(self.provider._is_user_defined_provider(
self.context, self.router))
# test the negative case
self.provider._flavor_plugin_ref.get_flavor_next_provider = (
mock.MagicMock(return_value=[{'driver': None}]))
self.assertFalse(self.provider._is_user_defined_provider(
self.context, self.router))
def test_router_processing(self):
with mock.patch.object(user_defined.LOG, 'debug') as log:
payload = events.DBEventPayload(
self.context,
states=(self.router, self.router),
resource_id=self.router['id'],
metadata={'subnet_ids': ['subnet-id']})
fl_plg = self.provider._flavor_plugin_ref
methods = [self.provider._process_router_add_association,
self.provider._process_router_create,
self.provider._process_router_update,
self.provider._process_router_delete,
self.provider._process_remove_router_interface]
for method in methods:
method('resource', 'event', self, payload)
fl_plg.get_flavor.assert_called_once()
fl_plg.get_flavor_next_provider.assert_called_once()
log.assert_called_once()
fl_plg.get_flavor.reset_mock()
fl_plg.get_flavor_next_provider.reset_mock()
log.reset_mock()
def test_add_router_interface(self):
with mock.patch.object(user_defined.LOG, 'debug') as log:
payload = events.DBEventPayload(
self.context,
states=(self.router, self.router),
resource_id=self.router['id'],
metadata={'subnet_ids': ['subnet-id'],
'port': {'tenant_id': 'tenant-id',
'id': 'id',
'network_id': 'network-id'},
'subnets': [{'id': 'id'}]})
fl_plg = self.provider._flavor_plugin_ref
l3_plg = self.fake_l3
self.provider._process_add_router_interface('resource',
'event',
self,
payload)
l3_plg._make_router_interface_info.assert_called_once()
fl_plg.get_flavor.assert_called_once()
fl_plg.get_flavor_next_provider.assert_called_once()
log.assert_called_once()
def test_floatingip_processing(self):
# Test all the methods related to FIP processing
with mock.patch.object(user_defined.LOG, 'debug') as log:
payload = events.DBEventPayload(
self.context,
states=(self.fip, self.fip))
fl_plg = self.provider._flavor_plugin_ref
l3_plg = self.fake_l3
methods = [self.provider._process_floatingip_create,
self.provider._process_floatingip_update,
self.provider._process_floatingip_delete,
self.provider._process_floatingip_status_update]
for method in methods:
method('resource', 'event', self, payload)
l3_plg.get_router.assert_called_once()
fl_plg.get_flavor.assert_called_once()
fl_plg.get_flavor_next_provider.assert_called_once()
log.assert_called_once()
l3_plg.get_router.reset_mock()
fl_plg.get_flavor.reset_mock()
fl_plg.get_flavor_next_provider.reset_mock()
log.reset_mock()

View File

@ -37,6 +37,8 @@ from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers import driver_type as driver_type_conf
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config
from neutron.db import extraroute_db
from neutron.db.models import l3 as l3_models
from neutron.db.models import ovn as ovn_models
from neutron import manager as neutron_manager
from neutron.plugins.ml2 import managers
from neutron.services.ovn_l3 import exceptions as ovn_l3_exc
@ -78,6 +80,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
attrs=network_attrs).info()
self.fake_router_port = {'device_id': '',
'network_id': self.fake_network['id'],
'tenant_id': 'tenant-id',
'device_owner': 'network:router_interface',
'mac_address': 'aa:aa:aa:aa:aa:aa',
'status': constants.PORT_STATUS_ACTIVE,
@ -103,6 +106,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.fake_router = {'id': 'router-id',
'name': 'router',
'admin_state_up': False,
'flavor_id': None,
'routes': [{'destination': '1.1.1.0/24',
'nexthop': '10.0.0.2'}]}
self.fake_router_interface_info = {
@ -122,6 +126,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'id': 'router-id',
'name': 'router',
'admin_state_up': True,
'flavor_id': None,
'external_gateway_info': self.fake_external_fixed_ips,
'gw_port_id': 'gw-port-id'
}
@ -179,6 +184,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.fake_floating_ip['router_id'])},
}))
self.l3_inst = directory.get_plugin(plugin_constants.L3)
ovn_provider = self.l3_inst.l3_driver_controller.default_provider
self.ovn_drv = self.l3_inst.l3_driver_controller.drivers[ovn_provider]
self.lb_id = uuidutils.generate_uuid()
self.member_subnet = {'id': 'subnet-id',
'ip_version': 4,
@ -292,6 +299,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.get_rev_p = self._start_mock(
'neutron.common.ovn.utils.get_revision_number',
return_value=1)
self.get_rev_row_p = self._start_mock(
'neutron.db.ovn_revision_numbers_db.get_revision_row',
return_value=ovn_models.OVNRevisionNumbers(revision_number=1))
self.admin_context = mock.Mock()
self._start_mock(
'neutron_lib.context.get_admin_context',
@ -326,13 +336,26 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
driver, None)
self.assertEqual(fake_mech_driver.obj, result)
def _create_payload_for_router_interface(self, router_id,
pass_subnet=True):
router_obj = l3_models.Router(id=router_id, flavor_id=None)
metadata = {'port': self.fake_router_port}
if pass_subnet:
metadata['subnets'] = [self.fake_subnet]
else:
metadata['subnet_ids'] = [self.fake_subnet['id']]
return events.DBEventPayload(self.context, metadata=metadata,
states=(router_obj,),
resource_id=router_id)
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface(self, func):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
func.return_value = self.fake_router_interface_info
self.l3_inst.add_router_interface(self.context, router_id,
interface_info)
payload = self._create_payload_for_router_interface(router_id)
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self, payload)
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
**self.fake_router_port_assert)
self.l3_inst._nb_ovn.set_lrouter_port_in_lswitch_port.\
@ -346,7 +369,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface_update_lrouter_port(self, func):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
func.return_value = {'id': router_id,
'port_id': 'router-port-id',
'subnet_id': 'subnet-id1',
@ -366,8 +388,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'mac_address': 'aa:aa:aa:aa:aa:aa',
'network_id': 'network-id1'}
fake_rtr_intf_networks = ['2001:db8::1/24', '2001:dba::1/24']
self.l3_inst.add_router_interface(self.context, router_id,
interface_info)
payload = self._create_payload_for_router_interface(router_id)
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self, payload)
called_args_dict = (
self.l3_inst._nb_ovn.update_lrouter_port.call_args_list[0][1])
@ -382,12 +406,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
def test_remove_router_interface(self):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
self.get_port.side_effect = n_exc.PortNotFound(
port_id='router-port-id')
self.l3_inst.remove_router_interface(
self.context, router_id, interface_info)
payload = self._create_payload_for_router_interface(router_id,
pass_subnet=False)
self.ovn_drv._process_remove_router_interface(
resources.ROUTER_INTERFACE, events.AFTER_DELETE, self, payload)
self.l3_inst._nb_ovn.lrp_del.assert_called_once_with(
'lrp-router-port-id', 'neutron-router-id', if_exists=True)
@ -396,9 +421,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
def test_remove_router_interface_update_lrouter_port(self):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
self.l3_inst.remove_router_interface(
self.context, router_id, interface_info)
payload = self._create_payload_for_router_interface(router_id,
pass_subnet=False)
self.ovn_drv._process_remove_router_interface(
resources.ROUTER_INTERFACE, events.AFTER_DELETE, self, payload)
self.l3_inst._nb_ovn.update_lrouter_port.assert_called_once_with(
if_exists=False, name='lrp-router-port-id',
@ -413,14 +439,15 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
def test_remove_router_interface_router_not_found(self):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
self.get_port.side_effect = n_exc.PortNotFound(
port_id='router-port-id')
self.get_router.side_effect = l3_exc.RouterNotFound(
router_id='router-id')
self.l3_inst.remove_router_interface(
self.context, router_id, interface_info)
payload = self._create_payload_for_router_interface(router_id,
pass_subnet=False)
self.ovn_drv._process_remove_router_interface(
resources.ROUTER_INTERFACE, events.AFTER_DELETE, self, payload)
self.get_router.assert_called_once_with(self.context, 'router-id')
self.l3_inst._nb_ovn.lrp_del.assert_called_once_with(
@ -433,31 +460,40 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb'
'.ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_update_router_admin_state_change(self, get_rps, func):
router_id = 'router-id'
new_router = self.fake_router.copy()
updated_data = {'admin_state_up': True}
new_router.update(updated_data)
func.return_value = new_router
self.l3_inst.update_router(self.context, router_id,
{'router': updated_data})
payload = self._create_payload_for_router_update(self.fake_router,
new_router)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with(
'neutron-router-id', enabled=True, external_ids={
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: ''})
def _create_payload_for_router_update(self, original, updated):
return events.DBEventPayload(self.context,
states=(original, updated),
resource_id=original['id'])
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_router')
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_update_router_name_change(self, get_rps, func):
router_id = 'router-id'
new_router = self.fake_router.copy()
updated_data = {'name': 'test'}
new_router.update(updated_data)
func.return_value = new_router
self.l3_inst.update_router(self.context, router_id,
{'router': updated_data})
payload = self._create_payload_for_router_update(self.fake_router,
new_router)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with(
'neutron-router-id', enabled=False,
external_ids={ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'test',
@ -492,7 +528,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_update_router_static_route_change(self, get_rps, func,
mock_routes):
router_id = 'router-id'
get_rps.return_value = [{'device_id': '',
'device_owner': 'network:router_interface',
'mac_address': 'aa:aa:aa:aa:aa:aa',
@ -506,8 +541,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'nexthop': '10.0.0.3'}]}
new_router.update(updated_data)
func.return_value = new_router
self.l3_inst.update_router(self.context, router_id,
{'router': updated_data})
payload = self._create_payload_for_router_update(self.fake_router,
new_router)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
'neutron-router-id',
ip_prefix='2.2.2.0/24', nexthop='10.0.0.3')
@ -522,7 +560,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_update_router_static_route_clear(self, get_rps, func,
mock_routes):
router_id = 'router-id'
get_rps.return_value = [{'device_id': '',
'device_owner': 'network:router_interface',
'mac_address': 'aa:aa:aa:aa:aa:aa',
@ -535,8 +572,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
updated_data = {'routes': []}
new_router.update(updated_data)
func.return_value = new_router
self.l3_inst.update_router(self.context, router_id,
{'router': updated_data})
payload = self._create_payload_for_router_update(self.fake_router,
new_router)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
self.l3_inst._nb_ovn.add_static_route.assert_not_called()
self.l3_inst._nb_ovn.delete_static_route.assert_called_once_with(
'neutron-router-id',
@ -546,12 +586,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_create_router_with_ext_gw(self, get_rps):
self.l3_inst._nb_ovn.is_col_present.return_value = True
router = {'router': {'name': 'router'}}
self.get_subnet.return_value = self.fake_ext_subnet
self.get_port.return_value = self.fake_ext_gw_port
get_rps.return_value = self.fake_ext_subnet['cidr']
self.l3_inst.create_router(self.context, router)
payload = events.DBEventPayload(self.context,
states=(self.fake_router_with_ext_gw,))
self.ovn_drv._process_router_create(resources.ROUTER,
events.AFTER_CREATE, self, payload)
external_ids = {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router',
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
@ -596,7 +638,12 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
fake_resources.FakeOVNRouter.from_neutron_router(
self.fake_router_with_ext_gw))
self.l3_inst.delete_router(self.context, 'router-id')
payload = events.DBEventPayload(
self.context, states=(self.fake_router_with_ext_gw,),
resource_id=self.fake_router_with_ext_gw['id'])
self.ovn_drv._process_router_delete(resources.ROUTER,
events.AFTER_DELETE,
self, payload)
self.l3_inst._nb_ovn.delete_lrouter.assert_called_once_with(
'neutron-router-id')
@ -606,12 +653,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface_with_gateway_set(self, ari, grps):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
ari.return_value = self.fake_router_interface_info
self.get_router.return_value = self.fake_router_with_ext_gw
self.l3_inst.add_router_interface(self.context, router_id,
interface_info)
payload = self._create_payload_for_router_interface(router_id)
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self, payload)
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
**self.fake_router_port_assert)
@ -632,14 +680,15 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
def test_add_router_interface_with_gateway_set_and_snat_disabled(
self, ari, grps):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
ari.return_value = self.fake_router_interface_info
get_router = self.fake_router_with_ext_gw
get_router['external_gateway_info']['enable_snat'] = False
self.get_router.return_value = get_router
self.l3_inst.add_router_interface(self.context, router_id,
interface_info)
payload = self._create_payload_for_router_interface(router_id)
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self, payload)
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
**self.fake_router_port_assert)
@ -655,7 +704,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface_vlan_network(self, ari, grps, gn):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
ari.return_value = self.fake_router_interface_info
self.get_router.return_value = self.fake_router_with_ext_gw
@ -664,8 +712,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
fake_network_vlan[pnet.NETWORK_TYPE] = constants.TYPE_VLAN
gn.return_value = fake_network_vlan
self.l3_inst.add_router_interface(self.context, router_id,
interface_info)
payload = self._create_payload_for_router_interface(router_id)
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self, payload)
# Make sure that the "reside-on-redirect-chassis" option was
# set to the new router port
@ -689,13 +739,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
def test_remove_router_interface_with_gateway_set(self):
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id',
'subnet_id': 'subnet-id'}
self.get_router.return_value = self.fake_router_with_ext_gw
self.get_port.side_effect = n_exc.PortNotFound(
port_id='router-port-id')
self.l3_inst.remove_router_interface(
self.context, router_id, interface_info)
payload = self._create_payload_for_router_interface(router_id,
pass_subnet=False)
self.ovn_drv._process_remove_router_interface(
resources.ROUTER_INTERFACE, events.AFTER_DELETE, self, payload)
nb_ovn = self.l3_inst._nb_ovn
nb_ovn.lrp_del.assert_called_once_with(
'lrp-router-port-id', 'neutron-router-id', if_exists=True)
@ -711,15 +761,17 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'update_router')
def test_update_router_with_ext_gw(self, ur, grps):
self.l3_inst._nb_ovn.is_col_present.return_value = True
router = {'router': {'name': 'router'}}
self.get_router.return_value = self.fake_router_without_ext_gw
ur.return_value = self.fake_router_with_ext_gw
self.get_subnet.side_effect = lambda ctx, sid: {
'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports
self.l3_inst.update_router(self.context, 'router-id', router)
payload = self._create_payload_for_router_update(
self.fake_router_without_ext_gw, self.fake_router_with_ext_gw)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
**self.fake_ext_gw_port_assert)
@ -748,7 +800,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
grps, mock_get_gw):
self.l3_inst._nb_ovn.is_col_present.return_value = True
mock_get_gw.return_value = [mock.sentinel.GwRoute]
router = {'router': {'name': 'router'}}
fake_old_ext_subnet = {'id': 'old-ext-subnet-id',
'ip_version': 4,
'cidr': '192.168.2.0/24',
@ -768,7 +819,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports
self.l3_inst.update_router(self.context, 'router-id', router)
payload = self._create_payload_for_router_update(
self.get_router.return_value, self.fake_router_with_ext_gw)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
# Check deleting old router gateway
self.l3_inst._nb_ovn.delete_lrouter_ext_gw.assert_called_once_with(
@ -805,7 +860,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
grps, mock_get_gw):
self.l3_inst._nb_ovn.is_col_present.return_value = True
mock_get_gw.return_value = [mock.sentinel.GwRoute]
router = {'router': {'name': 'router'}}
# Old gateway info with same subnet and different ip address
gr_value = copy.deepcopy(self.fake_router_with_ext_gw)
gr_value['external_gateway_info'][
@ -818,7 +872,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports
self.l3_inst.update_router(self.context, 'router-id', router)
payload = self._create_payload_for_router_update(
gr_value, self.fake_router_with_ext_gw)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
# Check deleting old router gateway
self.l3_inst._nb_ovn.delete_lrouter_ext_gw.assert_called_once_with(
@ -868,8 +926,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'update_router')
def test_update_router_with_ext_gw_and_disabled_snat(self, ur, grps):
self.l3_inst._nb_ovn.is_col_present.return_value = True
router = {'router': {'name': 'router'}}
self.get_router.return_value = self.fake_router_without_ext_gw
ur.return_value = self.fake_router_with_ext_gw
ur.return_value['external_gateway_info']['enable_snat'] = False
self.get_subnet.side_effect = lambda ctx, sid: {
@ -877,7 +933,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports
self.l3_inst.update_router(self.context, 'router-id', router)
payload = self._create_payload_for_router_update(
self.fake_router_without_ext_gw, self.fake_router_with_ext_gw)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
# Need not check lsp and lrp here, it has been tested in other cases
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
@ -892,7 +952,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_router')
def test_enable_snat(self, ur, grps):
router = {'router': {'name': 'router'}}
gr_value = copy.deepcopy(self.fake_router_with_ext_gw)
gr_value['external_gateway_info']['enable_snat'] = False
self.get_router.return_value = gr_value
@ -905,7 +964,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports
self.l3_inst.update_router(self.context, 'router-id', router)
payload = self._create_payload_for_router_update(
self.fake_router_with_ext_gw, ur.return_value)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
self.l3_inst._nb_ovn.delete_static_route.assert_not_called()
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
@ -927,7 +990,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
mock_get_gw.return_value = [mock.sentinel.GwRoute]
mock_snats.return_value = [mock.sentinel.NAT]
mock_ext_ips.return_value = False
router = {'router': {'name': 'router'}}
self.get_router.return_value = self.fake_router_with_ext_gw
ur.return_value = copy.deepcopy(self.fake_router_with_ext_gw)
ur.return_value['external_gateway_info']['enable_snat'] = False
@ -936,7 +998,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports
self.l3_inst.update_router(self.context, 'router-id', router)
payload = self._create_payload_for_router_update(
self.fake_router_with_ext_gw, ur.return_value)
self.ovn_drv._process_router_update(resources.ROUTER,
events.AFTER_UPDATE,
self, payload)
nb_ovn = self.l3_inst._nb_ovn
nb_ovn.delete_static_route.assert_not_called()
@ -949,7 +1015,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
def test_create_floatingip(self):
self.l3_inst._nb_ovn.is_col_present.return_value = True
self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
@ -975,7 +1047,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
config.cfg.CONF.set_override(
'enable_distributed_floating_ip', True, group='ovn')
self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
@ -1003,7 +1081,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
config.cfg.CONF.set_override(
'enable_distributed_floating_ip', True, group='ovn')
self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
@ -1026,7 +1110,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.l3_inst._nb_ovn.get_lrouter_nat_rules.return_value = [
{'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.6',
'type': 'dnat_and_snat', 'uuid': 'uuid1'}]
self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
@ -1051,7 +1141,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.l3_inst._nb_ovn.get_lrouter_nat_rules.return_value = [
{'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.0/24',
'type': 'snat', 'uuid': 'uuid1'}]
self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
self.l3_inst._nb_ovn.set_nat_rule_in_lrouter.assert_not_called()
expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
@ -1075,7 +1171,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
foo_lport = fake_resources.FakeOvsdbRow.create_one_ovsdb_row()
foo_lport.uuid = 'foo-port'
self.l3_inst._nb_ovn.get_lswitch_port.return_value = foo_lport
self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
calls = [mock.call(
'Logical_Switch_Port',
'foo-port',
@ -1092,7 +1194,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.l3_inst._nb_ovn.is_col_present.return_value = True
self.l3_inst._nb_ovn.lookup.return_value = self.lb_network
self.l3_inst._nb_ovn.get_lswitch_port.return_value = self.member_lsp
fip = self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
fip = self.fake_floating_ip
# Validate that there is no external_mac and logical_port while
# setting the NAT entry.
expected_ext_ids = {
@ -1121,7 +1230,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
[self.fake_ovn_nat_rule],
]
self.l3_inst._nb_ovn.lookup.return_value = self.lb_network
fip = self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
fip = self.fake_floating_ip
expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: fip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: mock.ANY,
@ -1160,7 +1276,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
_nb_ovn.get_lrouter_port.return_value = lrp
self.l3_inst.get_router.return_value = self.fake_router_with_ext_gw
self.l3_inst.create_floatingip(self.context, 'floatingip')
payload = events.DBEventPayload(
self.context, states=(self.fake_floating_ip,),
resource_id=self.fake_floating_ip['id'],
request_body={'floatingip': self.fake_floating_ip})
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
events.AFTER_CREATE,
self, payload)
_nb_ovn.set_nat_rule_in_lrouter.assert_not_called()
expected_ext_ids = {
@ -1278,7 +1400,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
nb_ovn.get_floatingip.return_value = (
self.fake_ovn_nat_rule)
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
self.l3_inst.update_floatingip(self.context, 'id', fip)
payload = events.DBEventPayload(
self.context,
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
resource_id=self.fake_floating_ip_new['id'],
request_body=fip)
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
events.AFTER_UPDATE,
self, payload)
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
'neutron-router-id',
type='dnat_and_snat',
@ -1311,9 +1440,16 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
nb_ovn.get_floatingip.return_value = (
self.fake_ovn_nat_rule)
fip = {l3_def.FLOATINGIP: {qos_consts.QOS_POLICY_ID: 'qos_id_1'}}
payload = events.DBEventPayload(
self.context,
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
resource_id=self.fake_floating_ip_new['id'],
request_body=fip)
with mock.patch.object(self.l3_inst._ovn_client._qos_driver,
'update_floatingip') as ufip:
self.l3_inst.update_floatingip(self.context, 'id', fip)
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
events.AFTER_UPDATE,
self, payload)
nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
nb_ovn.add_nat_rule_in_lrouter.assert_not_called()
ufip.assert_called_once_with(mock.ANY, self.fake_floating_ip_new)
@ -1325,7 +1461,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.fake_floating_ip.update({'fixed_port_id': None})
uf.return_value = self.fake_floating_ip_new
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
self.l3_inst.update_floatingip(self.context, 'id', fip)
payload = events.DBEventPayload(
self.context,
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
resource_id=self.fake_floating_ip_new['id'],
request_body=fip)
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
events.AFTER_UPDATE,
self, payload)
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
@ -1362,7 +1505,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
config.cfg.CONF.set_override(
'enable_distributed_floating_ip', True, group='ovn')
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
self.l3_inst.update_floatingip(self.context, 'id', fip)
payload = events.DBEventPayload(
self.context,
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
resource_id=self.fake_floating_ip_new['id'],
request_body=fip)
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
events.AFTER_UPDATE,
self, payload)
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
@ -1391,7 +1541,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.fake_floating_ip_new.update({'port_id': 'foo'})
uf.return_value = self.fake_floating_ip_new
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
self.l3_inst.update_floatingip(self.context, 'id', fip)
payload = events.DBEventPayload(
self.context,
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
resource_id=self.fake_floating_ip_new['id'],
request_body=fip)
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
events.AFTER_UPDATE,
self, payload)
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
'neutron-router-id',
type='dnat_and_snat',
@ -1427,7 +1584,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'fixed_port_id': 'port_id'})
uf.return_value = self.fake_floating_ip_new
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
self.l3_inst.update_floatingip(self.context, 'id', fip)
payload = events.DBEventPayload(
self.context,
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
resource_id=self.fake_floating_ip_new['id'],
request_body=fip)
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
events.AFTER_UPDATE,
self, payload)
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
'neutron-router-id',
@ -1452,48 +1616,58 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_port='port_id',
external_ids=expected_ext_ids)
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_floatingips')
def test_disassociate_floatingips(self, gfs):
gfs.return_value = [{'id': 'fip-id1',
'floating_ip_address': '192.168.0.10',
'router_id': 'router-id',
'port_id': 'port_id',
'floating_port_id': 'fip-port-id1',
'fixed_ip_address': '10.0.0.10',
'floating_network_id': 'net1'},
{'id': 'fip-id2',
'floating_ip_address': '192.167.0.10',
'router_id': 'router-id',
'port_id': 'port_id',
'floating_port_id': 'fip-port-id2',
'fixed_ip_address': '10.0.0.11',
'floating_network_id': 'net2'}]
self.l3_inst.disassociate_floatingips(self.context, 'port_id',
do_notify=False)
@mock.patch('neutron.services.ovn_l3.plugin.OVNL3RouterPlugin.'
'update_floatingip_status')
def test_disassociate_floatingips(self, status_upd_mock):
old_fips = [{'id': 'fip-id1',
'floating_ip_address': '192.168.0.10',
'router_id': 'router-id',
'port_id': 'port_id',
'floating_port_id': 'fip-port-id1',
'fixed_ip_address': '10.0.0.10',
'floating_network_id': 'net1'},
{'id': 'fip-id2',
'floating_ip_address': '192.167.0.10',
'router_id': 'router-id',
'port_id': 'port_id',
'floating_port_id': 'fip-port-id2',
'fixed_ip_address': '10.0.0.11',
'floating_network_id': 'net2'}]
for fip in old_fips:
payload = events.DBEventPayload(
self.context, states=(fip, self.fake_floating_ip_new),
resource_id=self.fake_floating_ip_new['id'])
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
events.AFTER_UPDATE,
self, payload)
delete_nat_calls = [mock.call('neutron-router-id',
type='dnat_and_snat',
logical_ip=fip['fixed_ip_address'],
external_ip=fip['floating_ip_address'])
for fip in gfs.return_value]
for fip in old_fips]
self.assertEqual(
len(delete_nat_calls),
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.call_count)
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_has_calls(
delete_nat_calls, any_order=True)
self.assertEqual(len(delete_nat_calls), status_upd_mock.call_count)
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient.update_router_port')
def test_port_update_postcommit(self, update_rp_mock):
def test_port_update_postcommit(self, update_rp_mock, gr):
context = 'fake_context'
port = {'device_owner': 'foo'}
port = {'device_owner': 'foo', 'device_id': 'id'}
gr.return_value = {'flavor_id': None}
self.l3_inst._port_update(resources.PORT, events.AFTER_UPDATE, None,
payload=events.DBEventPayload(
context,
states=(port,)))
update_rp_mock.assert_not_called()
port = {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF}
port = {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF,
'device_id': 'router_id'}
self.l3_inst._port_update(resources.PORT, events.AFTER_UPDATE, None,
payload=events.DBEventPayload(
context,
@ -1503,11 +1677,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
port,
if_exists=True)
def test_port_update_before_update_router_port_without_ip(self):
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
def test_port_update_before_update_router_port_without_ip(self, gr):
context = 'fake_context'
port = {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF,
'fixed_ips': [],
'id': 'port-id'}
'id': 'port-id',
'device_id': 'router_id'}
gr.return_value = {'flavor_id': None}
self.assertRaises(
n_exc.ServicePortInUse,
self.l3_inst._port_update,
@ -1674,8 +1851,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
gn.return_value = prov_net
gns.return_value = [self.fake_network]
self.l3_inst.add_router_interface(self.context, router_id,
interface_info)
payload = self._create_payload_for_router_interface(router_id)
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self, payload)
# Make sure that the "gateway_mtu" option was set to the router port
fake_router_port_assert = self.fake_router_port_assert
@ -1708,8 +1887,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
config.cfg.CONF.set_override(
'ovn_emit_need_to_frag', True, group='ovn')
router_id = 'router-id'
interface_info = {'port_id': 'router-port-id',
'network_id': 'priv-net'}
ari.return_value = self.fake_router_interface_info
# If we remove the router halfway the return value of
# _get_routers_ports will be RouterNotFound
@ -1723,8 +1900,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
gn.return_value = prov_net
gns.return_value = [self.fake_network]
self.l3_inst.add_router_interface(self.context, router_id,
interface_info)
payload = self._create_payload_for_router_interface(router_id)
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self, payload)
# Make sure that the "gateway_mtu" option was set to the router port
fake_router_port_assert = self.fake_router_port_assert
@ -1882,7 +2061,9 @@ class OVNL3ExtrarouteTests(test_l3_gw.ExtGwModeIntTestCase,
test_floatingip_update_subnet_gateway_disabled(expected_status)
# Test function _subnet_update of L3 OVN plugin.
def test_update_subnet_gateway_for_external_net(self):
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
def test_update_subnet_gateway_for_external_net(self, gr):
gr.return_value = {'flavor_id': None}
super(OVNL3ExtrarouteTests, self). \
test_update_subnet_gateway_for_external_net()
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(

View File

@ -0,0 +1,8 @@
---
features:
- Neutron allows users to create routers with flavors with the L3 OVN
plugin. This new feature can be configured in the neutron.conf file.
Please see the "Creating a L3 OVN router with a user defined flavor"
section under Neutron configuration in the documentation for more
details. This document also describes the steps users have to take to
create a router with a flavor assigned to it.