Skip to content

Commit fc170a5

Browse files
authored
Merge pull request #123 from stackhpc/upstream/2023.1-2024-02-19
Synchronise 2023.1 with upstream
2 parents dbe9c68 + 69f64a1 commit fc170a5

File tree

18 files changed

+418
-19
lines changed

18 files changed

+418
-19
lines changed

doc/source/ovn/gaps.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ at [1]_.
5656

5757
The NDP proxy functionality for IPv6 addresses is not supported by OVN.
5858

59+
* Floating IP Port Forwarding in provider networks and with distributed routing
60+
61+
Currently, when provider network types like ``vlan`` or ``flat`` are plugged
62+
to a router as internal networks while the ``enable_distributed_floating_ip``
63+
configuration option is enabled, Floating IP port forwardings
64+
which are using such router will not work properly.
65+
Due to an incompatible setting of the router to make traffic in the vlan/flat
66+
networks to be distributed but port forwardings are always centralized in
67+
ML2/OVN backend.
68+
This is being reported in [9]_.
69+
5970
References
6071
----------
6172

@@ -67,3 +78,4 @@ References
6778
.. [6] https://bugs.launchpad.net/neutron/+bug/1926515
6879
.. [7] https://review.opendev.org/c/openstack/neutron/+/788594
6980
.. [8] https://docs.openstack.org/neutron/latest/admin/config-dns-res.html
81+
.. [9] https://bugs.launchpad.net/neutron/+bug/2028846

neutron/cmd/upgrade_checks/checks.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727

2828
from neutron._i18n import _
2929
from neutron.cmd.upgrade_checks import base
30+
from neutron.common.ovn import exceptions as ovn_exc
31+
from neutron.common.ovn import utils as ovn_utils
32+
from neutron.conf.plugins.ml2 import config as ml2_conf
33+
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
3034
from neutron.conf import service as conf_service
3135
from neutron.db.extra_dhcp_opt import models as extra_dhcp_opt_models
3236
from neutron.db.models import agent as agent_model
@@ -179,6 +183,8 @@ def get_checks(self):
179183
self.floatingip_inherit_qos_from_network),
180184
(_('Port extra DHCP options check'),
181185
self.extra_dhcp_options_check),
186+
(_('Floating IP Port forwarding and OVN L3 plugin configuration'),
187+
self.ovn_port_forwarding_configuration_check),
182188
]
183189

184190
@staticmethod
@@ -515,3 +521,28 @@ def extra_dhcp_options_check(checker):
515521
upgradecheck.Code.SUCCESS,
516522
_('There are no extra_dhcp_opts with the newline character '
517523
'in the option name or option value.'))
524+
525+
@staticmethod
526+
def ovn_port_forwarding_configuration_check(checker):
527+
ovn_l3_plugin_names = [
528+
'ovn-router',
529+
'neutron.services.ovn_l3.plugin.OVNL3RouterPlugin']
530+
if not any(plugin in ovn_l3_plugin_names
531+
for plugin in cfg.CONF.service_plugins):
532+
return upgradecheck.Result(
533+
upgradecheck.Code.SUCCESS, _('No OVN L3 plugin enabled.'))
534+
535+
ml2_conf.register_ml2_plugin_opts()
536+
ovn_conf.register_opts()
537+
try:
538+
ovn_utils.validate_port_forwarding_configuration()
539+
return upgradecheck.Result(
540+
upgradecheck.Code.SUCCESS,
541+
_('OVN L3 plugin and Port Forwarding configuration are fine.'))
542+
except ovn_exc.InvalidPortForwardingConfiguration:
543+
return upgradecheck.Result(
544+
upgradecheck.Code.WARNING,
545+
_('Neutron configuration is invalid. Port forwardings '
546+
'can not be used with ML2/OVN backend, distributed '
547+
'floating IPs and provider network type(s) used as '
548+
'tenant networks.'))

neutron/common/ovn/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,10 @@ class HashRingIsEmpty(n_exc.NeutronException):
3737
'%(node_count)d nodes were found offline. This should never '
3838
'happen in a normal situation, please check the status '
3939
'of your cluster')
40+
41+
42+
class InvalidPortForwardingConfiguration(n_exc.NeutronException):
43+
message = _('Neutron configuration is invalid. Port forwardings '
44+
'can not be used with ML2/OVN backend, distributed '
45+
'floating IPs and provider network type(s) used as '
46+
'tenant networks.')

