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-qos-min-pps
config-rbac config-rbac
config-routed-networks config-routed-networks
config-router-flavor-ovn
config-sriov config-sriov
config-sfc config-sfc
config-service-subnets 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 fip_port_details
from neutron_lib.api.definitions import firewall_v2 from neutron_lib.api.definitions import firewall_v2
from neutron_lib.api.definitions import firewall_v2_stdattrs 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 floating_ip_port_forwarding
from neutron_lib.api.definitions import floatingip_pools from neutron_lib.api.definitions import floatingip_pools
from neutron_lib.api.definitions import l3 from neutron_lib.api.definitions import l3
from neutron_lib.api.definitions import l3_ext_gw_mode 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 logging
from neutron_lib.api.definitions import multiprovidernet from neutron_lib.api.definitions import multiprovidernet
from neutron_lib.api.definitions import network_availability_zone from neutron_lib.api.definitions import network_availability_zone
@ -114,6 +116,8 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
agent_def.ALIAS, agent_def.ALIAS,
az_def.ALIAS, az_def.ALIAS,
raz_def.ALIAS, raz_def.ALIAS,
flavors.ALIAS,
l3_flavors.ALIAS,
] ]
ML2_SUPPORTED_API_EXTENSIONS = [ ML2_SUPPORTED_API_EXTENSIONS = [
address_group.ALIAS, address_group.ALIAS,

View File

@ -1215,3 +1215,8 @@ def is_additional_chassis_supported(idl):
def is_nat_gateway_port_supported(idl): def is_nat_gateway_port_supported(idl):
return idl.is_col_present('NAT', 'gateway_port') 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] gw_ips = [x['ip_address'] for x in router.gw_port.fixed_ips]
cidrs = [x['cidr'] for x in subnets] cidrs = [x['cidr'] for x in subnets]
subnet_ids = [subnet['id'] for subnet in subnets]
metadata = {'interface_info': interface_info, metadata = {'interface_info': interface_info,
'port': port, 'gateway_ips': gw_ips, '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, registry.publish(resources.ROUTER_INTERFACE,
events.AFTER_DELETE, self, events.AFTER_DELETE, self,
payload=events.DBEventPayload( payload=events.DBEventPayload(
context, metadata=metadata, context, metadata=metadata,
resource_id=router_id)) resource_id=router_id,
states=(router,)))
return self._make_router_interface_info(router_id, port['tenant_id'], return self._make_router_interface_info(router_id, port['tenant_id'],
port['id'], port['network_id'], port['id'], port['network_id'],
subnets[0]['id'], subnets[0]['id'], subnet_ids)
[subnet['id'] for subnet in
subnets])
def _get_floatingip(self, context, id): def _get_floatingip(self, context, id):
floatingip = l3_obj.FloatingIP.get_object(context, id=id) floatingip = l3_obj.FloatingIP.get_object(context, id=id)
@ -1571,7 +1572,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
payload=events.DBEventPayload( payload=events.DBEventPayload(
context, states=(floatingip_dict,), context, states=(floatingip_dict,),
resource_id=floatingip_obj.id, resource_id=floatingip_obj.id,
metadata={'association_event': assoc_result})) metadata={'association_event': assoc_result},
request_body=floatingip))
if assoc_result: if assoc_result:
LOG.info(FIP_ASSOC_MSG, LOG.info(FIP_ASSOC_MSG,
{'fip_id': floatingip_obj.id, {'fip_id': floatingip_obj.id,
@ -1637,7 +1639,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
payload=events.DBEventPayload( payload=events.DBEventPayload(
context, states=(old_floatingip, floatingip_dict), context, states=(old_floatingip, floatingip_dict),
resource_id=floatingip_obj.id, resource_id=floatingip_obj.id,
metadata={'association_event': assoc_result})) metadata={'association_event': assoc_result},
request_body=floatingip))
if assoc_result is not None: if assoc_result is not None:
port_id = old_fixed_port_id or floatingip_obj.fixed_port_id port_id = old_fixed_port_id or floatingip_obj.fixed_port_id
assoc = 'associated' if assoc_result else 'disassociated' 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 ovn_revision_numbers_db
from neutron.db import provisioning_blocks from neutron.db import provisioning_blocks
from neutron.extensions import securitygroup as ext_sg 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 import db as ml2_db
from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent as n_agent from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent as n_agent
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \ 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 # in the case of router ports we also need to
# track the creation and update of the LRP OVN objects # 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( ovn_revision_numbers_db.create_initial_revision(
context.plugin_context, port['id'], context.plugin_context, port['id'],
ovn_const.TYPE_ROUTER_PORTS, ovn_const.TYPE_ROUTER_PORTS,
std_attr_id=context.current['standard_attr_id']) 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): def _is_port_provisioning_required(self, port, host, original_host=None):
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL) vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
if vnic_type not in self.supported_vnic_types: 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, self._insert_port_provisioning_block(context.plugin_context,
port['id']) 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 # handle the case when an existing port is added to a
# logical router so we need to track the creation of the lrp # logical router so we need to track the creation of the lrp
if not ovn_utils.is_lsp_router_port(original_port): 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): def _create_lrouter_port(self, context, port):
router_id = port['device_id'] router_id = port['device_id']
iface_info = self._ovn_client._l3_plugin._add_neutron_router_interface( 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) self._ovn_client.create_router_port(context, router_id, iface_info)
def _check_subnet_global_dhcp_opts(self): 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.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.db import ovn_revision_numbers_db as db_rev from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.db import segments_db from neutron.db import segments_db
from neutron.objects import router
from neutron.plugins.ml2 import db as ml2_db from neutron.plugins.ml2 import db as ml2_db
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \ from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
import placement as placement_extension import placement as placement_extension
@ -646,7 +647,10 @@ class OVNClient(object):
# LogicalSwitchPortUpdateDownEvent, that will most likely # LogicalSwitchPortUpdateDownEvent, that will most likely
# cause a revision conflict. # cause a revision conflict.
# https://bugs.launchpad.net/neutron/+bug/1955578 # 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( port_info.options.update(
self._nb_idl.get_router_port_options(port['id'])) self._nb_idl.get_router_port_options(port['id']))
else: else:

View File

@ -87,7 +87,7 @@ class DriverController(object):
router = payload.latest_state router = payload.latest_state
router_db = payload.metadata['router_db'] router_db = payload.metadata['router_db']
router_id = payload.resource_id router_id = payload.resource_id
if _flavor_specified(router): if flavor_specified(router):
router_db.flavor_id = router['flavor_id'] router_db.flavor_id = router['flavor_id']
drv = self._get_provider_for_create(context, router) drv = self._get_provider_for_create(context, router)
self._stm.add_resource_association(context, plugin_constants.L3, self._stm.add_resource_association(context, plugin_constants.L3,
@ -127,7 +127,7 @@ class DriverController(object):
drv = self.get_provider_for_router(payload.context, drv = self.get_provider_for_router(payload.context,
payload.resource_id) payload.resource_id)
new_drv = None new_drv = None
if _flavor_specified(payload.request_body): if flavor_specified(payload.request_body):
if (payload.request_body['flavor_id'] != if (payload.request_body['flavor_id'] !=
payload.states[0]['flavor_id']): payload.states[0]['flavor_id']):
# TODO(kevinbenton): this is currently disallowed by the API # TODO(kevinbenton): this is currently disallowed by the API
@ -210,7 +210,7 @@ class DriverController(object):
def _get_provider_for_create(self, context, router): def _get_provider_for_create(self, context, router):
"""Get provider based on flavor or ha/distributed flags.""" """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._attrs_to_driver(router)
return self._get_l3_driver_by_flavor(context, router['flavor_id']) return self._get_l3_driver_by_flavor(context, router['flavor_id'])
@ -293,7 +293,7 @@ def _is_ha(ha_attr):
return True return True
def _flavor_specified(router): def flavor_specified(router):
return ('flavor_id' in router and return ('flavor_id' in router and
router['flavor_id'] != lib_const.ATTR_NOT_SPECIFIED) 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 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 portbindings
from neutron_lib.api.definitions import provider_net as pnet from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib.api.definitions import qos_fip as qos_fip_apidef from neutron_lib.api.definitions import qos_fip as qos_fip_apidef
@ -23,19 +24,18 @@ from neutron_lib.callbacks import resources
from neutron_lib import constants as n_const from neutron_lib import constants as n_const
from neutron_lib import context as n_context from neutron_lib import context as n_context
from neutron_lib.db import api as db_api 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 import exceptions as n_exc
from neutron_lib.exceptions import availability_zone as az_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 constants as plugin_constants
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
from neutron_lib.services import base as service_base from neutron_lib.services import base as service_base
from oslo_log import log from oslo_log import log
from oslo_utils import excutils
from neutron._i18n import _ from neutron._i18n import _
from neutron.common.ovn import constants as ovn_const from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import extensions from neutron.common.ovn import extensions
from neutron.common.ovn import utils 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.availability_zone import router as router_az_db
from neutron.db import dns_db from neutron.db import dns_db
from neutron.db import extraroute_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_gateway_ip_qos
from neutron.db import l3_gwmode_db from neutron.db import l3_gwmode_db
from neutron.db.models import l3 as l3_models from neutron.db.models import l3 as l3_models
from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
from neutron.quota import resource_registry from neutron.quota import resource_registry
from neutron.scheduler import l3_ovn_scheduler from neutron.scheduler import l3_ovn_scheduler
from neutron.services.ovn_l3 import exceptions as ovn_l3_exc 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 \ from neutron.services.portforwarding.drivers.ovn import driver \
as port_forwarding as port_forwarding
@ -94,15 +94,7 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
self._ovn_client_inst = None self._ovn_client_inst = None
self.scheduler = l3_ovn_scheduler.get_scheduler() self.scheduler = l3_ovn_scheduler.get_scheduler()
self.port_forwarding = port_forwarding.OVNPortForwarding(self) self.port_forwarding = port_forwarding.OVNPortForwarding(self)
self._register_precommit_callbacks() self.l3_driver_controller = driver_controller.DriverController(self)
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)
@staticmethod @staticmethod
def _disable_qos_extensions_by_extension_drivers(aliases): 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" return ("L3 Router Service Plugin for basic L3 forwarding"
" using OVN") " 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, def _add_neutron_router_interface(self, context, router_id,
interface_info, may_exist=False): interface_info):
try: try:
router_interface_info = ( router_interface_info = (
super(OVNL3RouterPlugin, self).add_router_interface( super(OVNL3RouterPlugin, self).add_router_interface(
context, router_id, interface_info)) context, router_id, interface_info))
except n_exc.PortInUse: except n_exc.PortInUse:
if not may_exist:
raise
# NOTE(lucasagomes): If the port is already being used it means # NOTE(lucasagomes): If the port is already being used it means
# the interface has been created already, let's just fetch it from # the interface has been created already, let's just fetch it from
# the database. Perhaps the code below should live in Neutron # the database. Perhaps the code below should live in Neutron
@ -247,70 +185,24 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
return router_interface_info 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, def create_floatingip(self, context, floatingip,
initial_status=n_const.FLOATINGIP_STATUS_DOWN): 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) 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): def update_floatingip_status(self, context, floatingip_id, status):
fip = self.update_floatingip_status_retry( fip = self.update_floatingip_status_retry(
context, floatingip_id, status) 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 return fip
@db_api.retry_if_session_inactive() @db_api.retry_if_session_inactive()
@ -319,30 +211,6 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
return super(OVNL3RouterPlugin, self).update_floatingip_status( return super(OVNL3RouterPlugin, self).update_floatingip_status(
context, floatingip_id, 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): def _get_gateway_port_physnet_mapping(self):
# This function returns all gateway ports with corresponding # This function returns all gateway ports with corresponding
# external network's physnet # external network's physnet
@ -359,8 +227,10 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
net_physnet_dict[net['id']] = net.get(pnet.PHYSICAL_NETWORK) net_physnet_dict[net['id']] = net.get(pnet.PHYSICAL_NETWORK)
for port in l3plugin._plugin.get_ports(context, filters={ for port in l3plugin._plugin.get_ports(context, filters={
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW]}): 'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW]}):
port_physnet_dict[port['id']] = net_physnet_dict.get( if utils.is_ovn_provider_router(
port['network_id']) l3plugin.get_router(context, port['device_id'])):
port_physnet_dict[port['id']] = net_physnet_dict.get(
port['network_id'])
return port_physnet_dict return port_physnet_dict
def update_router_gateway_port_bindings(self, router, host): 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], 'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW],
'fixed_ips': {'subnet_id': [orig['id']]}, '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} remove = [{'destination': '0.0.0.0/0', 'nexthop': orig_gw_ip}
] if orig_gw_ip else [] ] if orig_gw_ip else []
add = [{'destination': '0.0.0.0/0', 'nexthop': current_gw_ip} 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 # https://bugs.launchpad.net/neutron/+bug/1948457
if (event == events.BEFORE_UPDATE and if (event == events.BEFORE_UPDATE and
'fixed_ips' in current and not current['fixed_ips'] 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.") reason = _("Router port must have at least one IP.")
raise n_exc.ServicePortInUse(port_id=original['id'], reason=reason) 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, # We call the update_router port with if_exists, because neutron,
# internally creates the port, and then calls update, which will # internally creates the port, and then calls update, which will
# trigger this callback even before we had the chance to create # trigger this callback even before we had the chance to create
@ -541,3 +418,8 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
if diff: if diff:
raise az_exc.AvailabilityZoneNotFound( raise az_exc.AvailabilityZoneNotFound(
availability_zone=', '.join(diff)) 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 external_net as extnet_apidef
from neutron_lib.api.definitions import floating_ip_port_forwarding as pf_def 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.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 constants as n_const
from neutron_lib import context as n_context from neutron_lib import context as n_context
from neutron_lib.exceptions import l3 as lib_l3_exc 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.conf.plugins.ml2.drivers.ovn import ovn_conf as ovn_config
from neutron.db import ovn_revision_numbers_db as db_rev from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance 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 import base
from neutron.tests.functional.services.logapi.drivers.ovn \ from neutron.tests.functional.services.logapi.drivers.ovn \
import test_driver as test_log_driver import test_driver as test_log_driver
@ -925,65 +928,69 @@ class TestMaintenance(_TestMaintenanceHelper):
p1 = self._create_port('testp1', net1['id']) p1 = self._create_port('testp1', net1['id'])
p1_ip = p1['fixed_ips'][0]['ip_address'] p1_ip = p1['fixed_ips'][0]['ip_address']
with mock.patch('neutron_lib.callbacks.registry.publish') as m_publish: callbacks = registry._get_callback_manager()._callbacks
# > Create pf_cb = callbacks[pf_consts.PORT_FORWARDING]
fip_pf_args = { key = list(pf_cb[events.AFTER_UPDATE][0][1].keys())[0]
pf_def.EXTERNAL_PORT: 2222, pf_cb[events.AFTER_CREATE][0][1][key] = mock.MagicMock()
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])
# Assert load balancer for port forwarding was not created # > Create
self.assertFalse(self._find_pf_lb(router_id, fip_id)) 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 # Assert load balancer for port forwarding was not created
self.maint.check_for_inconsistencies() self.assertFalse(self._find_pf_lb(router_id, fip_id))
# Assert load balancer for port forwarding was created # Call the maintenance thread to fix the problem
_verify_lb(self, 'tcp', 2222, 22) self.maint.check_for_inconsistencies()
# > Update # Assert load balancer for port forwarding was created
fip_pf_args = {pf_def.EXTERNAL_PORT: 5353, _verify_lb(self, 'tcp', 2222, 22)
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 is stale # > Update
_verify_lb(self, 'tcp', 2222, 22) 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 # Assert load balancer for port forwarding is stale
self.maint.check_for_inconsistencies() _verify_lb(self, 'tcp', 2222, 22)
# Assert load balancer for port forwarding was updated # Call the maintenance thread to fix the problem
_verify_lb(self, 'udp', 5353, 53) self.maint.check_for_inconsistencies()
# > Delete # Assert load balancer for port forwarding was updated
m_publish.reset_mock() _verify_lb(self, 'udp', 5353, 53)
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 is stale # > Delete
_verify_lb(self, 'udp', 5353, 53) 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 # Assert load balancer for port forwarding is stale
self.maint.check_for_inconsistencies() _verify_lb(self, 'udp', 5353, 53)
# Assert load balancer for port forwarding is gone # Call the maintenance thread to fix the problem
self.assertFalse(self._find_pf_lb(router_id, fip_id)) 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): def test_check_for_ha_chassis_group(self):
net1 = self._create_network('network1test', external=False) 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) 'ports', port_upt_data, port['id'], self.fmt)
port_res = port_req.get_response(self.api) 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): class LogicalSwitchPortUpdateUpEventTest(event.RowEvent):
def __init__(self): def __init__(self):
self.count = 0 self.count = 0
@ -651,6 +652,7 @@ class TestExternalPorts(base.TestOVNFunctionalBase):
def get_count(self): def get_count(self):
return self.count return self.count
gr.return_value = {'flavor_id': None}
og_up_event = ovsdb_monitor.LogicalSwitchPortUpdateUpEvent(None) og_up_event = ovsdb_monitor.LogicalSwitchPortUpdateUpEvent(None)
og_down_event = ovsdb_monitor.LogicalSwitchPortUpdateDownEvent(None) og_down_event = ovsdb_monitor.LogicalSwitchPortUpdateDownEvent(None)
test_down_event = LogicalSwitchPortUpdateDownEventTest() test_down_event = LogicalSwitchPortUpdateDownEventTest()
@ -666,7 +668,8 @@ class TestExternalPorts(base.TestOVNFunctionalBase):
# status as up, triggering only a LogicalSwitchPortUpdateUpEvent. # status as up, triggering only a LogicalSwitchPortUpdateUpEvent.
self._create_router_port(portbindings.VNIC_DIRECT) self._create_router_port(portbindings.VNIC_DIRECT)
self.assertEqual(test_down_event.get_count(), 0) 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): def test_external_port_create_vnic_direct(self):
self._test_external_port_create(portbindings.VNIC_DIRECT) 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, self.assertEqual(webob.exc.HTTPClientError.code,
res.status_int) 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', with self.subnet(gateway_ip='fe80::1',
cidr='fe80::/64', cidr='fe80::/64',
ip_version=constants.IP_VERSION_6, 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, self.assertIn({'ip_address': eui_addr,
'subnet_id': subnet2['subnet']['id']}, ips) '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: with self.network() as network:
# Create an IPv4 and an IPv6 SLAAC subnet on the network # Create an IPv4 and an IPv6 SLAAC subnet on the network
with self.subnet(network) as subnet_v4,\ with self.subnet(network) as subnet_v4,\
@ -3764,8 +3768,10 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
sport = self.deserialize(self.fmt, req.get_response(self.api)) sport = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(0, len(sport['port']['fixed_ips'])) 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""" """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( subnet, port = self._create_slaac_subnet_and_port(
constants.DEVICE_OWNER_ROUTER_INTF) constants.DEVICE_OWNER_ROUTER_INTF)
# Delete the subnet and assert that we get a HTTP 409 error # 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_ra_mode=constants.IPV6_SLAAC,
ipv6_address_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' cidr = '2001::/64'
with self.network() as network: with self.network() as network:
net_id = network['network']['id'] net_id = network['network']['id']
@ -4598,10 +4606,12 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
self.assertEqual(webob.exc.HTTPClientError.code, self.assertEqual(webob.exc.HTTPClientError.code,
ctx_manager.exception.code) ctx_manager.exception.code)
@mock.patch('neutron.objects.router.Router.get_object')
def _test_create_subnet_ipv6_auto_addr_with_port_on_network( 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_db_reference_error=False, insert_port_not_found=False,
insert_address_allocated=False): insert_address_allocated=False):
gr.return_value = {'flavor_id': ''}
# Create a network with one IPv4 subnet and one port # Create a network with one IPv4 subnet and one port
with self.network() as network,\ with self.network() as network,\
self.subnet(network=network) as v4_subnet,\ 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): def test__update_dnat_entry_if_needed_down_no_dvr(self):
self._test__update_dnat_entry_if_needed(up=False, dvr=False) 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.' @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient._get_router_ports') '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_attrs = {external_net.EXTERNAL: True}
network = self._make_network( network = self._make_network(
self.fmt, 'net1', True, as_admin=True, 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"): with self.port(device_owner="fake:test"):
self.assertTrue(cp.called) self.assertTrue(cp.called)
@mock.patch('neutron.objects.router.Router.get_object')
def _test_no_dhcp_provisioning_blocks_removed_empty_device_owner( 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, with mock.patch.object(provisioning_blocks,
'remove_provisioning_component') as cp: 'remove_provisioning_component') as cp:
with self.port(device_owner=device_owner): with self.port(device_owner=device_owner):
self.assertFalse(cp.called) self.assertFalse(cp.called)
@mock.patch('neutron.objects.router.Router.get_object')
def _test_no_dhcp_provisioning_blocks_added_empty_device_owner( 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, with mock.patch.object(provisioning_blocks,
'add_provisioning_component') as cp: 'add_provisioning_component') as cp:
with self.port(device_owner=device_owner): 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 import driver_type as driver_type_conf
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config
from neutron.db import extraroute_db 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 import manager as neutron_manager
from neutron.plugins.ml2 import managers from neutron.plugins.ml2 import managers
from neutron.services.ovn_l3 import exceptions as ovn_l3_exc 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() attrs=network_attrs).info()
self.fake_router_port = {'device_id': '', self.fake_router_port = {'device_id': '',
'network_id': self.fake_network['id'], 'network_id': self.fake_network['id'],
'tenant_id': 'tenant-id',
'device_owner': 'network:router_interface', 'device_owner': 'network:router_interface',
'mac_address': 'aa:aa:aa:aa:aa:aa', 'mac_address': 'aa:aa:aa:aa:aa:aa',
'status': constants.PORT_STATUS_ACTIVE, 'status': constants.PORT_STATUS_ACTIVE,
@ -103,6 +106,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.fake_router = {'id': 'router-id', self.fake_router = {'id': 'router-id',
'name': 'router', 'name': 'router',
'admin_state_up': False, 'admin_state_up': False,
'flavor_id': None,
'routes': [{'destination': '1.1.1.0/24', 'routes': [{'destination': '1.1.1.0/24',
'nexthop': '10.0.0.2'}]} 'nexthop': '10.0.0.2'}]}
self.fake_router_interface_info = { self.fake_router_interface_info = {
@ -122,6 +126,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'id': 'router-id', 'id': 'router-id',
'name': 'router', 'name': 'router',
'admin_state_up': True, 'admin_state_up': True,
'flavor_id': None,
'external_gateway_info': self.fake_external_fixed_ips, 'external_gateway_info': self.fake_external_fixed_ips,
'gw_port_id': 'gw-port-id' 'gw_port_id': 'gw-port-id'
} }
@ -179,6 +184,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.fake_floating_ip['router_id'])}, self.fake_floating_ip['router_id'])},
})) }))
self.l3_inst = directory.get_plugin(plugin_constants.L3) 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.lb_id = uuidutils.generate_uuid()
self.member_subnet = {'id': 'subnet-id', self.member_subnet = {'id': 'subnet-id',
'ip_version': 4, 'ip_version': 4,
@ -292,6 +299,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
self.get_rev_p = self._start_mock( self.get_rev_p = self._start_mock(
'neutron.common.ovn.utils.get_revision_number', 'neutron.common.ovn.utils.get_revision_number',
return_value=1) 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.admin_context = mock.Mock()
self._start_mock( self._start_mock(
'neutron_lib.context.get_admin_context', 'neutron_lib.context.get_admin_context',
@ -326,13 +336,26 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
driver, None) driver, None)
self.assertEqual(fake_mech_driver.obj, result) 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') @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface(self, func): def test_add_router_interface(self, func):
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
func.return_value = self.fake_router_interface_info func.return_value = self.fake_router_interface_info
self.l3_inst.add_router_interface(self.context, router_id, payload = self._create_payload_for_router_interface(router_id)
interface_info) 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.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
**self.fake_router_port_assert) **self.fake_router_port_assert)
self.l3_inst._nb_ovn.set_lrouter_port_in_lswitch_port.\ 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') @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface_update_lrouter_port(self, func): def test_add_router_interface_update_lrouter_port(self, func):
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
func.return_value = {'id': router_id, func.return_value = {'id': router_id,
'port_id': 'router-port-id', 'port_id': 'router-port-id',
'subnet_id': 'subnet-id1', 'subnet_id': 'subnet-id1',
@ -366,8 +388,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'mac_address': 'aa:aa:aa:aa:aa:aa', 'mac_address': 'aa:aa:aa:aa:aa:aa',
'network_id': 'network-id1'} 'network_id': 'network-id1'}
fake_rtr_intf_networks = ['2001:db8::1/24', '2001:dba::1/24'] fake_rtr_intf_networks = ['2001:db8::1/24', '2001:dba::1/24']
self.l3_inst.add_router_interface(self.context, router_id, payload = self._create_payload_for_router_interface(router_id)
interface_info) self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
events.AFTER_CREATE,
self, payload)
called_args_dict = ( called_args_dict = (
self.l3_inst._nb_ovn.update_lrouter_port.call_args_list[0][1]) 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): def test_remove_router_interface(self):
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
self.get_port.side_effect = n_exc.PortNotFound( self.get_port.side_effect = n_exc.PortNotFound(
port_id='router-port-id') port_id='router-port-id')
self.l3_inst.remove_router_interface( payload = self._create_payload_for_router_interface(router_id,
self.context, router_id, interface_info) 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( self.l3_inst._nb_ovn.lrp_del.assert_called_once_with(
'lrp-router-port-id', 'neutron-router-id', if_exists=True) '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): def test_remove_router_interface_update_lrouter_port(self):
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'} payload = self._create_payload_for_router_interface(router_id,
self.l3_inst.remove_router_interface( pass_subnet=False)
self.context, router_id, interface_info) 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( self.l3_inst._nb_ovn.update_lrouter_port.assert_called_once_with(
if_exists=False, name='lrp-router-port-id', 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): def test_remove_router_interface_router_not_found(self):
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
self.get_port.side_effect = n_exc.PortNotFound( self.get_port.side_effect = n_exc.PortNotFound(
port_id='router-port-id') port_id='router-port-id')
self.get_router.side_effect = l3_exc.RouterNotFound( self.get_router.side_effect = l3_exc.RouterNotFound(
router_id='router-id') router_id='router-id')
self.l3_inst.remove_router_interface( payload = self._create_payload_for_router_interface(router_id,
self.context, router_id, interface_info) 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.get_router.assert_called_once_with(self.context, 'router-id')
self.l3_inst._nb_ovn.lrp_del.assert_called_once_with( 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' @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb'
'.ovn_client.OVNClient._get_v4_network_of_all_router_ports') '.ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_update_router_admin_state_change(self, get_rps, func): def test_update_router_admin_state_change(self, get_rps, func):
router_id = 'router-id'
new_router = self.fake_router.copy() new_router = self.fake_router.copy()
updated_data = {'admin_state_up': True} updated_data = {'admin_state_up': True}
new_router.update(updated_data) new_router.update(updated_data)
func.return_value = new_router func.return_value = new_router
self.l3_inst.update_router(self.context, router_id, payload = self._create_payload_for_router_update(self.fake_router,
{'router': updated_data}) 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( self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with(
'neutron-router-id', enabled=True, external_ids={ 'neutron-router-id', enabled=True, external_ids={
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router', ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router',
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''}) ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''})
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.' @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_router') 'update_router')
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.' @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient._get_v4_network_of_all_router_ports') 'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_update_router_name_change(self, get_rps, func): def test_update_router_name_change(self, get_rps, func):
router_id = 'router-id'
new_router = self.fake_router.copy() new_router = self.fake_router.copy()
updated_data = {'name': 'test'} updated_data = {'name': 'test'}
new_router.update(updated_data) new_router.update(updated_data)
func.return_value = new_router func.return_value = new_router
self.l3_inst.update_router(self.context, router_id, payload = self._create_payload_for_router_update(self.fake_router,
{'router': updated_data}) 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( self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with(
'neutron-router-id', enabled=False, 'neutron-router-id', enabled=False,
external_ids={ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'test', 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') 'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_update_router_static_route_change(self, get_rps, func, def test_update_router_static_route_change(self, get_rps, func,
mock_routes): mock_routes):
router_id = 'router-id'
get_rps.return_value = [{'device_id': '', get_rps.return_value = [{'device_id': '',
'device_owner': 'network:router_interface', 'device_owner': 'network:router_interface',
'mac_address': 'aa:aa:aa:aa:aa:aa', 'mac_address': 'aa:aa:aa:aa:aa:aa',
@ -506,8 +541,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'nexthop': '10.0.0.3'}]} 'nexthop': '10.0.0.3'}]}
new_router.update(updated_data) new_router.update(updated_data)
func.return_value = new_router func.return_value = new_router
self.l3_inst.update_router(self.context, router_id, payload = self._create_payload_for_router_update(self.fake_router,
{'router': updated_data}) 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( self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
'neutron-router-id', 'neutron-router-id',
ip_prefix='2.2.2.0/24', nexthop='10.0.0.3') 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') 'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_update_router_static_route_clear(self, get_rps, func, def test_update_router_static_route_clear(self, get_rps, func,
mock_routes): mock_routes):
router_id = 'router-id'
get_rps.return_value = [{'device_id': '', get_rps.return_value = [{'device_id': '',
'device_owner': 'network:router_interface', 'device_owner': 'network:router_interface',
'mac_address': 'aa:aa:aa:aa:aa:aa', 'mac_address': 'aa:aa:aa:aa:aa:aa',
@ -535,8 +572,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
updated_data = {'routes': []} updated_data = {'routes': []}
new_router.update(updated_data) new_router.update(updated_data)
func.return_value = new_router func.return_value = new_router
self.l3_inst.update_router(self.context, router_id, payload = self._create_payload_for_router_update(self.fake_router,
{'router': updated_data}) 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.add_static_route.assert_not_called()
self.l3_inst._nb_ovn.delete_static_route.assert_called_once_with( self.l3_inst._nb_ovn.delete_static_route.assert_called_once_with(
'neutron-router-id', 'neutron-router-id',
@ -546,12 +586,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'ovn_client.OVNClient._get_v4_network_of_all_router_ports') 'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
def test_create_router_with_ext_gw(self, get_rps): def test_create_router_with_ext_gw(self, get_rps):
self.l3_inst._nb_ovn.is_col_present.return_value = True 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_subnet.return_value = self.fake_ext_subnet
self.get_port.return_value = self.fake_ext_gw_port self.get_port.return_value = self.fake_ext_gw_port
get_rps.return_value = self.fake_ext_subnet['cidr'] 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', external_ids = {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router',
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
@ -596,7 +638,12 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
fake_resources.FakeOVNRouter.from_neutron_router( fake_resources.FakeOVNRouter.from_neutron_router(
self.fake_router_with_ext_gw)) 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( self.l3_inst._nb_ovn.delete_lrouter.assert_called_once_with(
'neutron-router-id') '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') @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface_with_gateway_set(self, ari, grps): def test_add_router_interface_with_gateway_set(self, ari, grps):
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
ari.return_value = self.fake_router_interface_info ari.return_value = self.fake_router_interface_info
self.get_router.return_value = self.fake_router_with_ext_gw self.get_router.return_value = self.fake_router_with_ext_gw
self.l3_inst.add_router_interface(self.context, router_id, payload = self._create_payload_for_router_interface(router_id)
interface_info) 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.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
**self.fake_router_port_assert) **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( def test_add_router_interface_with_gateway_set_and_snat_disabled(
self, ari, grps): self, ari, grps):
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
ari.return_value = self.fake_router_interface_info ari.return_value = self.fake_router_interface_info
get_router = self.fake_router_with_ext_gw get_router = self.fake_router_with_ext_gw
get_router['external_gateway_info']['enable_snat'] = False get_router['external_gateway_info']['enable_snat'] = False
self.get_router.return_value = get_router self.get_router.return_value = get_router
self.l3_inst.add_router_interface(self.context, router_id, payload = self._create_payload_for_router_interface(router_id)
interface_info) 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.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
**self.fake_router_port_assert) **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') @mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
def test_add_router_interface_vlan_network(self, ari, grps, gn): def test_add_router_interface_vlan_network(self, ari, grps, gn):
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id'}
ari.return_value = self.fake_router_interface_info ari.return_value = self.fake_router_interface_info
self.get_router.return_value = self.fake_router_with_ext_gw 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 fake_network_vlan[pnet.NETWORK_TYPE] = constants.TYPE_VLAN
gn.return_value = fake_network_vlan gn.return_value = fake_network_vlan
self.l3_inst.add_router_interface(self.context, router_id, payload = self._create_payload_for_router_interface(router_id)
interface_info) 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 # Make sure that the "reside-on-redirect-chassis" option was
# set to the new router port # 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): def test_remove_router_interface_with_gateway_set(self):
router_id = 'router-id' 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_router.return_value = self.fake_router_with_ext_gw
self.get_port.side_effect = n_exc.PortNotFound( self.get_port.side_effect = n_exc.PortNotFound(
port_id='router-port-id') port_id='router-port-id')
self.l3_inst.remove_router_interface( payload = self._create_payload_for_router_interface(router_id,
self.context, router_id, interface_info) 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 = self.l3_inst._nb_ovn
nb_ovn.lrp_del.assert_called_once_with( nb_ovn.lrp_del.assert_called_once_with(
'lrp-router-port-id', 'neutron-router-id', if_exists=True) 'lrp-router-port-id', 'neutron-router-id', if_exists=True)
@ -711,15 +761,17 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'update_router') 'update_router')
def test_update_router_with_ext_gw(self, ur, grps): def test_update_router_with_ext_gw(self, ur, grps):
self.l3_inst._nb_ovn.is_col_present.return_value = True 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 = self.fake_router_with_ext_gw
self.get_subnet.side_effect = lambda ctx, sid: { self.get_subnet.side_effect = lambda ctx, sid: {
'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet) 'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
self.get_port.return_value = self.fake_ext_gw_port self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports 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.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
**self.fake_ext_gw_port_assert) **self.fake_ext_gw_port_assert)
@ -748,7 +800,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
grps, mock_get_gw): grps, mock_get_gw):
self.l3_inst._nb_ovn.is_col_present.return_value = True self.l3_inst._nb_ovn.is_col_present.return_value = True
mock_get_gw.return_value = [mock.sentinel.GwRoute] mock_get_gw.return_value = [mock.sentinel.GwRoute]
router = {'router': {'name': 'router'}}
fake_old_ext_subnet = {'id': 'old-ext-subnet-id', fake_old_ext_subnet = {'id': 'old-ext-subnet-id',
'ip_version': 4, 'ip_version': 4,
'cidr': '192.168.2.0/24', '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 self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports 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 # Check deleting old router gateway
self.l3_inst._nb_ovn.delete_lrouter_ext_gw.assert_called_once_with( 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): grps, mock_get_gw):
self.l3_inst._nb_ovn.is_col_present.return_value = True self.l3_inst._nb_ovn.is_col_present.return_value = True
mock_get_gw.return_value = [mock.sentinel.GwRoute] mock_get_gw.return_value = [mock.sentinel.GwRoute]
router = {'router': {'name': 'router'}}
# Old gateway info with same subnet and different ip address # Old gateway info with same subnet and different ip address
gr_value = copy.deepcopy(self.fake_router_with_ext_gw) gr_value = copy.deepcopy(self.fake_router_with_ext_gw)
gr_value['external_gateway_info'][ 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 self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports 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 # Check deleting old router gateway
self.l3_inst._nb_ovn.delete_lrouter_ext_gw.assert_called_once_with( 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') 'update_router')
def test_update_router_with_ext_gw_and_disabled_snat(self, ur, grps): def test_update_router_with_ext_gw_and_disabled_snat(self, ur, grps):
self.l3_inst._nb_ovn.is_col_present.return_value = True 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 = self.fake_router_with_ext_gw
ur.return_value['external_gateway_info']['enable_snat'] = False ur.return_value['external_gateway_info']['enable_snat'] = False
self.get_subnet.side_effect = lambda ctx, sid: { 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 self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports 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 # 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( 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.' @mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
'update_router') 'update_router')
def test_enable_snat(self, ur, grps): def test_enable_snat(self, ur, grps):
router = {'router': {'name': 'router'}}
gr_value = copy.deepcopy(self.fake_router_with_ext_gw) gr_value = copy.deepcopy(self.fake_router_with_ext_gw)
gr_value['external_gateway_info']['enable_snat'] = False gr_value['external_gateway_info']['enable_snat'] = False
self.get_router.return_value = gr_value 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 self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports 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_static_route.assert_not_called()
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.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_get_gw.return_value = [mock.sentinel.GwRoute]
mock_snats.return_value = [mock.sentinel.NAT] mock_snats.return_value = [mock.sentinel.NAT]
mock_ext_ips.return_value = False mock_ext_ips.return_value = False
router = {'router': {'name': 'router'}}
self.get_router.return_value = self.fake_router_with_ext_gw 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 = copy.deepcopy(self.fake_router_with_ext_gw)
ur.return_value['external_gateway_info']['enable_snat'] = False 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 self.get_port.return_value = self.fake_ext_gw_port
grps.return_value = self.fake_router_ports 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 = self.l3_inst._nb_ovn
nb_ovn.delete_static_route.assert_not_called() nb_ovn.delete_static_route.assert_not_called()
@ -949,7 +1015,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
def test_create_floatingip(self): def test_create_floatingip(self):
self.l3_inst._nb_ovn.is_col_present.return_value = True self.l3_inst._nb_ovn.is_col_present.return_value = True
self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'} 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 = { expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'], ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', 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'} self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
config.cfg.CONF.set_override( config.cfg.CONF.set_override(
'enable_distributed_floating_ip', True, group='ovn') '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 = { expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'], ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', 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'} self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
config.cfg.CONF.set_override( config.cfg.CONF.set_override(
'enable_distributed_floating_ip', True, group='ovn') '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 = { expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'], ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', 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 = [ self.l3_inst._nb_ovn.get_lrouter_nat_rules.return_value = [
{'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.6', {'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.6',
'type': 'dnat_and_snat', 'uuid': 'uuid1'}] '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 = { expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'], ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1', 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 = [ self.l3_inst._nb_ovn.get_lrouter_nat_rules.return_value = [
{'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.0/24', {'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.0/24',
'type': 'snat', 'uuid': 'uuid1'}] '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() self.l3_inst._nb_ovn.set_nat_rule_in_lrouter.assert_not_called()
expected_ext_ids = { expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'], 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 = fake_resources.FakeOvsdbRow.create_one_ovsdb_row()
foo_lport.uuid = 'foo-port' foo_lport.uuid = 'foo-port'
self.l3_inst._nb_ovn.get_lswitch_port.return_value = foo_lport 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( calls = [mock.call(
'Logical_Switch_Port', 'Logical_Switch_Port',
'foo-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.is_col_present.return_value = True
self.l3_inst._nb_ovn.lookup.return_value = self.lb_network self.l3_inst._nb_ovn.lookup.return_value = self.lb_network
self.l3_inst._nb_ovn.get_lswitch_port.return_value = self.member_lsp 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 # Validate that there is no external_mac and logical_port while
# setting the NAT entry. # setting the NAT entry.
expected_ext_ids = { expected_ext_ids = {
@ -1121,7 +1230,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
[self.fake_ovn_nat_rule], [self.fake_ovn_nat_rule],
] ]
self.l3_inst._nb_ovn.lookup.return_value = self.lb_network 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 = { expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: fip['id'], ovn_const.OVN_FIP_EXT_ID_KEY: fip['id'],
ovn_const.OVN_REV_NUM_EXT_ID_KEY: mock.ANY, 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 _nb_ovn.get_lrouter_port.return_value = lrp
self.l3_inst.get_router.return_value = self.fake_router_with_ext_gw 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() _nb_ovn.set_nat_rule_in_lrouter.assert_not_called()
expected_ext_ids = { expected_ext_ids = {
@ -1278,7 +1400,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
nb_ovn.get_floatingip.return_value = ( nb_ovn.get_floatingip.return_value = (
self.fake_ovn_nat_rule) self.fake_ovn_nat_rule)
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}} 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( nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
'neutron-router-id', 'neutron-router-id',
type='dnat_and_snat', type='dnat_and_snat',
@ -1311,9 +1440,16 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
nb_ovn.get_floatingip.return_value = ( nb_ovn.get_floatingip.return_value = (
self.fake_ovn_nat_rule) self.fake_ovn_nat_rule)
fip = {l3_def.FLOATINGIP: {qos_consts.QOS_POLICY_ID: 'qos_id_1'}} 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, with mock.patch.object(self.l3_inst._ovn_client._qos_driver,
'update_floatingip') as ufip: '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.delete_nat_rule_in_lrouter.assert_not_called()
nb_ovn.add_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) 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}) self.fake_floating_ip.update({'fixed_port_id': None})
uf.return_value = self.fake_floating_ip_new uf.return_value = self.fake_floating_ip_new
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}} 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() self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
expected_ext_ids = { expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'], 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( config.cfg.CONF.set_override(
'enable_distributed_floating_ip', True, group='ovn') 'enable_distributed_floating_ip', True, group='ovn')
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}} 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() self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
expected_ext_ids = { expected_ext_ids = {
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'], 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'}) self.fake_floating_ip_new.update({'port_id': 'foo'})
uf.return_value = self.fake_floating_ip_new uf.return_value = self.fake_floating_ip_new
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}} 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( nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
'neutron-router-id', 'neutron-router-id',
type='dnat_and_snat', type='dnat_and_snat',
@ -1427,7 +1584,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
'fixed_port_id': 'port_id'}) 'fixed_port_id': 'port_id'})
uf.return_value = self.fake_floating_ip_new uf.return_value = self.fake_floating_ip_new
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}} 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( nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
'neutron-router-id', 'neutron-router-id',
@ -1452,48 +1616,58 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
logical_port='port_id', logical_port='port_id',
external_ids=expected_ext_ids) external_ids=expected_ext_ids)
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_floatingips') @mock.patch('neutron.services.ovn_l3.plugin.OVNL3RouterPlugin.'
def test_disassociate_floatingips(self, gfs): 'update_floatingip_status')
gfs.return_value = [{'id': 'fip-id1', def test_disassociate_floatingips(self, status_upd_mock):
'floating_ip_address': '192.168.0.10', old_fips = [{'id': 'fip-id1',
'router_id': 'router-id', 'floating_ip_address': '192.168.0.10',
'port_id': 'port_id', 'router_id': 'router-id',
'floating_port_id': 'fip-port-id1', 'port_id': 'port_id',
'fixed_ip_address': '10.0.0.10', 'floating_port_id': 'fip-port-id1',
'floating_network_id': 'net1'}, 'fixed_ip_address': '10.0.0.10',
{'id': 'fip-id2', 'floating_network_id': 'net1'},
'floating_ip_address': '192.167.0.10', {'id': 'fip-id2',
'router_id': 'router-id', 'floating_ip_address': '192.167.0.10',
'port_id': 'port_id', 'router_id': 'router-id',
'floating_port_id': 'fip-port-id2', 'port_id': 'port_id',
'fixed_ip_address': '10.0.0.11', 'floating_port_id': 'fip-port-id2',
'floating_network_id': 'net2'}] 'fixed_ip_address': '10.0.0.11',
self.l3_inst.disassociate_floatingips(self.context, 'port_id', 'floating_network_id': 'net2'}]
do_notify=False) 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', delete_nat_calls = [mock.call('neutron-router-id',
type='dnat_and_snat', type='dnat_and_snat',
logical_ip=fip['fixed_ip_address'], logical_ip=fip['fixed_ip_address'],
external_ip=fip['floating_ip_address']) external_ip=fip['floating_ip_address'])
for fip in gfs.return_value] for fip in old_fips]
self.assertEqual( self.assertEqual(
len(delete_nat_calls), 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.call_count)
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_has_calls( self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_has_calls(
delete_nat_calls, any_order=True) 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.' @mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
'ovn_client.OVNClient.update_router_port') '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' 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, self.l3_inst._port_update(resources.PORT, events.AFTER_UPDATE, None,
payload=events.DBEventPayload( payload=events.DBEventPayload(
context, context,
states=(port,))) states=(port,)))
update_rp_mock.assert_not_called() 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, self.l3_inst._port_update(resources.PORT, events.AFTER_UPDATE, None,
payload=events.DBEventPayload( payload=events.DBEventPayload(
context, context,
@ -1503,11 +1677,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
port, port,
if_exists=True) 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' context = 'fake_context'
port = {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF, port = {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF,
'fixed_ips': [], 'fixed_ips': [],
'id': 'port-id'} 'id': 'port-id',
'device_id': 'router_id'}
gr.return_value = {'flavor_id': None}
self.assertRaises( self.assertRaises(
n_exc.ServicePortInUse, n_exc.ServicePortInUse,
self.l3_inst._port_update, self.l3_inst._port_update,
@ -1674,8 +1851,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
gn.return_value = prov_net gn.return_value = prov_net
gns.return_value = [self.fake_network] gns.return_value = [self.fake_network]
self.l3_inst.add_router_interface(self.context, router_id, payload = self._create_payload_for_router_interface(router_id)
interface_info) 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 # Make sure that the "gateway_mtu" option was set to the router port
fake_router_port_assert = self.fake_router_port_assert fake_router_port_assert = self.fake_router_port_assert
@ -1708,8 +1887,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
config.cfg.CONF.set_override( config.cfg.CONF.set_override(
'ovn_emit_need_to_frag', True, group='ovn') 'ovn_emit_need_to_frag', True, group='ovn')
router_id = 'router-id' router_id = 'router-id'
interface_info = {'port_id': 'router-port-id',
'network_id': 'priv-net'}
ari.return_value = self.fake_router_interface_info ari.return_value = self.fake_router_interface_info
# If we remove the router halfway the return value of # If we remove the router halfway the return value of
# _get_routers_ports will be RouterNotFound # _get_routers_ports will be RouterNotFound
@ -1723,8 +1900,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
gn.return_value = prov_net gn.return_value = prov_net
gns.return_value = [self.fake_network] gns.return_value = [self.fake_network]
self.l3_inst.add_router_interface(self.context, router_id, payload = self._create_payload_for_router_interface(router_id)
interface_info) 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 # Make sure that the "gateway_mtu" option was set to the router port
fake_router_port_assert = self.fake_router_port_assert 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_floatingip_update_subnet_gateway_disabled(expected_status)
# Test function _subnet_update of L3 OVN plugin. # 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). \ super(OVNL3ExtrarouteTests, self). \
test_update_subnet_gateway_for_external_net() test_update_subnet_gateway_for_external_net()
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with( 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.