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:
parent
b28bf2d3a1
commit
49366ecada
148
doc/source/admin/config-router-flavor-ovn.rst
Normal file
148
doc/source/admin/config-router-flavor-ovn.rst
Normal 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 |
|
||||
+--------------------------------------+-------------------------------+--------+-------+----------------------------------+
|
@ -38,6 +38,7 @@ Configuration
|
||||
config-qos-min-pps
|
||||
config-rbac
|
||||
config-routed-networks
|
||||
config-router-flavor-ovn
|
||||
config-sriov
|
||||
config-sfc
|
||||
config-service-subnets
|
||||
|
@ -36,10 +36,12 @@ from neutron_lib.api.definitions import fip_pf_port_range
|
||||
from neutron_lib.api.definitions import fip_port_details
|
||||
from neutron_lib.api.definitions import firewall_v2
|
||||
from neutron_lib.api.definitions import firewall_v2_stdattrs
|
||||
from neutron_lib.api.definitions import flavors
|
||||
from neutron_lib.api.definitions import floating_ip_port_forwarding
|
||||
from neutron_lib.api.definitions import floatingip_pools
|
||||
from neutron_lib.api.definitions import l3
|
||||
from neutron_lib.api.definitions import l3_ext_gw_mode
|
||||
from neutron_lib.api.definitions import l3_flavors
|
||||
from neutron_lib.api.definitions import logging
|
||||
from neutron_lib.api.definitions import multiprovidernet
|
||||
from neutron_lib.api.definitions import network_availability_zone
|
||||
@ -114,6 +116,8 @@ ML2_SUPPORTED_API_EXTENSIONS_OVN_L3 = [
|
||||
agent_def.ALIAS,
|
||||
az_def.ALIAS,
|
||||
raz_def.ALIAS,
|
||||
flavors.ALIAS,
|
||||
l3_flavors.ALIAS,
|
||||
]
|
||||
ML2_SUPPORTED_API_EXTENSIONS = [
|
||||
address_group.ALIAS,
|
||||
|
@ -1215,3 +1215,8 @@ def is_additional_chassis_supported(idl):
|
||||
|
||||
def is_nat_gateway_port_supported(idl):
|
||||
return idl.is_col_present('NAT', 'gateway_port')
|
||||
|
||||
|
||||
def is_ovn_provider_router(router):
|
||||
flavor_id = router.get('flavor_id')
|
||||
return flavor_id is None or flavor_id is const.ATTR_NOT_SPECIFIED
|
||||
|
@ -1216,20 +1216,21 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||
gw_ips = [x['ip_address'] for x in router.gw_port.fixed_ips]
|
||||
|
||||
cidrs = [x['cidr'] for x in subnets]
|
||||
subnet_ids = [subnet['id'] for subnet in subnets]
|
||||
metadata = {'interface_info': interface_info,
|
||||
'port': port, 'gateway_ips': gw_ips,
|
||||
'network_id': gw_network_id, 'cidrs': cidrs}
|
||||
'network_id': gw_network_id, 'cidrs': cidrs,
|
||||
'subnet_ids': subnet_ids}
|
||||
registry.publish(resources.ROUTER_INTERFACE,
|
||||
events.AFTER_DELETE, self,
|
||||
payload=events.DBEventPayload(
|
||||
context, metadata=metadata,
|
||||
resource_id=router_id))
|
||||
resource_id=router_id,
|
||||
states=(router,)))
|
||||
|
||||
return self._make_router_interface_info(router_id, port['tenant_id'],
|
||||
port['id'], port['network_id'],
|
||||
subnets[0]['id'],
|
||||
[subnet['id'] for subnet in
|
||||
subnets])
|
||||
subnets[0]['id'], subnet_ids)
|
||||
|
||||
def _get_floatingip(self, context, id):
|
||||
floatingip = l3_obj.FloatingIP.get_object(context, id=id)
|
||||
@ -1571,7 +1572,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||
payload=events.DBEventPayload(
|
||||
context, states=(floatingip_dict,),
|
||||
resource_id=floatingip_obj.id,
|
||||
metadata={'association_event': assoc_result}))
|
||||
metadata={'association_event': assoc_result},
|
||||
request_body=floatingip))
|
||||
if assoc_result:
|
||||
LOG.info(FIP_ASSOC_MSG,
|
||||
{'fip_id': floatingip_obj.id,
|
||||
@ -1637,7 +1639,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||
payload=events.DBEventPayload(
|
||||
context, states=(old_floatingip, floatingip_dict),
|
||||
resource_id=floatingip_obj.id,
|
||||
metadata={'association_event': assoc_result}))
|
||||
metadata={'association_event': assoc_result},
|
||||
request_body=floatingip))
|
||||
if assoc_result is not None:
|
||||
port_id = old_fixed_port_id or floatingip_obj.fixed_port_id
|
||||
assoc = 'associated' if assoc_result else 'disassociated'
|
||||
|
@ -57,6 +57,7 @@ from neutron.db import ovn_hash_ring_db
|
||||
from neutron.db import ovn_revision_numbers_db
|
||||
from neutron.db import provisioning_blocks
|
||||
from neutron.extensions import securitygroup as ext_sg
|
||||
from neutron.objects import router
|
||||
from neutron.plugins.ml2 import db as ml2_db
|
||||
from neutron.plugins.ml2.drivers.ovn.agent import neutron_agent as n_agent
|
||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
|
||||
@ -689,12 +690,18 @@ class OVNMechanismDriver(api.MechanismDriver):
|
||||
|
||||
# in the case of router ports we also need to
|
||||
# track the creation and update of the LRP OVN objects
|
||||
if ovn_utils.is_lsp_router_port(port):
|
||||
if (ovn_utils.is_lsp_router_port(port) and
|
||||
self._is_ovn_router_flavor_port(context, port)):
|
||||
ovn_revision_numbers_db.create_initial_revision(
|
||||
context.plugin_context, port['id'],
|
||||
ovn_const.TYPE_ROUTER_PORTS,
|
||||
std_attr_id=context.current['standard_attr_id'])
|
||||
|
||||
def _is_ovn_router_flavor_port(self, context, port):
|
||||
router_obj = router.Router.get_object(context.plugin_context,
|
||||
id=port['device_id'])
|
||||
return ovn_utils.is_ovn_provider_router(router_obj)
|
||||
|
||||
def _is_port_provisioning_required(self, port, host, original_host=None):
|
||||
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
|
||||
if vnic_type not in self.supported_vnic_types:
|
||||
@ -829,7 +836,8 @@ class OVNMechanismDriver(api.MechanismDriver):
|
||||
self._insert_port_provisioning_block(context.plugin_context,
|
||||
port['id'])
|
||||
|
||||
if ovn_utils.is_lsp_router_port(port):
|
||||
if (ovn_utils.is_lsp_router_port(port) and
|
||||
self._is_ovn_router_flavor_port(context, port)):
|
||||
# handle the case when an existing port is added to a
|
||||
# logical router so we need to track the creation of the lrp
|
||||
if not ovn_utils.is_lsp_router_port(original_port):
|
||||
|
@ -391,7 +391,7 @@ class DBInconsistenciesPeriodics(SchemaAwarePeriodicsBase):
|
||||
def _create_lrouter_port(self, context, port):
|
||||
router_id = port['device_id']
|
||||
iface_info = self._ovn_client._l3_plugin._add_neutron_router_interface(
|
||||
context, router_id, {'port_id': port['id']}, may_exist=True)
|
||||
context, router_id, {'port_id': port['id']})
|
||||
self._ovn_client.create_router_port(context, router_id, iface_info)
|
||||
|
||||
def _check_subnet_global_dhcp_opts(self):
|
||||
|
@ -51,6 +51,7 @@ from neutron.conf.agent import ovs_conf
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||
from neutron.db import ovn_revision_numbers_db as db_rev
|
||||
from neutron.db import segments_db
|
||||
from neutron.objects import router
|
||||
from neutron.plugins.ml2 import db as ml2_db
|
||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions \
|
||||
import placement as placement_extension
|
||||
@ -646,7 +647,10 @@ class OVNClient(object):
|
||||
# LogicalSwitchPortUpdateDownEvent, that will most likely
|
||||
# cause a revision conflict.
|
||||
# https://bugs.launchpad.net/neutron/+bug/1955578
|
||||
columns_dict['type'] = ovn_const.LSP_TYPE_ROUTER
|
||||
router_obj = router.Router.get_object(context,
|
||||
id=port['device_id'])
|
||||
if utils.is_ovn_provider_router(router_obj):
|
||||
columns_dict['type'] = ovn_const.LSP_TYPE_ROUTER
|
||||
port_info.options.update(
|
||||
self._nb_idl.get_router_port_options(port['id']))
|
||||
else:
|
||||
|
@ -87,7 +87,7 @@ class DriverController(object):
|
||||
router = payload.latest_state
|
||||
router_db = payload.metadata['router_db']
|
||||
router_id = payload.resource_id
|
||||
if _flavor_specified(router):
|
||||
if flavor_specified(router):
|
||||
router_db.flavor_id = router['flavor_id']
|
||||
drv = self._get_provider_for_create(context, router)
|
||||
self._stm.add_resource_association(context, plugin_constants.L3,
|
||||
@ -127,7 +127,7 @@ class DriverController(object):
|
||||
drv = self.get_provider_for_router(payload.context,
|
||||
payload.resource_id)
|
||||
new_drv = None
|
||||
if _flavor_specified(payload.request_body):
|
||||
if flavor_specified(payload.request_body):
|
||||
if (payload.request_body['flavor_id'] !=
|
||||
payload.states[0]['flavor_id']):
|
||||
# TODO(kevinbenton): this is currently disallowed by the API
|
||||
@ -210,7 +210,7 @@ class DriverController(object):
|
||||
|
||||
def _get_provider_for_create(self, context, router):
|
||||
"""Get provider based on flavor or ha/distributed flags."""
|
||||
if not _flavor_specified(router):
|
||||
if not flavor_specified(router):
|
||||
return self._attrs_to_driver(router)
|
||||
return self._get_l3_driver_by_flavor(context, router['flavor_id'])
|
||||
|
||||
@ -293,7 +293,7 @@ def _is_ha(ha_attr):
|
||||
return True
|
||||
|
||||
|
||||
def _flavor_specified(router):
|
||||
def flavor_specified(router):
|
||||
return ('flavor_id' in router and
|
||||
router['flavor_id'] != lib_const.ATTR_NOT_SPECIFIED)
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#
|
||||
|
||||
from neutron_lib.api.definitions import external_net
|
||||
from neutron_lib.api.definitions import l3 as l3_apidef
|
||||
from neutron_lib.api.definitions import portbindings
|
||||
from neutron_lib.api.definitions import provider_net as pnet
|
||||
from neutron_lib.api.definitions import qos_fip as qos_fip_apidef
|
||||
@ -23,19 +24,18 @@ from neutron_lib.callbacks import resources
|
||||
from neutron_lib import constants as n_const
|
||||
from neutron_lib import context as n_context
|
||||
from neutron_lib.db import api as db_api
|
||||
from neutron_lib.db import resource_extend
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from neutron_lib.exceptions import availability_zone as az_exc
|
||||
from neutron_lib.plugins import constants as plugin_constants
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.services import base as service_base
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.common.ovn import constants as ovn_const
|
||||
from neutron.common.ovn import extensions
|
||||
from neutron.common.ovn import utils
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
|
||||
from neutron.db.availability_zone import router as router_az_db
|
||||
from neutron.db import dns_db
|
||||
from neutron.db import extraroute_db
|
||||
@ -46,11 +46,11 @@ from neutron.db import l3_fip_qos
|
||||
from neutron.db import l3_gateway_ip_qos
|
||||
from neutron.db import l3_gwmode_db
|
||||
from neutron.db.models import l3 as l3_models
|
||||
from neutron.db import ovn_revision_numbers_db as db_rev
|
||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
|
||||
from neutron.quota import resource_registry
|
||||
from neutron.scheduler import l3_ovn_scheduler
|
||||
from neutron.services.ovn_l3 import exceptions as ovn_l3_exc
|
||||
from neutron.services.ovn_l3.service_providers import driver_controller
|
||||
from neutron.services.portforwarding.drivers.ovn import driver \
|
||||
as port_forwarding
|
||||
|
||||
@ -94,15 +94,7 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
self._ovn_client_inst = None
|
||||
self.scheduler = l3_ovn_scheduler.get_scheduler()
|
||||
self.port_forwarding = port_forwarding.OVNPortForwarding(self)
|
||||
self._register_precommit_callbacks()
|
||||
|
||||
def _register_precommit_callbacks(self):
|
||||
registry.subscribe(
|
||||
self.create_router_precommit, resources.ROUTER,
|
||||
events.PRECOMMIT_CREATE)
|
||||
registry.subscribe(
|
||||
self.create_floatingip_precommit, resources.FLOATING_IP,
|
||||
events.PRECOMMIT_CREATE)
|
||||
self.l3_driver_controller = driver_controller.DriverController(self)
|
||||
|
||||
@staticmethod
|
||||
def _disable_qos_extensions_by_extension_drivers(aliases):
|
||||
@ -170,67 +162,13 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
return ("L3 Router Service Plugin for basic L3 forwarding"
|
||||
" using OVN")
|
||||
|
||||
def create_router_precommit(self, resource, event, trigger, payload):
|
||||
context = payload.context
|
||||
context.session.flush()
|
||||
router_id = payload.resource_id
|
||||
router_db = payload.metadata['router_db']
|
||||
# NOTE(ralonsoh): the "distributed" flag is a static configuration
|
||||
# parameter that needs to be defined only during the router creation.
|
||||
extra_attr = router_db['extra_attributes']
|
||||
extra_attr.distributed = ovn_conf.is_ovn_distributed_floating_ip()
|
||||
|
||||
db_rev.create_initial_revision(
|
||||
context, router_id, ovn_const.TYPE_ROUTERS,
|
||||
std_attr_id=router_db.standard_attr.id)
|
||||
|
||||
def create_router(self, context, router):
|
||||
router = super(OVNL3RouterPlugin, self).create_router(context, router)
|
||||
try:
|
||||
self._ovn_client.create_router(context, router)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
# Delete the logical router
|
||||
LOG.exception('Unable to create lrouter %s', router['id'])
|
||||
super(OVNL3RouterPlugin, self).delete_router(context,
|
||||
router['id'])
|
||||
return router
|
||||
|
||||
def update_router(self, context, id, router):
|
||||
original_router = self.get_router(context, id)
|
||||
result = super(OVNL3RouterPlugin, self).update_router(context, id,
|
||||
router)
|
||||
try:
|
||||
self._ovn_client.update_router(context, result,
|
||||
original_router)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception('Unable to update lrouter %s', id)
|
||||
revert_router = {'router': original_router}
|
||||
super(OVNL3RouterPlugin, self).update_router(context, id,
|
||||
revert_router)
|
||||
return result
|
||||
|
||||
def delete_router(self, context, id):
|
||||
original_router = self.get_router(context, id)
|
||||
super(OVNL3RouterPlugin, self).delete_router(context, id)
|
||||
try:
|
||||
self._ovn_client.delete_router(context, id)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception('Unable to delete lrouter %s', id)
|
||||
super(OVNL3RouterPlugin, self).create_router(
|
||||
context, {'router': original_router})
|
||||
|
||||
def _add_neutron_router_interface(self, context, router_id,
|
||||
interface_info, may_exist=False):
|
||||
interface_info):
|
||||
try:
|
||||
router_interface_info = (
|
||||
super(OVNL3RouterPlugin, self).add_router_interface(
|
||||
context, router_id, interface_info))
|
||||
except n_exc.PortInUse:
|
||||
if not may_exist:
|
||||
raise
|
||||
# NOTE(lucasagomes): If the port is already being used it means
|
||||
# the interface has been created already, let's just fetch it from
|
||||
# the database. Perhaps the code below should live in Neutron
|
||||
@ -247,70 +185,24 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
|
||||
return router_interface_info
|
||||
|
||||
def add_router_interface(self, context, router_id, interface_info=None):
|
||||
router_interface_info = self._add_neutron_router_interface(
|
||||
context, router_id, interface_info)
|
||||
try:
|
||||
self._ovn_client.create_router_port(
|
||||
context, router_id, router_interface_info)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(
|
||||
'Unable to add router interface to lrouter %s. '
|
||||
'Interface info: %s', router_id, interface_info)
|
||||
super(OVNL3RouterPlugin, self).remove_router_interface(
|
||||
context, router_id, router_interface_info)
|
||||
|
||||
return router_interface_info
|
||||
|
||||
def remove_router_interface(self, context, router_id, interface_info):
|
||||
router_interface_info = (
|
||||
super(OVNL3RouterPlugin, self).remove_router_interface(
|
||||
context, router_id, interface_info))
|
||||
try:
|
||||
port_id = router_interface_info['port_id']
|
||||
subnet_ids = router_interface_info.get('subnet_ids')
|
||||
self._ovn_client.delete_router_port(
|
||||
context, port_id, router_id=router_id, subnet_ids=subnet_ids)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(
|
||||
'Unable to remove router interface from lrouter %s. '
|
||||
'Interface info: %s', router_id, interface_info)
|
||||
super(OVNL3RouterPlugin, self).add_router_interface(
|
||||
context, router_id, interface_info)
|
||||
return router_interface_info
|
||||
|
||||
def create_floatingip_precommit(self, resource, event, trigger, payload):
|
||||
context = payload.context
|
||||
floatingip_id = payload.resource_id
|
||||
floatingip_db = payload.desired_state
|
||||
|
||||
db_rev.create_initial_revision(
|
||||
context, floatingip_id, ovn_const.TYPE_FLOATINGIPS,
|
||||
std_attr_id=floatingip_db.standard_attr.id)
|
||||
|
||||
def create_floatingip(self, context, floatingip,
|
||||
initial_status=n_const.FLOATINGIP_STATUS_DOWN):
|
||||
fip = super(OVNL3RouterPlugin, self).create_floatingip(
|
||||
# The OVN L3 plugin creates floating IPs in down status by default,
|
||||
# whereas the L3 DB layer creates them in active status. So we keep
|
||||
# this method to create the floating IP in the DB with status down,
|
||||
# while the flavor drivers are responsible for calling the correct
|
||||
# backend to instatiate the floating IP in the data plane
|
||||
return super(OVNL3RouterPlugin, self).create_floatingip(
|
||||
context, floatingip, initial_status)
|
||||
self._ovn_client.create_floatingip(context, fip)
|
||||
return fip
|
||||
|
||||
def delete_floatingip(self, context, id):
|
||||
super(OVNL3RouterPlugin, self).delete_floatingip(context, id)
|
||||
self._ovn_client.delete_floatingip(context, id)
|
||||
|
||||
def update_floatingip(self, context, id, floatingip):
|
||||
fip = super(OVNL3RouterPlugin, self).update_floatingip(context, id,
|
||||
floatingip)
|
||||
self._ovn_client.update_floatingip(context, fip, floatingip)
|
||||
return fip
|
||||
|
||||
def update_floatingip_status(self, context, floatingip_id, status):
|
||||
fip = self.update_floatingip_status_retry(
|
||||
context, floatingip_id, status)
|
||||
self._ovn_client.update_floatingip_status(context, fip)
|
||||
registry.publish(
|
||||
resources.FLOATING_IP, events.AFTER_STATUS_UPDATE, self,
|
||||
payload=events.DBEventPayload(
|
||||
context, states=(fip,),
|
||||
resource_id=floatingip_id))
|
||||
return fip
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
@ -319,30 +211,6 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
return super(OVNL3RouterPlugin, self).update_floatingip_status(
|
||||
context, floatingip_id, status)
|
||||
|
||||
def disassociate_floatingips(self, context, port_id, do_notify=True):
|
||||
fips = self.get_floatingips(context.elevated(),
|
||||
filters={'port_id': [port_id]})
|
||||
router_ids = super(OVNL3RouterPlugin, self).disassociate_floatingips(
|
||||
context, port_id, do_notify)
|
||||
for fip in fips:
|
||||
router_id = fip.get('router_id')
|
||||
fixed_ip_address = fip.get('fixed_ip_address')
|
||||
if router_id and fixed_ip_address:
|
||||
update_fip = {
|
||||
'id': fip['id'],
|
||||
'logical_ip': fixed_ip_address,
|
||||
'external_ip': fip['floating_ip_address'],
|
||||
'floating_network_id': fip['floating_network_id']}
|
||||
try:
|
||||
self._ovn_client.disassociate_floatingip(update_fip,
|
||||
router_id)
|
||||
self.update_floatingip_status(
|
||||
context, fip['id'], n_const.FLOATINGIP_STATUS_DOWN)
|
||||
except Exception as e:
|
||||
LOG.error('Error in disassociating floatingip %(id)s: '
|
||||
'%(error)s', {'id': fip['id'], 'error': e})
|
||||
return router_ids
|
||||
|
||||
def _get_gateway_port_physnet_mapping(self):
|
||||
# This function returns all gateway ports with corresponding
|
||||
# external network's physnet
|
||||
@ -359,8 +227,10 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
net_physnet_dict[net['id']] = net.get(pnet.PHYSICAL_NETWORK)
|
||||
for port in l3plugin._plugin.get_ports(context, filters={
|
||||
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW]}):
|
||||
port_physnet_dict[port['id']] = net_physnet_dict.get(
|
||||
port['network_id'])
|
||||
if utils.is_ovn_provider_router(
|
||||
l3plugin.get_router(context, port['device_id'])):
|
||||
port_physnet_dict[port['id']] = net_physnet_dict.get(
|
||||
port['network_id'])
|
||||
return port_physnet_dict
|
||||
|
||||
def update_router_gateway_port_bindings(self, router, host):
|
||||
@ -474,7 +344,9 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
'device_owner': [n_const.DEVICE_OWNER_ROUTER_GW],
|
||||
'fixed_ips': {'subnet_id': [orig['id']]},
|
||||
})
|
||||
router_ids = {port['device_id'] for port in gw_ports}
|
||||
router_ids = {port['device_id'] for port in gw_ports
|
||||
if utils.is_ovn_provider_router(
|
||||
l3plugin.get_router(context, port['device_id']))}
|
||||
remove = [{'destination': '0.0.0.0/0', 'nexthop': orig_gw_ip}
|
||||
] if orig_gw_ip else []
|
||||
add = [{'destination': '0.0.0.0/0', 'nexthop': current_gw_ip}
|
||||
@ -508,11 +380,16 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
# https://bugs.launchpad.net/neutron/+bug/1948457
|
||||
if (event == events.BEFORE_UPDATE and
|
||||
'fixed_ips' in current and not current['fixed_ips'] and
|
||||
utils.is_lsp_router_port(original)):
|
||||
utils.is_lsp_router_port(original) and
|
||||
utils.is_ovn_provider_router(
|
||||
l3plugin.get_router(context, original['device_id']))):
|
||||
reason = _("Router port must have at least one IP.")
|
||||
raise n_exc.ServicePortInUse(port_id=original['id'], reason=reason)
|
||||
|
||||
if event == events.AFTER_UPDATE and utils.is_lsp_router_port(current):
|
||||
if (event == events.AFTER_UPDATE and
|
||||
utils.is_lsp_router_port(current) and
|
||||
utils.is_ovn_provider_router(
|
||||
l3plugin.get_router(context, current['device_id']))):
|
||||
# We call the update_router port with if_exists, because neutron,
|
||||
# internally creates the port, and then calls update, which will
|
||||
# trigger this callback even before we had the chance to create
|
||||
@ -541,3 +418,8 @@ class OVNL3RouterPlugin(service_base.ServicePluginBase,
|
||||
if diff:
|
||||
raise az_exc.AvailabilityZoneNotFound(
|
||||
availability_zone=', '.join(diff))
|
||||
|
||||
@staticmethod
|
||||
@resource_extend.extends([l3_apidef.ROUTERS])
|
||||
def add_flavor_id(router_res, router_db):
|
||||
router_res['flavor_id'] = router_db['flavor_id']
|
||||
|
@ -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.")
|
252
neutron/services/ovn_l3/service_providers/ovn.py
Normal file
252
neutron/services/ovn_l3/service_providers/ovn.py
Normal 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])
|
170
neutron/services/ovn_l3/service_providers/user_defined.py
Normal file
170
neutron/services/ovn_l3/service_providers/user_defined.py
Normal 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)
|
@ -21,6 +21,8 @@ from futurist import periodics
|
||||
from neutron_lib.api.definitions import external_net as extnet_apidef
|
||||
from neutron_lib.api.definitions import floating_ip_port_forwarding as pf_def
|
||||
from neutron_lib.api.definitions import provider_net as provnet_apidef
|
||||
from neutron_lib.callbacks import events
|
||||
from neutron_lib.callbacks import registry
|
||||
from neutron_lib import constants as n_const
|
||||
from neutron_lib import context as n_context
|
||||
from neutron_lib.exceptions import l3 as lib_l3_exc
|
||||
@ -30,6 +32,7 @@ from neutron.common.ovn import utils
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as ovn_config
|
||||
from neutron.db import ovn_revision_numbers_db as db_rev
|
||||
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance
|
||||
from neutron.services.portforwarding import constants as pf_consts
|
||||
from neutron.tests.functional import base
|
||||
from neutron.tests.functional.services.logapi.drivers.ovn \
|
||||
import test_driver as test_log_driver
|
||||
@ -925,65 +928,69 @@ class TestMaintenance(_TestMaintenanceHelper):
|
||||
p1 = self._create_port('testp1', net1['id'])
|
||||
p1_ip = p1['fixed_ips'][0]['ip_address']
|
||||
|
||||
with mock.patch('neutron_lib.callbacks.registry.publish') as m_publish:
|
||||
# > Create
|
||||
fip_pf_args = {
|
||||
pf_def.EXTERNAL_PORT: 2222,
|
||||
pf_def.INTERNAL_PORT: 22,
|
||||
pf_def.INTERNAL_PORT_ID: p1['id'],
|
||||
pf_def.PROTOCOL: 'tcp',
|
||||
pf_def.INTERNAL_IP_ADDRESS: p1_ip}
|
||||
pf_obj = self.pf_plugin.create_floatingip_port_forwarding(
|
||||
self.context, fip_id, **fip_attrs(fip_pf_args))
|
||||
call = mock.call('port_forwarding', 'after_create', self.pf_plugin,
|
||||
payload=mock.ANY)
|
||||
m_publish.assert_has_calls([call])
|
||||
callbacks = registry._get_callback_manager()._callbacks
|
||||
pf_cb = callbacks[pf_consts.PORT_FORWARDING]
|
||||
key = list(pf_cb[events.AFTER_UPDATE][0][1].keys())[0]
|
||||
pf_cb[events.AFTER_CREATE][0][1][key] = mock.MagicMock()
|
||||
|
||||
# Assert load balancer for port forwarding was not created
|
||||
self.assertFalse(self._find_pf_lb(router_id, fip_id))
|
||||
# > Create
|
||||
fip_pf_args = {
|
||||
pf_def.EXTERNAL_PORT: 2222,
|
||||
pf_def.INTERNAL_PORT: 22,
|
||||
pf_def.INTERNAL_PORT_ID: p1['id'],
|
||||
pf_def.PROTOCOL: 'tcp',
|
||||
pf_def.INTERNAL_IP_ADDRESS: p1_ip}
|
||||
pf_obj = self.pf_plugin.create_floatingip_port_forwarding(
|
||||
self.context, fip_id, **fip_attrs(fip_pf_args))
|
||||
call = mock.call('port_forwarding', 'after_create', self.pf_plugin,
|
||||
payload=mock.ANY)
|
||||
pf_cb[events.AFTER_CREATE][0][1][key].assert_has_calls([call])
|
||||
|
||||
# Call the maintenance thread to fix the problem
|
||||
self.maint.check_for_inconsistencies()
|
||||
# Assert load balancer for port forwarding was not created
|
||||
self.assertFalse(self._find_pf_lb(router_id, fip_id))
|
||||
|
||||
# Assert load balancer for port forwarding was created
|
||||
_verify_lb(self, 'tcp', 2222, 22)
|
||||
# Call the maintenance thread to fix the problem
|
||||
self.maint.check_for_inconsistencies()
|
||||
|
||||
# > Update
|
||||
fip_pf_args = {pf_def.EXTERNAL_PORT: 5353,
|
||||
pf_def.INTERNAL_PORT: 53,
|
||||
pf_def.PROTOCOL: 'udp'}
|
||||
m_publish.reset_mock()
|
||||
self.pf_plugin.update_floatingip_port_forwarding(
|
||||
self.context, pf_obj['id'], fip_id, **fip_attrs(fip_pf_args))
|
||||
call = mock.call('port_forwarding', 'after_update', self.pf_plugin,
|
||||
payload=mock.ANY)
|
||||
m_publish.assert_has_calls([call])
|
||||
# Assert load balancer for port forwarding was created
|
||||
_verify_lb(self, 'tcp', 2222, 22)
|
||||
|
||||
# Assert load balancer for port forwarding is stale
|
||||
_verify_lb(self, 'tcp', 2222, 22)
|
||||
# > Update
|
||||
fip_pf_args = {pf_def.EXTERNAL_PORT: 5353,
|
||||
pf_def.INTERNAL_PORT: 53,
|
||||
pf_def.PROTOCOL: 'udp'}
|
||||
pf_cb[events.AFTER_UPDATE][0][1][key] = mock.MagicMock()
|
||||
self.pf_plugin.update_floatingip_port_forwarding(
|
||||
self.context, pf_obj['id'], fip_id, **fip_attrs(fip_pf_args))
|
||||
call = mock.call('port_forwarding', 'after_update', self.pf_plugin,
|
||||
payload=mock.ANY)
|
||||
pf_cb[events.AFTER_UPDATE][0][1][key].assert_has_calls([call])
|
||||
|
||||
# Call the maintenance thread to fix the problem
|
||||
self.maint.check_for_inconsistencies()
|
||||
# Assert load balancer for port forwarding is stale
|
||||
_verify_lb(self, 'tcp', 2222, 22)
|
||||
|
||||
# Assert load balancer for port forwarding was updated
|
||||
_verify_lb(self, 'udp', 5353, 53)
|
||||
# Call the maintenance thread to fix the problem
|
||||
self.maint.check_for_inconsistencies()
|
||||
|
||||
# > Delete
|
||||
m_publish.reset_mock()
|
||||
self.pf_plugin.delete_floatingip_port_forwarding(
|
||||
self.context, pf_obj['id'], fip_id)
|
||||
call = mock.call('port_forwarding', 'after_delete', self.pf_plugin,
|
||||
payload=mock.ANY)
|
||||
m_publish.assert_has_calls([call])
|
||||
# Assert load balancer for port forwarding was updated
|
||||
_verify_lb(self, 'udp', 5353, 53)
|
||||
|
||||
# Assert load balancer for port forwarding is stale
|
||||
_verify_lb(self, 'udp', 5353, 53)
|
||||
# > Delete
|
||||
pf_cb[events.AFTER_DELETE][0][1][key] = mock.MagicMock()
|
||||
self.pf_plugin.delete_floatingip_port_forwarding(
|
||||
self.context, pf_obj['id'], fip_id)
|
||||
call = mock.call('port_forwarding', 'after_delete', self.pf_plugin,
|
||||
payload=mock.ANY)
|
||||
pf_cb[events.AFTER_DELETE][0][1][key].assert_has_calls([call])
|
||||
|
||||
# Call the maintenance thread to fix the problem
|
||||
self.maint.check_for_inconsistencies()
|
||||
# Assert load balancer for port forwarding is stale
|
||||
_verify_lb(self, 'udp', 5353, 53)
|
||||
|
||||
# Assert load balancer for port forwarding is gone
|
||||
self.assertFalse(self._find_pf_lb(router_id, fip_id))
|
||||
# Call the maintenance thread to fix the problem
|
||||
self.maint.check_for_inconsistencies()
|
||||
|
||||
# Assert load balancer for port forwarding is gone
|
||||
self.assertFalse(self._find_pf_lb(router_id, fip_id))
|
||||
|
||||
def test_check_for_ha_chassis_group(self):
|
||||
net1 = self._create_network('network1test', external=False)
|
||||
|
@ -620,7 +620,8 @@ class TestExternalPorts(base.TestOVNFunctionalBase):
|
||||
'ports', port_upt_data, port['id'], self.fmt)
|
||||
port_res = port_req.get_response(self.api)
|
||||
|
||||
def test_add_external_port_avoid_flapping(self):
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
def test_add_external_port_avoid_flapping(self, gr):
|
||||
class LogicalSwitchPortUpdateUpEventTest(event.RowEvent):
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
@ -651,6 +652,7 @@ class TestExternalPorts(base.TestOVNFunctionalBase):
|
||||
def get_count(self):
|
||||
return self.count
|
||||
|
||||
gr.return_value = {'flavor_id': None}
|
||||
og_up_event = ovsdb_monitor.LogicalSwitchPortUpdateUpEvent(None)
|
||||
og_down_event = ovsdb_monitor.LogicalSwitchPortUpdateDownEvent(None)
|
||||
test_down_event = LogicalSwitchPortUpdateDownEventTest()
|
||||
@ -666,7 +668,8 @@ class TestExternalPorts(base.TestOVNFunctionalBase):
|
||||
# status as up, triggering only a LogicalSwitchPortUpdateUpEvent.
|
||||
self._create_router_port(portbindings.VNIC_DIRECT)
|
||||
self.assertEqual(test_down_event.get_count(), 0)
|
||||
self.assertEqual(test_up_event.get_count(), 1)
|
||||
n_utils.wait_until_true(lambda: test_up_event.get_count() == 1,
|
||||
timeout=10)
|
||||
|
||||
def test_external_port_create_vnic_direct(self):
|
||||
self._test_external_port_create(portbindings.VNIC_DIRECT)
|
||||
|
@ -2224,7 +2224,9 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
|
||||
self.assertEqual(webob.exc.HTTPClientError.code,
|
||||
res.status_int)
|
||||
|
||||
def test_requested_fixed_ip_address_v6_slaac_router_iface(self):
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
def test_requested_fixed_ip_address_v6_slaac_router_iface(self, gr):
|
||||
gr.return_value = {'flavor_id': ''}
|
||||
with self.subnet(gateway_ip='fe80::1',
|
||||
cidr='fe80::/64',
|
||||
ip_version=constants.IP_VERSION_6,
|
||||
@ -2284,7 +2286,9 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s
|
||||
self.assertIn({'ip_address': eui_addr,
|
||||
'subnet_id': subnet2['subnet']['id']}, ips)
|
||||
|
||||
def test_create_router_port_ipv4_and_ipv6_slaac_no_fixed_ips(self):
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
def test_create_router_port_ipv4_and_ipv6_slaac_no_fixed_ips(self, gr):
|
||||
gr.return_value = {'flavor_id': ''}
|
||||
with self.network() as network:
|
||||
# Create an IPv4 and an IPv6 SLAAC subnet on the network
|
||||
with self.subnet(network) as subnet_v4,\
|
||||
@ -3764,8 +3768,10 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
|
||||
sport = self.deserialize(self.fmt, req.get_response(self.api))
|
||||
self.assertEqual(0, len(sport['port']['fixed_ips']))
|
||||
|
||||
def test_delete_subnet_ipv6_slaac_router_port_exists(self):
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
def test_delete_subnet_ipv6_slaac_router_port_exists(self, gr):
|
||||
"""Test IPv6 SLAAC subnet delete with a router port using the subnet"""
|
||||
gr.return_value = {'flavor_id': ''}
|
||||
subnet, port = self._create_slaac_subnet_and_port(
|
||||
constants.DEVICE_OWNER_ROUTER_INTF)
|
||||
# Delete the subnet and assert that we get a HTTP 409 error
|
||||
@ -4502,7 +4508,9 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
|
||||
ipv6_ra_mode=constants.IPV6_SLAAC,
|
||||
ipv6_address_mode=constants.IPV6_SLAAC)
|
||||
|
||||
def test_create_subnet_ipv6_first_ip_owned_by_router(self):
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
def test_create_subnet_ipv6_first_ip_owned_by_router(self, gr):
|
||||
gr.return_value = {'flavor_id': ''}
|
||||
cidr = '2001::/64'
|
||||
with self.network() as network:
|
||||
net_id = network['network']['id']
|
||||
@ -4598,10 +4606,12 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
|
||||
self.assertEqual(webob.exc.HTTPClientError.code,
|
||||
ctx_manager.exception.code)
|
||||
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
def _test_create_subnet_ipv6_auto_addr_with_port_on_network(
|
||||
self, addr_mode, device_owner=DEVICE_OWNER_COMPUTE,
|
||||
self, addr_mode, gr, device_owner=DEVICE_OWNER_COMPUTE,
|
||||
insert_db_reference_error=False, insert_port_not_found=False,
|
||||
insert_address_allocated=False):
|
||||
gr.return_value = {'flavor_id': ''}
|
||||
# Create a network with one IPv4 subnet and one port
|
||||
with self.network() as network,\
|
||||
self.subnet(network=network) as v4_subnet,\
|
||||
|
@ -2495,9 +2495,12 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
|
||||
def test__update_dnat_entry_if_needed_down_no_dvr(self):
|
||||
self._test__update_dnat_entry_if_needed(up=False, dvr=False)
|
||||
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
|
||||
'ovn_client.OVNClient._get_router_ports')
|
||||
def _test_update_network_fragmentation(self, new_mtu, expected_opts, grps):
|
||||
def _test_update_network_fragmentation(self, new_mtu, expected_opts, grps,
|
||||
gr):
|
||||
gr.return_value = {'flavor_id': ''}
|
||||
network_attrs = {external_net.EXTERNAL: True}
|
||||
network = self._make_network(
|
||||
self.fmt, 'net1', True, as_admin=True,
|
||||
|
@ -1344,15 +1344,19 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
|
||||
with self.port(device_owner="fake:test"):
|
||||
self.assertTrue(cp.called)
|
||||
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
def _test_no_dhcp_provisioning_blocks_removed_empty_device_owner(
|
||||
self, device_owner):
|
||||
self, device_owner, gr):
|
||||
gr.return_value = {'flavor_id': ''}
|
||||
with mock.patch.object(provisioning_blocks,
|
||||
'remove_provisioning_component') as cp:
|
||||
with self.port(device_owner=device_owner):
|
||||
self.assertFalse(cp.called)
|
||||
|
||||
@mock.patch('neutron.objects.router.Router.get_object')
|
||||
def _test_no_dhcp_provisioning_blocks_added_empty_device_owner(
|
||||
self, device_owner):
|
||||
self, device_owner, gr):
|
||||
gr.return_value = {'flavor_id': ''}
|
||||
with mock.patch.object(provisioning_blocks,
|
||||
'add_provisioning_component') as cp:
|
||||
with self.port(device_owner=device_owner):
|
||||
|
@ -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()
|
@ -37,6 +37,8 @@ from neutron.common.ovn import utils
|
||||
from neutron.conf.plugins.ml2.drivers import driver_type as driver_type_conf
|
||||
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config
|
||||
from neutron.db import extraroute_db
|
||||
from neutron.db.models import l3 as l3_models
|
||||
from neutron.db.models import ovn as ovn_models
|
||||
from neutron import manager as neutron_manager
|
||||
from neutron.plugins.ml2 import managers
|
||||
from neutron.services.ovn_l3 import exceptions as ovn_l3_exc
|
||||
@ -78,6 +80,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
attrs=network_attrs).info()
|
||||
self.fake_router_port = {'device_id': '',
|
||||
'network_id': self.fake_network['id'],
|
||||
'tenant_id': 'tenant-id',
|
||||
'device_owner': 'network:router_interface',
|
||||
'mac_address': 'aa:aa:aa:aa:aa:aa',
|
||||
'status': constants.PORT_STATUS_ACTIVE,
|
||||
@ -103,6 +106,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.fake_router = {'id': 'router-id',
|
||||
'name': 'router',
|
||||
'admin_state_up': False,
|
||||
'flavor_id': None,
|
||||
'routes': [{'destination': '1.1.1.0/24',
|
||||
'nexthop': '10.0.0.2'}]}
|
||||
self.fake_router_interface_info = {
|
||||
@ -122,6 +126,7 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'id': 'router-id',
|
||||
'name': 'router',
|
||||
'admin_state_up': True,
|
||||
'flavor_id': None,
|
||||
'external_gateway_info': self.fake_external_fixed_ips,
|
||||
'gw_port_id': 'gw-port-id'
|
||||
}
|
||||
@ -179,6 +184,8 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.fake_floating_ip['router_id'])},
|
||||
}))
|
||||
self.l3_inst = directory.get_plugin(plugin_constants.L3)
|
||||
ovn_provider = self.l3_inst.l3_driver_controller.default_provider
|
||||
self.ovn_drv = self.l3_inst.l3_driver_controller.drivers[ovn_provider]
|
||||
self.lb_id = uuidutils.generate_uuid()
|
||||
self.member_subnet = {'id': 'subnet-id',
|
||||
'ip_version': 4,
|
||||
@ -292,6 +299,9 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.get_rev_p = self._start_mock(
|
||||
'neutron.common.ovn.utils.get_revision_number',
|
||||
return_value=1)
|
||||
self.get_rev_row_p = self._start_mock(
|
||||
'neutron.db.ovn_revision_numbers_db.get_revision_row',
|
||||
return_value=ovn_models.OVNRevisionNumbers(revision_number=1))
|
||||
self.admin_context = mock.Mock()
|
||||
self._start_mock(
|
||||
'neutron_lib.context.get_admin_context',
|
||||
@ -326,13 +336,26 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
driver, None)
|
||||
self.assertEqual(fake_mech_driver.obj, result)
|
||||
|
||||
def _create_payload_for_router_interface(self, router_id,
|
||||
pass_subnet=True):
|
||||
router_obj = l3_models.Router(id=router_id, flavor_id=None)
|
||||
metadata = {'port': self.fake_router_port}
|
||||
if pass_subnet:
|
||||
metadata['subnets'] = [self.fake_subnet]
|
||||
else:
|
||||
metadata['subnet_ids'] = [self.fake_subnet['id']]
|
||||
return events.DBEventPayload(self.context, metadata=metadata,
|
||||
states=(router_obj,),
|
||||
resource_id=router_id)
|
||||
|
||||
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
|
||||
def test_add_router_interface(self, func):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id'}
|
||||
func.return_value = self.fake_router_interface_info
|
||||
self.l3_inst.add_router_interface(self.context, router_id,
|
||||
interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id)
|
||||
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
|
||||
**self.fake_router_port_assert)
|
||||
self.l3_inst._nb_ovn.set_lrouter_port_in_lswitch_port.\
|
||||
@ -346,7 +369,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
|
||||
def test_add_router_interface_update_lrouter_port(self, func):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id'}
|
||||
func.return_value = {'id': router_id,
|
||||
'port_id': 'router-port-id',
|
||||
'subnet_id': 'subnet-id1',
|
||||
@ -366,8 +388,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'mac_address': 'aa:aa:aa:aa:aa:aa',
|
||||
'network_id': 'network-id1'}
|
||||
fake_rtr_intf_networks = ['2001:db8::1/24', '2001:dba::1/24']
|
||||
self.l3_inst.add_router_interface(self.context, router_id,
|
||||
interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id)
|
||||
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
called_args_dict = (
|
||||
self.l3_inst._nb_ovn.update_lrouter_port.call_args_list[0][1])
|
||||
|
||||
@ -382,12 +406,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
|
||||
def test_remove_router_interface(self):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id'}
|
||||
self.get_port.side_effect = n_exc.PortNotFound(
|
||||
port_id='router-port-id')
|
||||
|
||||
self.l3_inst.remove_router_interface(
|
||||
self.context, router_id, interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id,
|
||||
pass_subnet=False)
|
||||
self.ovn_drv._process_remove_router_interface(
|
||||
resources.ROUTER_INTERFACE, events.AFTER_DELETE, self, payload)
|
||||
|
||||
self.l3_inst._nb_ovn.lrp_del.assert_called_once_with(
|
||||
'lrp-router-port-id', 'neutron-router-id', if_exists=True)
|
||||
@ -396,9 +421,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
|
||||
def test_remove_router_interface_update_lrouter_port(self):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id'}
|
||||
self.l3_inst.remove_router_interface(
|
||||
self.context, router_id, interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id,
|
||||
pass_subnet=False)
|
||||
self.ovn_drv._process_remove_router_interface(
|
||||
resources.ROUTER_INTERFACE, events.AFTER_DELETE, self, payload)
|
||||
|
||||
self.l3_inst._nb_ovn.update_lrouter_port.assert_called_once_with(
|
||||
if_exists=False, name='lrp-router-port-id',
|
||||
@ -413,14 +439,15 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
|
||||
def test_remove_router_interface_router_not_found(self):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id'}
|
||||
self.get_port.side_effect = n_exc.PortNotFound(
|
||||
port_id='router-port-id')
|
||||
self.get_router.side_effect = l3_exc.RouterNotFound(
|
||||
router_id='router-id')
|
||||
|
||||
self.l3_inst.remove_router_interface(
|
||||
self.context, router_id, interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id,
|
||||
pass_subnet=False)
|
||||
self.ovn_drv._process_remove_router_interface(
|
||||
resources.ROUTER_INTERFACE, events.AFTER_DELETE, self, payload)
|
||||
|
||||
self.get_router.assert_called_once_with(self.context, 'router-id')
|
||||
self.l3_inst._nb_ovn.lrp_del.assert_called_once_with(
|
||||
@ -433,31 +460,40 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb'
|
||||
'.ovn_client.OVNClient._get_v4_network_of_all_router_ports')
|
||||
def test_update_router_admin_state_change(self, get_rps, func):
|
||||
router_id = 'router-id'
|
||||
new_router = self.fake_router.copy()
|
||||
updated_data = {'admin_state_up': True}
|
||||
new_router.update(updated_data)
|
||||
func.return_value = new_router
|
||||
self.l3_inst.update_router(self.context, router_id,
|
||||
{'router': updated_data})
|
||||
payload = self._create_payload_for_router_update(self.fake_router,
|
||||
new_router)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with(
|
||||
'neutron-router-id', enabled=True, external_ids={
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router',
|
||||
ovn_const.OVN_AZ_HINTS_EXT_ID_KEY: ''})
|
||||
|
||||
def _create_payload_for_router_update(self, original, updated):
|
||||
return events.DBEventPayload(self.context,
|
||||
states=(original, updated),
|
||||
resource_id=original['id'])
|
||||
|
||||
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
|
||||
'update_router')
|
||||
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
|
||||
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
|
||||
def test_update_router_name_change(self, get_rps, func):
|
||||
router_id = 'router-id'
|
||||
new_router = self.fake_router.copy()
|
||||
updated_data = {'name': 'test'}
|
||||
new_router.update(updated_data)
|
||||
func.return_value = new_router
|
||||
self.l3_inst.update_router(self.context, router_id,
|
||||
{'router': updated_data})
|
||||
payload = self._create_payload_for_router_update(self.fake_router,
|
||||
new_router)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
self.l3_inst._nb_ovn.update_lrouter.assert_called_once_with(
|
||||
'neutron-router-id', enabled=False,
|
||||
external_ids={ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'test',
|
||||
@ -492,7 +528,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
|
||||
def test_update_router_static_route_change(self, get_rps, func,
|
||||
mock_routes):
|
||||
router_id = 'router-id'
|
||||
get_rps.return_value = [{'device_id': '',
|
||||
'device_owner': 'network:router_interface',
|
||||
'mac_address': 'aa:aa:aa:aa:aa:aa',
|
||||
@ -506,8 +541,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'nexthop': '10.0.0.3'}]}
|
||||
new_router.update(updated_data)
|
||||
func.return_value = new_router
|
||||
self.l3_inst.update_router(self.context, router_id,
|
||||
{'router': updated_data})
|
||||
payload = self._create_payload_for_router_update(self.fake_router,
|
||||
new_router)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
||||
'neutron-router-id',
|
||||
ip_prefix='2.2.2.0/24', nexthop='10.0.0.3')
|
||||
@ -522,7 +560,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
|
||||
def test_update_router_static_route_clear(self, get_rps, func,
|
||||
mock_routes):
|
||||
router_id = 'router-id'
|
||||
get_rps.return_value = [{'device_id': '',
|
||||
'device_owner': 'network:router_interface',
|
||||
'mac_address': 'aa:aa:aa:aa:aa:aa',
|
||||
@ -535,8 +572,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
updated_data = {'routes': []}
|
||||
new_router.update(updated_data)
|
||||
func.return_value = new_router
|
||||
self.l3_inst.update_router(self.context, router_id,
|
||||
{'router': updated_data})
|
||||
payload = self._create_payload_for_router_update(self.fake_router,
|
||||
new_router)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
self.l3_inst._nb_ovn.add_static_route.assert_not_called()
|
||||
self.l3_inst._nb_ovn.delete_static_route.assert_called_once_with(
|
||||
'neutron-router-id',
|
||||
@ -546,12 +586,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'ovn_client.OVNClient._get_v4_network_of_all_router_ports')
|
||||
def test_create_router_with_ext_gw(self, get_rps):
|
||||
self.l3_inst._nb_ovn.is_col_present.return_value = True
|
||||
router = {'router': {'name': 'router'}}
|
||||
self.get_subnet.return_value = self.fake_ext_subnet
|
||||
self.get_port.return_value = self.fake_ext_gw_port
|
||||
get_rps.return_value = self.fake_ext_subnet['cidr']
|
||||
|
||||
self.l3_inst.create_router(self.context, router)
|
||||
payload = events.DBEventPayload(self.context,
|
||||
states=(self.fake_router_with_ext_gw,))
|
||||
self.ovn_drv._process_router_create(resources.ROUTER,
|
||||
events.AFTER_CREATE, self, payload)
|
||||
|
||||
external_ids = {ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY: 'router',
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
@ -596,7 +638,12 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
fake_resources.FakeOVNRouter.from_neutron_router(
|
||||
self.fake_router_with_ext_gw))
|
||||
|
||||
self.l3_inst.delete_router(self.context, 'router-id')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_router_with_ext_gw,),
|
||||
resource_id=self.fake_router_with_ext_gw['id'])
|
||||
self.ovn_drv._process_router_delete(resources.ROUTER,
|
||||
events.AFTER_DELETE,
|
||||
self, payload)
|
||||
|
||||
self.l3_inst._nb_ovn.delete_lrouter.assert_called_once_with(
|
||||
'neutron-router-id')
|
||||
@ -606,12 +653,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
|
||||
def test_add_router_interface_with_gateway_set(self, ari, grps):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id'}
|
||||
ari.return_value = self.fake_router_interface_info
|
||||
self.get_router.return_value = self.fake_router_with_ext_gw
|
||||
|
||||
self.l3_inst.add_router_interface(self.context, router_id,
|
||||
interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id)
|
||||
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
|
||||
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
|
||||
**self.fake_router_port_assert)
|
||||
@ -632,14 +680,15 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
def test_add_router_interface_with_gateway_set_and_snat_disabled(
|
||||
self, ari, grps):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id'}
|
||||
ari.return_value = self.fake_router_interface_info
|
||||
get_router = self.fake_router_with_ext_gw
|
||||
get_router['external_gateway_info']['enable_snat'] = False
|
||||
self.get_router.return_value = get_router
|
||||
|
||||
self.l3_inst.add_router_interface(self.context, router_id,
|
||||
interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id)
|
||||
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
|
||||
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
|
||||
**self.fake_router_port_assert)
|
||||
@ -655,7 +704,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.add_router_interface')
|
||||
def test_add_router_interface_vlan_network(self, ari, grps, gn):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id'}
|
||||
ari.return_value = self.fake_router_interface_info
|
||||
self.get_router.return_value = self.fake_router_with_ext_gw
|
||||
|
||||
@ -664,8 +712,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
fake_network_vlan[pnet.NETWORK_TYPE] = constants.TYPE_VLAN
|
||||
gn.return_value = fake_network_vlan
|
||||
|
||||
self.l3_inst.add_router_interface(self.context, router_id,
|
||||
interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id)
|
||||
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
|
||||
# Make sure that the "reside-on-redirect-chassis" option was
|
||||
# set to the new router port
|
||||
@ -689,13 +739,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
|
||||
def test_remove_router_interface_with_gateway_set(self):
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id',
|
||||
'subnet_id': 'subnet-id'}
|
||||
self.get_router.return_value = self.fake_router_with_ext_gw
|
||||
self.get_port.side_effect = n_exc.PortNotFound(
|
||||
port_id='router-port-id')
|
||||
self.l3_inst.remove_router_interface(
|
||||
self.context, router_id, interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id,
|
||||
pass_subnet=False)
|
||||
self.ovn_drv._process_remove_router_interface(
|
||||
resources.ROUTER_INTERFACE, events.AFTER_DELETE, self, payload)
|
||||
nb_ovn = self.l3_inst._nb_ovn
|
||||
nb_ovn.lrp_del.assert_called_once_with(
|
||||
'lrp-router-port-id', 'neutron-router-id', if_exists=True)
|
||||
@ -711,15 +761,17 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'update_router')
|
||||
def test_update_router_with_ext_gw(self, ur, grps):
|
||||
self.l3_inst._nb_ovn.is_col_present.return_value = True
|
||||
router = {'router': {'name': 'router'}}
|
||||
self.get_router.return_value = self.fake_router_without_ext_gw
|
||||
ur.return_value = self.fake_router_with_ext_gw
|
||||
self.get_subnet.side_effect = lambda ctx, sid: {
|
||||
'ext-subnet-id': self.fake_ext_subnet}.get(sid, self.fake_subnet)
|
||||
self.get_port.return_value = self.fake_ext_gw_port
|
||||
grps.return_value = self.fake_router_ports
|
||||
|
||||
self.l3_inst.update_router(self.context, 'router-id', router)
|
||||
payload = self._create_payload_for_router_update(
|
||||
self.fake_router_without_ext_gw, self.fake_router_with_ext_gw)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
|
||||
self.l3_inst._nb_ovn.add_lrouter_port.assert_called_once_with(
|
||||
**self.fake_ext_gw_port_assert)
|
||||
@ -748,7 +800,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
grps, mock_get_gw):
|
||||
self.l3_inst._nb_ovn.is_col_present.return_value = True
|
||||
mock_get_gw.return_value = [mock.sentinel.GwRoute]
|
||||
router = {'router': {'name': 'router'}}
|
||||
fake_old_ext_subnet = {'id': 'old-ext-subnet-id',
|
||||
'ip_version': 4,
|
||||
'cidr': '192.168.2.0/24',
|
||||
@ -768,7 +819,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.get_port.return_value = self.fake_ext_gw_port
|
||||
grps.return_value = self.fake_router_ports
|
||||
|
||||
self.l3_inst.update_router(self.context, 'router-id', router)
|
||||
payload = self._create_payload_for_router_update(
|
||||
self.get_router.return_value, self.fake_router_with_ext_gw)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
|
||||
# Check deleting old router gateway
|
||||
self.l3_inst._nb_ovn.delete_lrouter_ext_gw.assert_called_once_with(
|
||||
@ -805,7 +860,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
grps, mock_get_gw):
|
||||
self.l3_inst._nb_ovn.is_col_present.return_value = True
|
||||
mock_get_gw.return_value = [mock.sentinel.GwRoute]
|
||||
router = {'router': {'name': 'router'}}
|
||||
# Old gateway info with same subnet and different ip address
|
||||
gr_value = copy.deepcopy(self.fake_router_with_ext_gw)
|
||||
gr_value['external_gateway_info'][
|
||||
@ -818,7 +872,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.get_port.return_value = self.fake_ext_gw_port
|
||||
grps.return_value = self.fake_router_ports
|
||||
|
||||
self.l3_inst.update_router(self.context, 'router-id', router)
|
||||
payload = self._create_payload_for_router_update(
|
||||
gr_value, self.fake_router_with_ext_gw)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
|
||||
# Check deleting old router gateway
|
||||
self.l3_inst._nb_ovn.delete_lrouter_ext_gw.assert_called_once_with(
|
||||
@ -868,8 +926,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'update_router')
|
||||
def test_update_router_with_ext_gw_and_disabled_snat(self, ur, grps):
|
||||
self.l3_inst._nb_ovn.is_col_present.return_value = True
|
||||
router = {'router': {'name': 'router'}}
|
||||
self.get_router.return_value = self.fake_router_without_ext_gw
|
||||
ur.return_value = self.fake_router_with_ext_gw
|
||||
ur.return_value['external_gateway_info']['enable_snat'] = False
|
||||
self.get_subnet.side_effect = lambda ctx, sid: {
|
||||
@ -877,7 +933,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.get_port.return_value = self.fake_ext_gw_port
|
||||
grps.return_value = self.fake_router_ports
|
||||
|
||||
self.l3_inst.update_router(self.context, 'router-id', router)
|
||||
payload = self._create_payload_for_router_update(
|
||||
self.fake_router_without_ext_gw, self.fake_router_with_ext_gw)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
|
||||
# Need not check lsp and lrp here, it has been tested in other cases
|
||||
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
||||
@ -892,7 +952,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
@mock.patch('neutron.db.extraroute_db.ExtraRoute_dbonly_mixin.'
|
||||
'update_router')
|
||||
def test_enable_snat(self, ur, grps):
|
||||
router = {'router': {'name': 'router'}}
|
||||
gr_value = copy.deepcopy(self.fake_router_with_ext_gw)
|
||||
gr_value['external_gateway_info']['enable_snat'] = False
|
||||
self.get_router.return_value = gr_value
|
||||
@ -905,7 +964,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.get_port.return_value = self.fake_ext_gw_port
|
||||
grps.return_value = self.fake_router_ports
|
||||
|
||||
self.l3_inst.update_router(self.context, 'router-id', router)
|
||||
payload = self._create_payload_for_router_update(
|
||||
self.fake_router_with_ext_gw, ur.return_value)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
|
||||
self.l3_inst._nb_ovn.delete_static_route.assert_not_called()
|
||||
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
|
||||
@ -927,7 +990,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
mock_get_gw.return_value = [mock.sentinel.GwRoute]
|
||||
mock_snats.return_value = [mock.sentinel.NAT]
|
||||
mock_ext_ips.return_value = False
|
||||
router = {'router': {'name': 'router'}}
|
||||
self.get_router.return_value = self.fake_router_with_ext_gw
|
||||
ur.return_value = copy.deepcopy(self.fake_router_with_ext_gw)
|
||||
ur.return_value['external_gateway_info']['enable_snat'] = False
|
||||
@ -936,7 +998,11 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.get_port.return_value = self.fake_ext_gw_port
|
||||
grps.return_value = self.fake_router_ports
|
||||
|
||||
self.l3_inst.update_router(self.context, 'router-id', router)
|
||||
payload = self._create_payload_for_router_update(
|
||||
self.fake_router_with_ext_gw, ur.return_value)
|
||||
self.ovn_drv._process_router_update(resources.ROUTER,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
|
||||
nb_ovn = self.l3_inst._nb_ovn
|
||||
nb_ovn.delete_static_route.assert_not_called()
|
||||
@ -949,7 +1015,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
def test_create_floatingip(self):
|
||||
self.l3_inst._nb_ovn.is_col_present.return_value = True
|
||||
self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
|
||||
self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
expected_ext_ids = {
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
@ -975,7 +1047,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
|
||||
config.cfg.CONF.set_override(
|
||||
'enable_distributed_floating_ip', True, group='ovn')
|
||||
self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
expected_ext_ids = {
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
@ -1003,7 +1081,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self._get_floatingip.return_value = {'floating_port_id': 'fip-port-id'}
|
||||
config.cfg.CONF.set_override(
|
||||
'enable_distributed_floating_ip', True, group='ovn')
|
||||
self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
expected_ext_ids = {
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
@ -1026,7 +1110,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.l3_inst._nb_ovn.get_lrouter_nat_rules.return_value = [
|
||||
{'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.6',
|
||||
'type': 'dnat_and_snat', 'uuid': 'uuid1'}]
|
||||
self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
expected_ext_ids = {
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: '1',
|
||||
@ -1051,7 +1141,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.l3_inst._nb_ovn.get_lrouter_nat_rules.return_value = [
|
||||
{'external_ip': '192.168.0.10', 'logical_ip': '10.0.0.0/24',
|
||||
'type': 'snat', 'uuid': 'uuid1'}]
|
||||
self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
self.l3_inst._nb_ovn.set_nat_rule_in_lrouter.assert_not_called()
|
||||
expected_ext_ids = {
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip['id'],
|
||||
@ -1075,7 +1171,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
foo_lport = fake_resources.FakeOvsdbRow.create_one_ovsdb_row()
|
||||
foo_lport.uuid = 'foo-port'
|
||||
self.l3_inst._nb_ovn.get_lswitch_port.return_value = foo_lport
|
||||
self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
calls = [mock.call(
|
||||
'Logical_Switch_Port',
|
||||
'foo-port',
|
||||
@ -1092,7 +1194,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.l3_inst._nb_ovn.is_col_present.return_value = True
|
||||
self.l3_inst._nb_ovn.lookup.return_value = self.lb_network
|
||||
self.l3_inst._nb_ovn.get_lswitch_port.return_value = self.member_lsp
|
||||
fip = self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
fip = self.fake_floating_ip
|
||||
# Validate that there is no external_mac and logical_port while
|
||||
# setting the NAT entry.
|
||||
expected_ext_ids = {
|
||||
@ -1121,7 +1230,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
[self.fake_ovn_nat_rule],
|
||||
]
|
||||
self.l3_inst._nb_ovn.lookup.return_value = self.lb_network
|
||||
fip = self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
fip = self.fake_floating_ip
|
||||
expected_ext_ids = {
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: fip['id'],
|
||||
ovn_const.OVN_REV_NUM_EXT_ID_KEY: mock.ANY,
|
||||
@ -1160,7 +1276,13 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
_nb_ovn.get_lrouter_port.return_value = lrp
|
||||
self.l3_inst.get_router.return_value = self.fake_router_with_ext_gw
|
||||
|
||||
self.l3_inst.create_floatingip(self.context, 'floatingip')
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(self.fake_floating_ip,),
|
||||
resource_id=self.fake_floating_ip['id'],
|
||||
request_body={'floatingip': self.fake_floating_ip})
|
||||
self.ovn_drv._process_floatingip_create(resources.FLOATING_IP,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
_nb_ovn.set_nat_rule_in_lrouter.assert_not_called()
|
||||
|
||||
expected_ext_ids = {
|
||||
@ -1278,7 +1400,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
nb_ovn.get_floatingip.return_value = (
|
||||
self.fake_ovn_nat_rule)
|
||||
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
|
||||
self.l3_inst.update_floatingip(self.context, 'id', fip)
|
||||
payload = events.DBEventPayload(
|
||||
self.context,
|
||||
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
|
||||
resource_id=self.fake_floating_ip_new['id'],
|
||||
request_body=fip)
|
||||
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
|
||||
'neutron-router-id',
|
||||
type='dnat_and_snat',
|
||||
@ -1311,9 +1440,16 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
nb_ovn.get_floatingip.return_value = (
|
||||
self.fake_ovn_nat_rule)
|
||||
fip = {l3_def.FLOATINGIP: {qos_consts.QOS_POLICY_ID: 'qos_id_1'}}
|
||||
payload = events.DBEventPayload(
|
||||
self.context,
|
||||
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
|
||||
resource_id=self.fake_floating_ip_new['id'],
|
||||
request_body=fip)
|
||||
with mock.patch.object(self.l3_inst._ovn_client._qos_driver,
|
||||
'update_floatingip') as ufip:
|
||||
self.l3_inst.update_floatingip(self.context, 'id', fip)
|
||||
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
|
||||
nb_ovn.add_nat_rule_in_lrouter.assert_not_called()
|
||||
ufip.assert_called_once_with(mock.ANY, self.fake_floating_ip_new)
|
||||
@ -1325,7 +1461,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.fake_floating_ip.update({'fixed_port_id': None})
|
||||
uf.return_value = self.fake_floating_ip_new
|
||||
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
|
||||
self.l3_inst.update_floatingip(self.context, 'id', fip)
|
||||
payload = events.DBEventPayload(
|
||||
self.context,
|
||||
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
|
||||
resource_id=self.fake_floating_ip_new['id'],
|
||||
request_body=fip)
|
||||
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
|
||||
expected_ext_ids = {
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
|
||||
@ -1362,7 +1505,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
config.cfg.CONF.set_override(
|
||||
'enable_distributed_floating_ip', True, group='ovn')
|
||||
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
|
||||
self.l3_inst.update_floatingip(self.context, 'id', fip)
|
||||
payload = events.DBEventPayload(
|
||||
self.context,
|
||||
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
|
||||
resource_id=self.fake_floating_ip_new['id'],
|
||||
request_body=fip)
|
||||
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_not_called()
|
||||
expected_ext_ids = {
|
||||
ovn_const.OVN_FIP_EXT_ID_KEY: self.fake_floating_ip_new['id'],
|
||||
@ -1391,7 +1541,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
self.fake_floating_ip_new.update({'port_id': 'foo'})
|
||||
uf.return_value = self.fake_floating_ip_new
|
||||
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
|
||||
self.l3_inst.update_floatingip(self.context, 'id', fip)
|
||||
payload = events.DBEventPayload(
|
||||
self.context,
|
||||
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
|
||||
resource_id=self.fake_floating_ip_new['id'],
|
||||
request_body=fip)
|
||||
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
|
||||
'neutron-router-id',
|
||||
type='dnat_and_snat',
|
||||
@ -1427,7 +1584,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
'fixed_port_id': 'port_id'})
|
||||
uf.return_value = self.fake_floating_ip_new
|
||||
fip = {l3_def.FLOATINGIP: {'port_id': 'port1'}}
|
||||
self.l3_inst.update_floatingip(self.context, 'id', fip)
|
||||
payload = events.DBEventPayload(
|
||||
self.context,
|
||||
states=(self.fake_floating_ip_new, self.fake_floating_ip_new),
|
||||
resource_id=self.fake_floating_ip_new['id'],
|
||||
request_body=fip)
|
||||
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
|
||||
nb_ovn.delete_nat_rule_in_lrouter.assert_called_once_with(
|
||||
'neutron-router-id',
|
||||
@ -1452,48 +1616,58 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
logical_port='port_id',
|
||||
external_ids=expected_ext_ids)
|
||||
|
||||
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_floatingips')
|
||||
def test_disassociate_floatingips(self, gfs):
|
||||
gfs.return_value = [{'id': 'fip-id1',
|
||||
'floating_ip_address': '192.168.0.10',
|
||||
'router_id': 'router-id',
|
||||
'port_id': 'port_id',
|
||||
'floating_port_id': 'fip-port-id1',
|
||||
'fixed_ip_address': '10.0.0.10',
|
||||
'floating_network_id': 'net1'},
|
||||
{'id': 'fip-id2',
|
||||
'floating_ip_address': '192.167.0.10',
|
||||
'router_id': 'router-id',
|
||||
'port_id': 'port_id',
|
||||
'floating_port_id': 'fip-port-id2',
|
||||
'fixed_ip_address': '10.0.0.11',
|
||||
'floating_network_id': 'net2'}]
|
||||
self.l3_inst.disassociate_floatingips(self.context, 'port_id',
|
||||
do_notify=False)
|
||||
@mock.patch('neutron.services.ovn_l3.plugin.OVNL3RouterPlugin.'
|
||||
'update_floatingip_status')
|
||||
def test_disassociate_floatingips(self, status_upd_mock):
|
||||
old_fips = [{'id': 'fip-id1',
|
||||
'floating_ip_address': '192.168.0.10',
|
||||
'router_id': 'router-id',
|
||||
'port_id': 'port_id',
|
||||
'floating_port_id': 'fip-port-id1',
|
||||
'fixed_ip_address': '10.0.0.10',
|
||||
'floating_network_id': 'net1'},
|
||||
{'id': 'fip-id2',
|
||||
'floating_ip_address': '192.167.0.10',
|
||||
'router_id': 'router-id',
|
||||
'port_id': 'port_id',
|
||||
'floating_port_id': 'fip-port-id2',
|
||||
'fixed_ip_address': '10.0.0.11',
|
||||
'floating_network_id': 'net2'}]
|
||||
for fip in old_fips:
|
||||
payload = events.DBEventPayload(
|
||||
self.context, states=(fip, self.fake_floating_ip_new),
|
||||
resource_id=self.fake_floating_ip_new['id'])
|
||||
self.ovn_drv._process_floatingip_update(resources.FLOATING_IP,
|
||||
events.AFTER_UPDATE,
|
||||
self, payload)
|
||||
|
||||
delete_nat_calls = [mock.call('neutron-router-id',
|
||||
type='dnat_and_snat',
|
||||
logical_ip=fip['fixed_ip_address'],
|
||||
external_ip=fip['floating_ip_address'])
|
||||
for fip in gfs.return_value]
|
||||
for fip in old_fips]
|
||||
self.assertEqual(
|
||||
len(delete_nat_calls),
|
||||
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.call_count)
|
||||
self.l3_inst._nb_ovn.delete_nat_rule_in_lrouter.assert_has_calls(
|
||||
delete_nat_calls, any_order=True)
|
||||
self.assertEqual(len(delete_nat_calls), status_upd_mock.call_count)
|
||||
|
||||
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
|
||||
@mock.patch('neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.'
|
||||
'ovn_client.OVNClient.update_router_port')
|
||||
def test_port_update_postcommit(self, update_rp_mock):
|
||||
def test_port_update_postcommit(self, update_rp_mock, gr):
|
||||
context = 'fake_context'
|
||||
port = {'device_owner': 'foo'}
|
||||
port = {'device_owner': 'foo', 'device_id': 'id'}
|
||||
gr.return_value = {'flavor_id': None}
|
||||
self.l3_inst._port_update(resources.PORT, events.AFTER_UPDATE, None,
|
||||
payload=events.DBEventPayload(
|
||||
context,
|
||||
states=(port,)))
|
||||
update_rp_mock.assert_not_called()
|
||||
|
||||
port = {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF}
|
||||
port = {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF,
|
||||
'device_id': 'router_id'}
|
||||
self.l3_inst._port_update(resources.PORT, events.AFTER_UPDATE, None,
|
||||
payload=events.DBEventPayload(
|
||||
context,
|
||||
@ -1503,11 +1677,14 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
port,
|
||||
if_exists=True)
|
||||
|
||||
def test_port_update_before_update_router_port_without_ip(self):
|
||||
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
|
||||
def test_port_update_before_update_router_port_without_ip(self, gr):
|
||||
context = 'fake_context'
|
||||
port = {'device_owner': constants.DEVICE_OWNER_ROUTER_INTF,
|
||||
'fixed_ips': [],
|
||||
'id': 'port-id'}
|
||||
'id': 'port-id',
|
||||
'device_id': 'router_id'}
|
||||
gr.return_value = {'flavor_id': None}
|
||||
self.assertRaises(
|
||||
n_exc.ServicePortInUse,
|
||||
self.l3_inst._port_update,
|
||||
@ -1674,8 +1851,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
gn.return_value = prov_net
|
||||
gns.return_value = [self.fake_network]
|
||||
|
||||
self.l3_inst.add_router_interface(self.context, router_id,
|
||||
interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id)
|
||||
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
|
||||
# Make sure that the "gateway_mtu" option was set to the router port
|
||||
fake_router_port_assert = self.fake_router_port_assert
|
||||
@ -1708,8 +1887,6 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
config.cfg.CONF.set_override(
|
||||
'ovn_emit_need_to_frag', True, group='ovn')
|
||||
router_id = 'router-id'
|
||||
interface_info = {'port_id': 'router-port-id',
|
||||
'network_id': 'priv-net'}
|
||||
ari.return_value = self.fake_router_interface_info
|
||||
# If we remove the router halfway the return value of
|
||||
# _get_routers_ports will be RouterNotFound
|
||||
@ -1723,8 +1900,10 @@ class TestOVNL3RouterPlugin(test_mech_driver.Ml2PluginV2TestCase):
|
||||
gn.return_value = prov_net
|
||||
gns.return_value = [self.fake_network]
|
||||
|
||||
self.l3_inst.add_router_interface(self.context, router_id,
|
||||
interface_info)
|
||||
payload = self._create_payload_for_router_interface(router_id)
|
||||
self.ovn_drv._process_add_router_interface(resources.ROUTER_INTERFACE,
|
||||
events.AFTER_CREATE,
|
||||
self, payload)
|
||||
|
||||
# Make sure that the "gateway_mtu" option was set to the router port
|
||||
fake_router_port_assert = self.fake_router_port_assert
|
||||
@ -1882,7 +2061,9 @@ class OVNL3ExtrarouteTests(test_l3_gw.ExtGwModeIntTestCase,
|
||||
test_floatingip_update_subnet_gateway_disabled(expected_status)
|
||||
|
||||
# Test function _subnet_update of L3 OVN plugin.
|
||||
def test_update_subnet_gateway_for_external_net(self):
|
||||
@mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.get_router')
|
||||
def test_update_subnet_gateway_for_external_net(self, gr):
|
||||
gr.return_value = {'flavor_id': None}
|
||||
super(OVNL3ExtrarouteTests, self). \
|
||||
test_update_subnet_gateway_for_external_net()
|
||||
self.l3_inst._nb_ovn.add_static_route.assert_called_once_with(
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user