neutron/common/ovn/extensions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
from neutron_lib.api.definitions import stateful_security_group
7474
from neutron_lib.api.definitions import subnet_dns_publish_fixed_ip
7575
from neutron_lib.api.definitions import subnet_service_types
76+
from neutron_lib.api.definitions import subnetpool_prefix_ops
7677
from neutron_lib.api.definitions import trunk
7778
from neutron_lib.api.definitions import uplink_status_propagation
7879
from neutron_lib.api.definitions import vlantransparent
@@ -156,6 +157,7 @@
156157
constants.SUBNET_ALLOCATION_EXT_ALIAS,
157158
'standard-attr-tag',
158159
'standard-attr-timestamp',
160+
subnetpool_prefix_ops.ALIAS,
159161
subnet_service_types.ALIAS,
160162
trunk.ALIAS,
161163
seg_def.ALIAS,

neutron/common/ovn/utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,15 @@ def is_gateway_chassis_invalid(chassis_name, gw_chassis,
679679

680680

681681
def is_provider_network(network):
682+
"""Check if given network is provider network
683+
:param network: (str, dict) it can be given as network object or as string
684+
with network ID only. In the latter case, network object
685+
will be loaded from the database
686+
"""
687+
if isinstance(network, str):
688+
ctx = n_context.get_admin_context()
689+
plugin = directory.get_plugin()
690+
network = plugin.get_network(ctx, network)
682691
return network.get(provider_net.PHYSICAL_NETWORK, False)
683692

684693

@@ -1211,3 +1220,20 @@ def get_requested_chassis(requested_chassis):
12111220
# becomes the norm and older versions of OVN are no longer supported
12121221
def is_additional_chassis_supported(idl):
12131222
return idl.is_col_present('Port_Binding', 'additional_chassis')
1223+
1224+
1225+
def validate_port_forwarding_configuration():
1226+
if not ovn_conf.is_ovn_distributed_floating_ip():
1227+
return
1228+
1229+
pf_plugin_names = [
1230+
'port_forwarding',
1231+
'neutron.services.portforwarding.pf_plugin.PortForwardingPlugin']
1232+
if not any(plugin in pf_plugin_names
1233+
for plugin in cfg.CONF.service_plugins):
1234+
return
1235+
1236+
provider_network_types = ['vlan', 'flat']
1237+
if any(net_type in provider_network_types
1238+
for net_type in cfg.CONF.ml2.tenant_network_types):
1239+
raise ovn_exc.InvalidPortForwardingConfiguration()

neutron/db/models/securitygroup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,13 @@ class SecurityGroupRule(standard_attr.HasStandardAttributes, model_base.BASEV2,
101101
port_range_min = sa.Column(sa.Integer)
102102
port_range_max = sa.Column(sa.Integer)
103103
remote_ip_prefix = sa.Column(sa.String(255))
104+
# NOTE(ralonsoh): loading method is temporarily changed to "joined" until
105+
# a proper way to only load the security groups "shared" field, without
106+
# loading the rest of the synthetic fields, is implemented. LP#2052419
107+
# description for more information and context.
104108
security_group = orm.relationship(
105109
SecurityGroup, load_on_pending=True,
106-
backref=orm.backref('rules', cascade='all,delete', lazy='dynamic'),
110+
backref=orm.backref('rules', cascade='all,delete', lazy='joined'),
107111
primaryjoin="SecurityGroup.id==SecurityGroupRule.security_group_id")
108112
source_group = orm.relationship(
109113
SecurityGroup,

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/maintenance.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,41 @@ def create_router_extra_attributes_registers(self):
971971

972972
raise periodics.NeverAgain()
973973

974+
# TODO(slaweq): Remove this in the E cycle (C+2 as it will be next SLURP)
975+
@periodics.periodic(spacing=600, run_immediately=True)
976+
def add_gw_port_info_to_logical_router_port(self):
977+
"""Add info if LRP is connecting internal subnet or ext gateway."""
978+
cmds = []
979+
context = n_context.get_admin_context()
980+
for router in self._ovn_client._l3_plugin.get_routers(context):
981+
ext_gw_networks = [
982+
ext_gw['network_id'] for ext_gw in router['external_gateways']]
983+
rtr_name = 'neutron-{}'.format(router['id'])
984+
ovn_lr = self._nb_idl.get_lrouter(rtr_name)
985+
for lrp in ovn_lr.ports:
986+
if ovn_const.OVN_ROUTER_IS_EXT_GW in lrp.external_ids:
987+
continue
988+
ovn_network_name = lrp.external_ids.get(
989+
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY)
990+
if not ovn_network_name:
991+
continue
992+
network_id = ovn_network_name.replace('neutron-', '')
993+
if not network_id:
994+
continue
995+
is_ext_gw = str(network_id in ext_gw_networks)
996+
external_ids = lrp.external_ids
997+
external_ids[ovn_const.OVN_ROUTER_IS_EXT_GW] = is_ext_gw
998+
cmds.append(
999+
self._nb_idl.update_lrouter_port(
1000+
name=lrp.name,
1001+
external_ids=external_ids))
1002+
1003+
if cmds:
1004+
with self._nb_idl.transaction(check_error=True) as txn:
1005+
for cmd in cmds:
1006+
txn.add(cmd)
1007+
raise periodics.NeverAgain()
1008+
9741009
@periodics.periodic(spacing=600, run_immediately=True)
9751010
def check_router_default_route_empty_dst_ip(self):
9761011
"""Check routers with default route with empty dst-ip (LP: #2002993).

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,10 @@ def _gen_router_port_ext_ids(self, port):
15371537
ovn_const.OVN_SUBNET_EXT_IDS_KEY:
15381538
' '.join(utils.get_port_subnet_ids(port)),
15391539
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY:
1540-
utils.ovn_name(port['network_id'])}
1540+
utils.ovn_name(port['network_id']),
1541+
ovn_const.OVN_ROUTER_IS_EXT_GW:
1542+
str(const.DEVICE_OWNER_ROUTER_GW == port.get('device_owner')),
1543+
}
15411544

15421545
router_id = port.get('device_id')
15431546
if router_id:

neutron/services/portforwarding/drivers/ovn/driver.py

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,21 @@
1010
# License for the specific language governing permissions and limitations
1111
# under the License.
1212

13-
from oslo_log import log
14-
15-
from ovsdbapp.backend.ovs_idl import idlutils
16-
from ovsdbapp import constants as ovsdbapp_const
17-
1813
from neutron_lib.callbacks import events
1914
from neutron_lib.callbacks import registry
2015
from neutron_lib.callbacks import resources
2116
from neutron_lib import constants as const
2217
from neutron_lib.plugins import constants as plugin_constants
2318
from neutron_lib.plugins import directory
19+
from oslo_log import log
20+
from oslo_utils import strutils
21+
from ovsdbapp.backend.ovs_idl import idlutils
22+
from ovsdbapp import constants as ovsdbapp_const
2423

2524
from neutron.common.ovn import constants as ovn_const
25+
from neutron.common.ovn import exceptions as ovn_exc
2626
from neutron.common.ovn import utils as ovn_utils
27+
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
2728
from neutron.db import ovn_revision_numbers_db as db_rev
2829
from neutron import manager
2930
from neutron.objects import port_forwarding as port_forwarding_obj
@@ -130,7 +131,41 @@ def _port_forwarding_created(self, ovn_txn, nb_ovn, pf_obj,
130131
"Switch %s failed as it is not found",
131132
lb_name, ls_name)
132133

134+
def _validate_router_networks(self, nb_ovn, router_id):
135+
if not ovn_conf.is_ovn_distributed_floating_ip():
136+
return
137+
rtr_name = 'neutron-{}'.format(router_id)
138+
ovn_lr = nb_ovn.get_lrouter(rtr_name)
139+
if not ovn_lr:
140+
return
141+
for lrouter_port in ovn_lr.ports:
142+
is_ext_gw = strutils.bool_from_string(
143+
lrouter_port.external_ids.get(ovn_const.OVN_ROUTER_IS_EXT_GW))
144+
if is_ext_gw:
145+
# NOTE(slaweq): This is external gateway port of the router and
146+
# this not needs to be checked
147+
continue
148+
ovn_network_name = lrouter_port.external_ids.get(
149+
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY)
150+
if not ovn_network_name:
151+
continue
152+
network_id = ovn_network_name.replace('neutron-', '')
153+
if not network_id:
154+
continue
155+
if ovn_utils.is_provider_network(network_id):
156+
LOG.warning("Port forwarding configured in the router "
157+
"%(router_id)s will not work properly as "
158+
"distributed floating IPs are enabled "
159+
"and at least one provider network "
160+
"(%(network_id)s) is connected to that router. "
161+
"See bug https://launchpad.net/bugs/2028846 for "
162+
"more details.", {
163+
'router_id': router_id,
164+
'network_id': network_id})
165+
return
166+
133167
def port_forwarding_created(self, ovn_txn, nb_ovn, pf_obj):
168+
self._validate_router_networks(nb_ovn, pf_obj.router_id)
134169
pf_objs = pf_obj.unroll_port_ranges()
135170
is_range = len(pf_objs) > 1
136171
for pf_obj in pf_objs:
@@ -192,10 +227,27 @@ def port_forwarding_deleted(self, ovn_txn, nb_ovn, pf_obj):
192227
class OVNPortForwarding(object):
193228

194229
def __init__(self, l3_plugin):
230+
self._validate_configuration()
195231
self._l3_plugin = l3_plugin
196232
self._pf_plugin_property = None
197233
self._handler = OVNPortForwardingHandler()
198234

235+
def _validate_configuration(self):
236+
"""This method checks if Neutron config is compatible with OVN and PFs.
237+
238+
It stops process in case when provider network types (vlan/flat)
239+
are enabled as tenant networks AND distributed floating IPs are enabled
240+
as this configuration is not working fine with FIP PFs in ML2/OVN case.
241+
"""
242+
try:
243+
ovn_utils.validate_port_forwarding_configuration()
244+
except ovn_exc.InvalidPortForwardingConfiguration:
245+
LOG.warning("Neutron configuration is invalid for port "
246+
"forwardings and ML2/OVN backend. "
247+
"It is not valid to use together provider network "
248+
"types (vlan/flat) as tenant networks, distributed "
249+
"floating IPs and port forwardings.")
250+
199251
@property
200252
def _pf_plugin(self):
201253
if self._pf_plugin_property is None:

neutron/tests/unit/cmd/upgrade_checks/test_checks.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from oslo_upgradecheck.upgradecheck import Code
1919

2020
from neutron.cmd.upgrade_checks import checks
21+
from neutron.common.ovn import exceptions as ovn_exc
22+
from neutron.common.ovn import utils as ovn_utils
2123
from neutron.tests import base
2224

2325

@@ -278,3 +280,25 @@ def test_extra_dhcp_options_check_bad_value(self):
278280
opt_value='bar\nbar')]
279281
result = checks.CoreChecks.extra_dhcp_options_check(mock.ANY)
280282
self.assertEqual(Code.WARNING, result.code)
283+
284+
def test_ovn_port_forwarding_configuration_check_ovn_l3_success(self):
285+
cfg.CONF.set_override("service_plugins", 'ovn-router')
286+
with mock.patch.object(
287+
ovn_utils,
288+
'validate_port_forwarding_configuration') as validate_mock:
289+
result = checks.CoreChecks.ovn_port_forwarding_configuration_check(
290+
mock.ANY)
291+
self.assertEqual(Code.SUCCESS, result.code)
292+
validate_mock.assert_called_once_with()
293+
294+
def test_ovn_port_forwarding_configuration_check_ovn_l3_failure(self):
295+
cfg.CONF.set_override("service_plugins", 'ovn-router')
296+
with mock.patch.object(
297+
ovn_utils,
298+
'validate_port_forwarding_configuration',
299+
side_effect=ovn_exc.InvalidPortForwardingConfiguration
300+
) as validate_mock:
301+
result = checks.CoreChecks.ovn_port_forwarding_configuration_check(
302+
mock.ANY)
303+
self.assertEqual(Code.WARNING, result.code)
304+
validate_mock.assert_called_once_with()

0 commit comments

Comments
 (0)