Skip to content

Commit 84479e4

Browse files
authored
Merge pull request #251 from stackhpc/upstream/2025.1-2026-01-12
Synchronise 2025.1 with upstream
2 parents 647afe7 + 9d7d55b commit 84479e4

20 files changed

Lines changed: 559 additions & 60 deletions

File tree

doc/source/ovn/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ OVN Driver
1313
ml2ovn_trace.rst
1414
faq/index.rst
1515
ovn_agent.rst
16+
virtual_ips.rst

doc/source/ovn/virtual_ips.rst

Lines changed: 193 additions & 0 deletions
Large diffs are not rendered by default.

neutron/agent/ovn/agent/ovn_neutron_agent.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ def __init__(self, conf):
5858
self._chassis = None
5959
self._chassis_id = None
6060
self._ovn_bridge = None
61-
self.ext_manager_api = ext_mgr.OVNAgentExtensionAPI()
62-
self.ext_manager = ext_mgr.OVNAgentExtensionManager(self._conf)
63-
self.ext_manager.initialize(None, 'ovn', self)
61+
self.ext_manager_api = None
62+
self.ext_manager = None
6463

6564
def __getitem__(self, name):
6665
"""Return the named extension objet from ``self.ext_manager``"""
@@ -159,7 +158,22 @@ def update_neutron_sb_cfg_key(self):
159158
'Chassis_Private', self.chassis,
160159
('external_ids', external_ids)).execute(check_error=True)
161160

161+
def _initialize_ext_manager(self):
162+
"""Initialize the externsion manager and the extension manager API.
163+
164+
This method must be called once, outside the ``__init__`` method and
165+
at the beginning of the ``start`` method.
166+
"""
167+
if not self.ext_manager:
168+
self.ext_manager_api = ext_mgr.OVNAgentExtensionAPI()
169+
self.ext_manager = ext_mgr.OVNAgentExtensionManager(self._conf)
170+
self.ext_manager.initialize(None, 'ovn', self)
171+
162172
def start(self):
173+
# This must be the first operation in the `start` method.
174+
self._initialize_ext_manager()
175+
176+
# Extension manager configuration.
163177
self.ext_manager_api.ovs_idl = self._load_ovs_idl()
164178
self.load_config()
165179
# Before executing "_load_sb_idl", is is needed to execute

neutron/agent/ovn/metadata/agent.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,21 @@ def _update_chassis_private_config(self):
431431
'Chassis_Private', self.chassis,
432432
('external_ids', external_ids)).execute(check_error=True)
433433

434+
def _update_metadata_sb_cfg_key(self):
435+
"""Update the Chassis_Private nb_cfg information in external_ids
436+
437+
This method should be called once the Metadata Agent has been
438+
registered (method ``register_metadata_agent`` has been called) and
439+
the corresponding Chassis_Private register has been created/updated
440+
and chassis private config has been updated.
441+
"""
442+
nb_cfg = self.sb_idl.db_get('Chassis_Private',
443+
self.chassis, 'nb_cfg').execute()
444+
external_ids = {ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: str(nb_cfg)}
445+
self.sb_idl.db_set(
446+
'Chassis_Private', self.chassis,
447+
('external_ids', external_ids)).execute(check_error=True)
448+
434449
@_sync_lock
435450
def resync(self):
436451
"""Resync the agent.
@@ -439,6 +454,7 @@ def resync(self):
439454
"""
440455
self._load_config()
441456
self._update_chassis_private_config()
457+
self._update_metadata_sb_cfg_key()
442458
self.sync()
443459

444460
def start(self):
@@ -474,6 +490,7 @@ def start(self):
474490
# Register the agent with its corresponding Chassis
475491
self.register_metadata_agent()
476492
self._update_chassis_private_config()
493+
self._update_metadata_sb_cfg_key()
477494

478495
self._proxy.wait()
479496

neutron/common/metadata.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
PROXY_SERVICE_NAME = 'haproxy'
3434
PROXY_SERVICE_CMD = 'haproxy'
3535

36-
CONTENT_ENCODERS = ('gzip', 'deflate')
36+
CONTENT_ENCODERS = {
37+
'gzip': b'\x1f\x8b\x08',
38+
'deflate': b'\x1f\x8b\x08'
39+
}
3740

3841

3942
class InvalidUserOrGroupException(Exception):
@@ -159,15 +162,21 @@ class MetadataProxyHandlerBaseSocketServer(
159162
metaclass=abc.ABCMeta):
160163
@staticmethod
161164
def _http_response(http_response, request):
165+
headerlist = list(http_response.headers.items())
166+
# We detect if content is compressed by magic signature,
167+
# when `content-encoding` is not present.
168+
if not http_response.headers.get('content-encoding'):
169+
if http_response.content[:3] == CONTENT_ENCODERS['gzip']:
170+
headerlist.append(('content-encoding', 'gzip'))
171+
162172
_res = webob.Response(
163173
body=http_response.content,
164174
status=http_response.status_code,
165-
content_type=http_response.headers['content-type'],
166-
charset=http_response.encoding)
175+
headerlist=headerlist)
167176
# The content of the response is decoded depending on the
168177
# "Context-Enconding" header, if present. The operation is limited to
169178
# ("gzip", "deflate"), as is in the ``webob.response.Response`` class.
170-
if _res.content_encoding in CONTENT_ENCODERS:
179+
if _res.content_encoding in CONTENT_ENCODERS.keys():
171180
_res.decode_content()
172181

173182
# NOTE(ralonsoh): there should be a better way to format the HTTP

neutron/common/ovn/extensions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from neutron_lib.api.definitions import qinq
7171
from neutron_lib.api.definitions import qos
7272
from neutron_lib.api.definitions import qos_bw_limit_direction
73+
from neutron_lib.api.definitions import qos_bw_minimum_ingress
7374
from neutron_lib.api.definitions import qos_default
7475
from neutron_lib.api.definitions import qos_gateway_ip
7576
from neutron_lib.api.definitions import qos_rule_type_details
@@ -171,6 +172,7 @@
171172
qinq.ALIAS,
172173
qos.ALIAS,
173174
qos_bw_limit_direction.ALIAS,
175+
qos_bw_minimum_ingress.ALIAS,
174176
qos_default.ALIAS,
175177
qos_rule_type_details.ALIAS,
176178
qos_rule_type_filter.ALIAS,

neutron/pecan_wsgi/hooks/policy_enforcement.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from neutron_lib import constants as const
1717
from oslo_log import log as logging
1818
from oslo_policy import policy as oslo_policy
19+
from oslo_serialization import jsonutils
1920
from oslo_utils import excutils
2021
from pecan import hooks
2122
import webob
@@ -190,6 +191,14 @@ def after(self, state):
190191
# we have to set the status_code here to prevent the catch_errors
191192
# middleware from turning this into a 500.
192193
state.response.status_code = 404
194+
# replace the original body on NotFound body
195+
error_message = {
196+
'type': 'HTTPNotFound',
197+
'message': 'The resource could not be found.',
198+
'detail': ''
199+
}
200+
state.response.text = jsonutils.dumps(error_message)
201+
state.response.content_type = 'application/json'
193202
return
194203

195204
if is_single:

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,14 +655,22 @@ def _get_common_ips(ip_addresses, ip_networks):
655655
common_ips.add(str(ip_address))
656656
return common_ips
657657

658+
# NOTE(slaweq): We can safely ignore any CIDR larger than /32 (for
659+
# IPv4) or /128 (for IPv6) in the allowed_address_pairs, since such
660+
# CIDRs cannot be set as a Virtual IP in OVN.
661+
# Only /32 and /128 CIDRs are allowed to be set as Virtual IPs in OVN.
662+
address_pairs_to_check = [
663+
ip_net for ip_net in port_allowed_address_pairs_ip_addresses
664+
if ip_net.size == 1]
665+
658666
for distributed_port in distributed_ports:
659667
distributed_port_ip_addresses = [
660668
netaddr.IPAddress(fixed_ip['ip_address']) for fixed_ip in
661669
distributed_port.get('fixed_ips', [])]
662670

663671
common_ips = _get_common_ips(
664672
distributed_port_ip_addresses,
665-
port_allowed_address_pairs_ip_addresses)
673+
address_pairs_to_check)
666674

667675
if common_ips:
668676
err_msg = (

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

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def _get_port_dhcp_options(self, port, ip_version):
267267
wait=tenacity.wait_random(min=2, max=3),
268268
stop=tenacity.stop_after_attempt(3),
269269
reraise=True)
270-
def _wait_for_port_bindings_host(self, context, port_id):
270+
def _wait_for_active_port_bindings_host(self, context, port_id):
271271
db_port = ml2_db.get_port(context, port_id)
272272
# This is already checked previously but, just to stay on
273273
# the safe side in case the port is deleted mid-operation
@@ -280,11 +280,18 @@ def _wait_for_port_bindings_host(self, context, port_id):
280280
_('No port bindings information found for '
281281
'port %s') % port_id)
282282

283-
if not db_port.port_bindings[0].host:
283+
active_binding = p_utils.get_port_binding_by_status_and_host(
284+
db_port.port_bindings, const.ACTIVE)
285+
if not active_binding:
286+
raise RuntimeError(
287+
_('No active port bindings information found for '
288+
'port %s') % port_id)
289+
290+
if not active_binding.host:
284291
raise RuntimeError(
285292
_('No hosting information found for port %s') % port_id)
286293

287-
return db_port
294+
return active_binding
288295

289296
def update_lsp_host_info(self, context, db_port, up=True):
290297
"""Update the binding hosting information for the LSP.
@@ -307,24 +314,29 @@ def update_lsp_host_info(self, context, db_port, up=True):
307314
if up:
308315
if not port_up:
309316
LOG.warning('Logical_Switch_Port %s host information not '
310-
'updated, the port state is down')
317+
'updated, the port state is down', db_port.id)
311318
return
312319

313320
if not db_port.port_bindings:
314321
return
315322

316-
if not db_port.port_bindings[0].host:
323+
# There could be more than one port binding present, we need
324+
# to find the active one
325+
active_binding = p_utils.get_port_binding_by_status_and_host(
326+
db_port.port_bindings, const.ACTIVE)
327+
328+
if not active_binding or not active_binding.host:
317329
# NOTE(lucasgomes): There might be a sync issue between
318330
# the moment that this port was fetched from the database
319331
# and the hosting information being set, retry a few times
320332
try:
321-
db_port = self._wait_for_port_bindings_host(
333+
active_binding = self._wait_for_active_port_bindings_host(
322334
context, db_port.id)
323335
except RuntimeError as e:
324336
LOG.warning(e)
325337
return
326338

327-
host = db_port.port_bindings[0].host
339+
host = active_binding.host
328340
ext_ids = ('external_ids',
329341
{ovn_const.OVN_HOST_ID_EXT_ID_KEY: host})
330342
cmd.append(
@@ -333,7 +345,7 @@ def update_lsp_host_info(self, context, db_port, up=True):
333345
else:
334346
if port_up:
335347
LOG.warning('Logical_Switch_Port %s host information not '
336-
'removed, the port state is up')
348+
'removed, the port state is up', db_port.id)
337349
return
338350

339351
cmd.append(

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,33 @@ def sync_acls(self, ctx):
354354
add_acls.append(na)
355355
n_index += 1
356356

357+
# Check any ACLs we found to add against existing ACLs, ignoring the
358+
# SG rule ID key. This eliminates any false-positives where the
359+
# normalized cidr for two SG rules is the same value, since there
360+
# will only be a single ACL that matches exactly with the SG rule ID.
361+
if add_acls:
362+
def copy_acl_rem_id_key(acl):
363+
acl_copy = acl.copy()
364+
del acl_copy[ovn_const.OVN_SG_RULE_EXT_ID_KEY]
365+
return acl_copy
366+
367+
add_rem_acls = []
368+
# Make a list of non-default rule ACLs (they have a security group
369+
# rule id). See ovn_default_acls code/comment above for more info.
370+
nd_ovn_acls = [copy_acl_rem_id_key(oa) for oa in ovn_acls
371+
if ovn_const.OVN_SG_RULE_EXT_ID_KEY in oa]
372+
# We must copy here since we need to keep the original
373+
# 'add_acl' intact for removal
374+
for add_acl in add_acls:
375+
add_acl_copy = copy_acl_rem_id_key(add_acl)
376+
if add_acl_copy in nd_ovn_acls:
377+
add_rem_acls.append(add_acl)
378+
379+
# Remove any of the false-positive ACLs
380+
LOG.warning('False-positive ACLs to remove: (%s)', add_rem_acls)
381+
for add_rem in add_rem_acls:
382+
add_acls.remove(add_rem)
383+
357384
if n_index < neutron_num:
358385
# We didn't find the OVN ACLs matching the Neutron ACLs
359386
# in "ovn_acls" and we are just adding the pending Neutron ACLs.

0 commit comments

Comments
 (0)