Version in base suite: 14.0.0-2 Base version: python-oslo.messaging_14.0.0-2 Target version: python-oslo.messaging_14.0.3-0+deb12u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/python-oslo.messaging/python-oslo.messaging_14.0.0-2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/python-oslo.messaging/python-oslo.messaging_14.0.3-0+deb12u1.dsc .gitreview | 1 debian/changelog | 12 debian/patches/CVE-2026-44393_OSSN-0096_Fix_RabbitMQ_TLS_hostname_verification.patch | 361 ++++++++++ debian/patches/Implement_get_rpc_client_function.patch | 324 ++++++++ debian/patches/Support_overriding_class_for_get_rpc_helper_functions.patch | 110 +++ debian/patches/series | 3 oslo_messaging/_drivers/amqpdriver.py | 2 oslo_messaging/_drivers/impl_rabbit.py | 8 oslo_messaging/_utils.py | 3 oslo_messaging/notify/notifier.py | 52 + oslo_messaging/tests/notify/test_listener.py | 34 oslo_messaging/tests/notify/test_notifier.py | 166 ++++ oslo_messaging/tests/test_config_opts_proxy.py | 2 releasenotes/notes/bug-1993149-e8b231791b65e938.yaml | 9 tox.ini | 2 15 files changed, 1059 insertions(+), 30 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp0v57w7zr/python-oslo.messaging_14.0.0-2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp0v57w7zr/python-oslo.messaging_14.0.3-0+deb12u1.dsc: no acceptable signature found diff -Nru python-oslo.messaging-14.0.0/.gitreview python-oslo.messaging-14.0.3/.gitreview --- python-oslo.messaging-14.0.0/.gitreview 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/.gitreview 2023-11-09 05:31:32.000000000 +0000 @@ -2,3 +2,4 @@ host=review.opendev.org port=29418 project=openstack/oslo.messaging.git +defaultbranch=stable/zed diff -Nru python-oslo.messaging-14.0.0/debian/changelog python-oslo.messaging-14.0.3/debian/changelog --- python-oslo.messaging-14.0.0/debian/changelog 2022-09-23 11:49:53.000000000 +0000 +++ python-oslo.messaging-14.0.3/debian/changelog 2024-03-06 10:42:43.000000000 +0000 @@ -1,3 +1,15 @@ +python-oslo.messaging (14.0.3-0+deb12u1) bookworm-security; urgency=medium + + * New upstream release. + * Add patches: + - Implement_get_rpc_client_function.patch + - Support_overriding_class_for_get_rpc_helper_functions.patch + * CVE-2026-44393 / OSSN-0096: oslo.messaging does not verify RabbitMQ broker + hostname during TLS handshake. Added upstream patch: Fix RabbitMQ TLS + hostname verification (Closes: #1138848). + + -- Thomas Goirand Wed, 06 Mar 2024 11:42:43 +0100 + python-oslo.messaging (14.0.0-2) unstable; urgency=medium * Uploading to unstable. diff -Nru python-oslo.messaging-14.0.0/debian/patches/CVE-2026-44393_OSSN-0096_Fix_RabbitMQ_TLS_hostname_verification.patch python-oslo.messaging-14.0.3/debian/patches/CVE-2026-44393_OSSN-0096_Fix_RabbitMQ_TLS_hostname_verification.patch --- python-oslo.messaging-14.0.0/debian/patches/CVE-2026-44393_OSSN-0096_Fix_RabbitMQ_TLS_hostname_verification.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.messaging-14.0.3/debian/patches/CVE-2026-44393_OSSN-0096_Fix_RabbitMQ_TLS_hostname_verification.patch 2024-03-06 10:42:43.000000000 +0000 @@ -0,0 +1,361 @@ +Author: Daniel Bengtsson +Date: Mon, 11 May 2026 13:23:01 +0200 +Description: [PATCH] Fix RabbitMQ TLS hostname verification + When TLS is used with ssl_ca_file, the Rabbit driver validates the + certificate chain but does not verify the broker hostname. This + could allow a MITM attacker with a certificate trusted by the + deployment CA to impersonate the RabbitMQ broker. + . + This change adds ssl_enforce_hostname_verification. When enabled + together with ssl_ca_file, the driver enables hostname verification. + . + For single-broker configurations, the broker hostname is passed + explicitly. For multi-broker configurations, Kombu >= 5.2.0 is required + when hostname verification is enforced. Older Kombu versions will result + in an explicit failure instead of silently disabling verification. + . + NOTE: Unlike master, the backport keeps + ssl_enforce_hostname_verification disabled by default to preserve + stable branch behavior compatibility. + . + Conflicts: + oslo_messaging/tests/drivers/test_impl_rabbit.py + . + Resolved conflict caused by remaining unit tests for FIPS mode support. +Bug: https://launchpad.net/bugs/2150316 +Bug-Debian: https://bugs.debian.org/1138848 +Change-Id: I317ebe5c3728041d15152b0c372cf471e527d1fb +Signed-off-by: Daniel Bengtsson +Origin: upstream, https://review.opendev.org/c/openstack/oslo.messaging/+/988981 +Last-Update: 2026-06-04 + +Index: python-oslo.messaging/oslo_messaging/_drivers/impl_rabbit.py +=================================================================== +--- python-oslo.messaging.orig/oslo_messaging/_drivers/impl_rabbit.py ++++ python-oslo.messaging/oslo_messaging/_drivers/impl_rabbit.py +@@ -16,6 +16,7 @@ import collections + import contextlib + import errno + import functools ++import importlib.metadata + import itertools + import math + import os +@@ -36,6 +37,7 @@ import kombu.messaging + from oslo_config import cfg + from oslo_log import log as logging + from oslo_utils import eventletutils ++from oslo_utils import versionutils + + import oslo_messaging + from oslo_messaging._drivers import amqp as rpc_amqp +@@ -82,6 +84,15 @@ rabbit_opts = [ + deprecated_name='kombu_ssl_ca_certs', + help='SSL certification authority file ' + '(valid only if SSL enabled).'), ++ cfg.BoolOpt('ssl_enforce_hostname_verification', ++ default=False, ++ help='When true, verify the broker hostname against the ' ++ 'certificate when ``ssl_ca_file`` is set. When false ' ++ '(default on stable branches), ``ssl`` with ' ++ '``ssl_ca_file`` still validates the certificate chain ' ++ 'but does not verify the broker hostname. ``ssl=true`` ' ++ 'without ``ssl_ca_file`` never enables hostname ' ++ 'verification.'), + cfg.BoolOpt('ssl_enforce_fips_mode', + default=False, + help='Global toggle for enforcing the OpenSSL FIPS mode. ' +@@ -617,6 +628,11 @@ class Connection(object): + self.ssl_key_file = driver_conf.ssl_key_file + self.ssl_cert_file = driver_conf.ssl_cert_file + self.ssl_ca_file = driver_conf.ssl_ca_file ++ self.ssl_enforce_hostname_verification = ( ++ driver_conf.ssl_enforce_hostname_verification) ++ self.ssl_server_hostname = None ++ if (self.ssl_ca_file and self.ssl_enforce_hostname_verification): ++ self.ssl_server_hostname = self._get_ssl_server_hostname(url) + + if self.ssl_enforce_fips_mode: + if hasattr(ssl, 'FIPS_mode'): +@@ -792,6 +808,33 @@ class Connection(object): + except KeyError: + raise RuntimeError("Invalid SSL version : %s" % version) + ++ @staticmethod ++ def _get_ssl_server_hostname(url): ++ if len(url.hosts) == 1: ++ return url.hosts[0].hostname ++ if len(url.hosts) > 1: ++ kombu_ver = importlib.metadata.version('kombu') ++ try: ++ kombu_substitutes_failover_hostname = ( ++ versionutils.convert_version_to_tuple(kombu_ver) >= ++ (5, 2, 0) ++ ) ++ except ValueError: ++ kombu_substitutes_failover_hostname = False ++ if kombu_substitutes_failover_hostname: ++ # Kombu >= 5.2.0 substitutes None with the selected broker ++ # hostname after failover chooses the active URL. ++ return None ++ LOG.warning( ++ "Multi-host RabbitMQ TLS hostname verification with Kombu " ++ "before 5.2.0 cannot automatically track the active failover " ++ "broker for the TLS server name. Using the first configured " ++ "broker hostname as a best effort. Upgrade to Kombu >= " ++ "5.2.0, or use a broker certificate (SAN or wildcard) that " ++ "covers all configured broker hostnames.") ++ return url.hosts[0].hostname ++ return None ++ + def _get_quorum_configurations(self, driver_conf): + """Get the quorum queue configurations""" + delivery_limit = driver_conf.rabbit_quorum_delivery_limit +@@ -840,6 +883,8 @@ class Connection(object): + # We might want to allow variations in the + # future with this? + ssl_params['cert_reqs'] = ssl.CERT_REQUIRED ++ if self.ssl_enforce_hostname_verification: ++ ssl_params['server_hostname'] = self.ssl_server_hostname + return ssl_params or True + return False + +Index: python-oslo.messaging/oslo_messaging/tests/drivers/test_impl_rabbit.py +=================================================================== +--- python-oslo.messaging.orig/oslo_messaging/tests/drivers/test_impl_rabbit.py ++++ python-oslo.messaging/oslo_messaging/tests/drivers/test_impl_rabbit.py +@@ -161,6 +161,7 @@ class TestRabbitDriverLoad(test_utils.Ba + + + class TestRabbitDriverLoadSSL(test_utils.BaseTestCase): ++ hostname_verification = 'ssl_enforce_hostname_verification' + scenarios = [ + ('no_ssl', dict(options=dict(), expected=False)), + ('no_ssl_with_options', dict(options=dict(ssl_version='TLSv1'), +@@ -171,12 +172,28 @@ class TestRabbitDriverLoadSSL(test_utils + ssl_version='TLSv1', + ssl_key_file='foo', + ssl_cert_file='bar', +- ssl_ca_file='foobar'), ++ ssl_ca_file='foobar', ++ **{ ++ hostname_verification: True ++ }), + expected=dict(ssl_version=3, + keyfile='foo', + certfile='bar', + ca_certs='foobar', +- cert_reqs=ssl.CERT_REQUIRED))), ++ cert_reqs=ssl.CERT_REQUIRED, ++ server_hostname=None))), ++ ('ssl_with_options_no_hostname_check', ++ dict(options=dict(ssl=True, ++ ssl_version='TLSv1', ++ ssl_key_file='foo', ++ ssl_cert_file='bar', ++ ssl_ca_file='foobar', ++ ssl_enforce_hostname_verification=False), ++ expected=dict(ssl_version=3, ++ keyfile='foo', ++ certfile='bar', ++ ca_certs='foobar', ++ cert_reqs=ssl.CERT_REQUIRED))), + ] + + @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' +@@ -265,6 +282,166 @@ class TestRabbitDriverLoadSSLWithFIPS(te + transport._driver._get_connection) + + ++class TestRabbitDriverSSLHostname(test_utils.BaseTestCase): ++ ++ def test_ssl_enforce_hostname_verification_default_false(self): ++ transport = oslo_messaging.get_transport(self.conf, ++ 'kombu+memory:////') ++ self.addCleanup(transport.cleanup) ++ self.assertFalse( ++ self.conf.oslo_messaging_rabbit.ssl_enforce_hostname_verification) ++ ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' ++ '.ensure_connection') ++ @mock.patch('kombu.connection.Connection') ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.LOG') ++ @mock.patch( ++ 'oslo_messaging._drivers.impl_rabbit.importlib.metadata.version', ++ return_value='5.2.0') ++ def test_multi_host_kombu_5_2_uses_hostname_substitution( ++ self, mock_version, mock_log, connection_klass, fake_ensure): ++ self.config(ssl=True, ssl_ca_file='foobar', ++ ssl_enforce_hostname_verification=True, ++ group='oslo_messaging_rabbit') ++ transport = oslo_messaging.get_transport( ++ self.conf, 'rabbit://host1:5672,host2:5672//') ++ self.addCleanup(transport.cleanup) ++ ++ transport._driver._get_connection() ++ ++ ssl_params = connection_klass.call_args.kwargs['ssl'] ++ self.assertIsNone(ssl_params['server_hostname']) ++ mock_log.warning.assert_not_called() ++ ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' ++ '.ensure_connection') ++ @mock.patch('kombu.connection.Connection') ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.LOG') ++ @mock.patch( ++ 'oslo_messaging._drivers.impl_rabbit.importlib.metadata.version', ++ return_value='5.1.0') ++ def test_multi_host_old_kombu_warns_and_uses_first_hostname( ++ self, mock_version, mock_log, connection_klass, fake_ensure): ++ self.config(ssl=True, ssl_ca_file='foobar', ++ ssl_enforce_hostname_verification=True, ++ group='oslo_messaging_rabbit') ++ transport = oslo_messaging.get_transport( ++ self.conf, 'rabbit://host1:5672,host2:5672//') ++ self.addCleanup(transport.cleanup) ++ ++ transport._driver._get_connection() ++ ++ ssl_params = connection_klass.call_args.kwargs['ssl'] ++ self.assertEqual('host1', ssl_params['server_hostname']) ++ mock_log.warning.assert_called_once() ++ self.assertIn('Multi-host RabbitMQ TLS', ++ mock_log.warning.call_args[0][0]) ++ ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' ++ '.ensure_connection') ++ @mock.patch('kombu.connection.Connection') ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.LOG') ++ @mock.patch( ++ 'oslo_messaging._drivers.impl_rabbit.importlib.metadata.version', ++ return_value='unknown') ++ def test_multi_host_unparsable_kombu_version_warns_first_hostname( ++ self, mock_version, mock_log, connection_klass, fake_ensure): ++ self.config(ssl=True, ssl_ca_file='foobar', ++ ssl_enforce_hostname_verification=True, ++ group='oslo_messaging_rabbit') ++ transport = oslo_messaging.get_transport( ++ self.conf, 'rabbit://host1:5672,host2:5672//') ++ self.addCleanup(transport.cleanup) ++ ++ transport._driver._get_connection() ++ ++ ssl_params = connection_klass.call_args.kwargs['ssl'] ++ self.assertEqual('host1', ssl_params['server_hostname']) ++ mock_log.warning.assert_called_once() ++ ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' ++ '.ensure_connection') ++ @mock.patch('kombu.connection.Connection') ++ def test_enforcement_disabled_omits_server_hostname(self, connection_klass, ++ fake_ensure): ++ self.config(ssl=True, ssl_ca_file='foobar', ++ ssl_enforce_hostname_verification=False, ++ group='oslo_messaging_rabbit') ++ transport = oslo_messaging.get_transport( ++ self.conf, 'rabbit://host1:5672//') ++ self.addCleanup(transport.cleanup) ++ ++ transport._driver._get_connection() ++ ++ ssl_params = connection_klass.call_args.kwargs['ssl'] ++ self.assertNotIn('server_hostname', ssl_params) ++ ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' ++ '.ensure_connection') ++ @mock.patch('kombu.connection.Connection') ++ def test_single_host_uses_hostname(self, connection_klass, fake_ensure): ++ self.config(ssl=True, ssl_ca_file='foobar', ++ ssl_enforce_hostname_verification=True, ++ group='oslo_messaging_rabbit') ++ transport = oslo_messaging.get_transport( ++ self.conf, 'rabbit://host1:5672//') ++ self.addCleanup(transport.cleanup) ++ ++ transport._driver._get_connection() ++ ++ ssl_params = connection_klass.call_args.kwargs['ssl'] ++ self.assertEqual('host1', ssl_params['server_hostname']) ++ ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' ++ '.ensure_connection') ++ @mock.patch('kombu.connection.Connection') ++ @mock.patch( ++ 'oslo_messaging._drivers.impl_rabbit.importlib.metadata.version', ++ return_value='5.1.0') ++ def test_multi_host_old_kombu_allowed_without_enforcement( ++ self, mock_version, connection_klass, fake_ensure): ++ self.config(ssl=True, ssl_ca_file='foobar', ++ ssl_enforce_hostname_verification=False, ++ group='oslo_messaging_rabbit') ++ transport = oslo_messaging.get_transport( ++ self.conf, 'rabbit://host1:5672,host2:5672//') ++ self.addCleanup(transport.cleanup) ++ transport._driver._get_connection() ++ ++ ssl_params = connection_klass.call_args.kwargs['ssl'] ++ self.assertNotIn('server_hostname', ssl_params) ++ ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' ++ '.ensure_connection') ++ @mock.patch('kombu.connection.Connection') ++ def test_multi_host_unknown_kombu_allowed_without_enforcement( ++ self, connection_klass, fake_ensure): ++ self.config(ssl=True, ssl_ca_file='foobar', ++ ssl_enforce_hostname_verification=False, ++ group='oslo_messaging_rabbit') ++ transport = oslo_messaging.get_transport( ++ self.conf, 'rabbit://host1:5672,host2:5672//') ++ self.addCleanup(transport.cleanup) ++ transport._driver._get_connection() ++ ++ ssl_params = connection_klass.call_args.kwargs['ssl'] ++ self.assertNotIn('server_hostname', ssl_params) ++ ++ @mock.patch('oslo_messaging._drivers.impl_rabbit.Connection' ++ '.ensure_connection') ++ @mock.patch('kombu.connection.Connection') ++ def test_ssl_without_ca_does_not_check_hostname(self, connection_klass, ++ fake_ensure): ++ self.config(ssl=True, group='oslo_messaging_rabbit') ++ transport = oslo_messaging.get_transport( ++ self.conf, 'rabbit://host1:5672,host2:5672//') ++ self.addCleanup(transport.cleanup) ++ ++ transport._driver._get_connection() ++ ++ self.assertIs(True, connection_klass.call_args.kwargs['ssl']) ++ ++ + class TestRabbitPublisher(test_utils.BaseTestCase): + @mock.patch('kombu.messaging.Producer.publish') + def test_send_with_timeout(self, fake_publish): +Index: python-oslo.messaging/releasenotes/notes/rabbit-ssl-hostname-verification-option.yaml +=================================================================== +--- /dev/null ++++ python-oslo.messaging/releasenotes/notes/rabbit-ssl-hostname-verification-option.yaml +@@ -0,0 +1,24 @@ ++--- ++security: ++ - | ++ Under TLS with ``ssl_ca_file``, oslo.messaging validated the broker ++ certificate chain but did not verify the RabbitMQ broker hostname. A ++ man-in-the-middle attacker with a certificate trusted by that CA could ++ impersonate the broker. ++ ++ The RabbitMQ driver now verifies the broker hostname when ``ssl_ca_file`` ++ is set and ``[oslo_messaging_rabbit] ssl_enforce_hostname_verification`` ++ is enabled. Using ``ssl=true`` without ``ssl_ca_file`` still does not ++ verify the broker hostname. ++ ++ The ``ssl_enforce_hostname_verification`` option defaults to ++ ``false`` to preserve existing behavior until operators opt in. When ++ enabled together with ``ssl_ca_file``, hostname verification is enforced ++ for RabbitMQ TLS connections. ++ ++ For transport URLs with multiple brokers and hostname verification ++ enabled, Kombu 5.2.0 or newer substitutes the active broker hostname for ++ TLS. Older Kombu versions log a warning and use the first configured ++ broker hostname as a best effort; operators should upgrade Kombu or use a ++ certificate (SAN or wildcard) that covers all configured broker ++ hostnames. diff -Nru python-oslo.messaging-14.0.0/debian/patches/Implement_get_rpc_client_function.patch python-oslo.messaging-14.0.3/debian/patches/Implement_get_rpc_client_function.patch --- python-oslo.messaging-14.0.0/debian/patches/Implement_get_rpc_client_function.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.messaging-14.0.3/debian/patches/Implement_get_rpc_client_function.patch 2024-03-06 10:42:43.000000000 +0000 @@ -0,0 +1,324 @@ +From 4ead7cb2dcf376032f7bf9532a375256db6d3784 Mon Sep 17 00:00:00 2001 +From: Tobias Urdin +Date: Sat, 22 Oct 2022 12:55:34 +0200 +Subject: [PATCH] Implement get_rpc_client function + +We already expose functions to handle the instantiation +of classes such as RPCServer and RPCTransport but the +same was never done for RPCClient so the API is +inconsistent in its enforcement. + +This adds a get_rpc_client function that should be used +instead of instatiating the RPCClient class directly to +be more consistent. + +This also allows to handle more logic inside the function +in the future such as if implementations for an async client +is implemented, as investigation in [1] has shown. + +[1] https://review.opendev.org/c/openstack/oslo.messaging/+/858936 + +Change-Id: Ia4d1f0497b9e2728bde02f4ff05fdc175ddffe66 +--- + +diff --git a/oslo_messaging/rpc/__init__.py b/oslo_messaging/rpc/__init__.py +index 9a320a8..135428e 100644 +--- a/oslo_messaging/rpc/__init__.py ++++ b/oslo_messaging/rpc/__init__.py +@@ -30,6 +30,7 @@ + 'expected_exceptions', + 'get_rpc_transport', + 'get_rpc_server', ++ 'get_rpc_client', + 'expose' + ] + +diff --git a/oslo_messaging/rpc/client.py b/oslo_messaging/rpc/client.py +index cbec525..8e997e9 100644 +--- a/oslo_messaging/rpc/client.py ++++ b/oslo_messaging/rpc/client.py +@@ -32,6 +32,7 @@ + 'RPCClient', + 'RPCVersionCapError', + 'RemoteError', ++ 'get_rpc_client', + ] + + LOG = logging.getLogger(__name__) +@@ -263,6 +264,9 @@ + The RPCClient class is responsible for sending method invocations to and + receiving return values from remote RPC servers via a messaging transport. + ++ The class should always be instantiated by using the get_rpc_client ++ function and not constructing the class directly. ++ + Two RPC patterns are supported: RPC calls and RPC casts. + + An RPC cast is used when an RPC method does *not* return a value to +@@ -295,7 +299,7 @@ + + def __init__(self, transport): + target = messaging.Target(topic='test', version='2.0') +- self._client = messaging.RPCClient(transport, target) ++ self._client = messaging.get_rpc_client(transport, target) + + def test(self, ctxt, arg): + return self._client.call(ctxt, 'test', arg=arg) +@@ -320,7 +324,7 @@ + + transport = messaging.get_rpc_transport(cfg.CONF) + target = messaging.Target(topic='test', version='2.0') +- client = messaging.RPCClient(transport, target) ++ client = messaging.get_rpc_client(transport, target) + client.call(ctxt, 'test', arg=arg) + + but this is probably only useful in limited circumstances as a wrapper +@@ -334,7 +338,7 @@ + have the RPC request fail with a MessageDeliveryFailure after the given + number of retries. For example:: + +- client = messaging.RPCClient(transport, target, retry=None) ++ client = messaging.get_rpc_client(transport, target, retry=None) + client.call(ctxt, 'sync') + try: + client.prepare(retry=0).cast(ctxt, 'ping') +@@ -346,9 +350,13 @@ + + def __init__(self, transport, target, + timeout=None, version_cap=None, serializer=None, retry=None, +- call_monitor_timeout=None, transport_options=None): ++ call_monitor_timeout=None, transport_options=None, ++ _manual_load=True): + """Construct an RPC client. + ++ This should not be called directly, use the get_rpc_client function ++ to instantiate this class. ++ + :param transport: a messaging transport handle + :type transport: Transport + :param target: the default target for invocations +@@ -371,7 +379,17 @@ + (less than the overall timeout + parameter). + :type call_monitor_timeout: int ++ :param transport_options: Transport options passed to client. ++ :type transport_options: TransportOptions ++ :param _manual_load: Internal use only to check if class was ++ manually instantiated or not. ++ :type _manual_load: bool + """ ++ if _manual_load: ++ LOG.warning("Using RPCClient manually to instantiate client. " ++ "Please use get_rpc_client to obtain an RPC client " ++ "instance.") ++ + if serializer is None: + serializer = msg_serializer.NoOpSerializer() + +@@ -530,3 +548,16 @@ + def can_send_version(self, version=_marker): + """Check to see if a version is compatible with the version cap.""" + return self.prepare(version=version).can_send_version() ++ ++ ++def get_rpc_client(transport, target, **kwargs): ++ """Construct an RPC client. ++ ++ :param transport: the messaging transport ++ :type transport: Transport ++ :param target: the exchange, topic and server to listen on ++ :type target: Target ++ :param **kwargs: The kwargs will be passed down to the ++ RPCClient constructor ++ """ ++ return RPCClient(transport, target, _manual_load=False, **kwargs) +diff --git a/oslo_messaging/tests/functional/utils.py b/oslo_messaging/tests/functional/utils.py +index 0d007b2..6a49f49 100644 +--- a/oslo_messaging/tests/functional/utils.py ++++ b/oslo_messaging/tests/functional/utils.py +@@ -114,8 +114,8 @@ + target=self.target, + endpoints=endpoints, + executor=self.executor) +- self._ctrl = oslo_messaging.RPCClient(transport.transport, +- self.ctrl_target) ++ self._ctrl = oslo_messaging.get_rpc_client(transport.transport, ++ self.ctrl_target) + self._start() + transport.wait() + +@@ -230,7 +230,7 @@ + transport_options=None, **kwargs): + self.name = name or "functional-tests" + self.cast = cast +- self.client = oslo_messaging.RPCClient( ++ self.client = oslo_messaging.get_rpc_client( + transport=transport, + target=target, + transport_options=transport_options, +diff --git a/oslo_messaging/tests/rpc/test_client.py b/oslo_messaging/tests/rpc/test_client.py +index af1282a..1358c98 100644 +--- a/oslo_messaging/tests/rpc/test_client.py ++++ b/oslo_messaging/tests/rpc/test_client.py +@@ -44,8 +44,9 @@ + self.config(rpc_response_timeout=None) + transport_options = oslo_messaging.TransportOptions() + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') +- client = oslo_messaging.RPCClient(transport, oslo_messaging.Target(), +- transport_options=transport_options) ++ client = oslo_messaging.get_rpc_client( ++ transport, oslo_messaging.Target(), ++ transport_options=transport_options) + + transport._send = mock.Mock() + +@@ -70,7 +71,7 @@ + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') + + transport_options = oslo_messaging.TransportOptions(at_least_once=True) +- client = oslo_messaging.RPCClient( ++ client = oslo_messaging.get_rpc_client( + transport, + oslo_messaging.Target(), + transport_options=transport_options) +@@ -215,7 +216,7 @@ + expect_target = oslo_messaging.Target(**self.expect) + + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') +- client = oslo_messaging.RPCClient(transport, target) ++ client = oslo_messaging.get_rpc_client(transport, target) + + transport._send = mock.Mock() + +@@ -269,9 +270,9 @@ + self.config(rpc_response_timeout=self.confval) + + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') +- client = oslo_messaging.RPCClient(transport, oslo_messaging.Target(), +- timeout=self.ctor, +- call_monitor_timeout=self.cm) ++ client = oslo_messaging.get_rpc_client( ++ transport, oslo_messaging.Target(), timeout=self.ctor, ++ call_monitor_timeout=self.cm) + + transport._send = mock.Mock() + +@@ -302,8 +303,9 @@ + + def test_call_retry(self): + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') +- client = oslo_messaging.RPCClient(transport, oslo_messaging.Target(), +- retry=self.ctor) ++ client = oslo_messaging.get_rpc_client( ++ transport, oslo_messaging.Target(), ++ retry=self.ctor) + + transport._send = mock.Mock() + +@@ -332,8 +334,8 @@ + + def test_call_fanout(self): + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') +- client = oslo_messaging.RPCClient(transport, +- oslo_messaging.Target(**self.target)) ++ client = oslo_messaging.get_rpc_client( ++ transport, oslo_messaging.Target(**self.target)) + + if self.prepare is not _notset: + client = client.prepare(**self.prepare) +@@ -363,8 +365,8 @@ + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') + serializer = msg_serializer.NoOpSerializer() + +- client = oslo_messaging.RPCClient(transport, oslo_messaging.Target(), +- serializer=serializer) ++ client = oslo_messaging.get_rpc_client( ++ transport, oslo_messaging.Target(), serializer=serializer) + + transport._send = mock.Mock() + kwargs = dict(wait_for_reply=True, +@@ -465,8 +467,8 @@ + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') + + target = oslo_messaging.Target(version=self.version) +- client = oslo_messaging.RPCClient(transport, target, +- version_cap=self.cap) ++ client = oslo_messaging.get_rpc_client(transport, target, ++ version_cap=self.cap) + + if self.success: + transport._send = mock.Mock() +@@ -574,8 +576,8 @@ + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') + + target = oslo_messaging.Target(version=self.version) +- client = oslo_messaging.RPCClient(transport, target, +- version_cap=self.cap) ++ client = oslo_messaging.get_rpc_client(transport, target, ++ version_cap=self.cap) + + prep_kwargs = {} + if self.prepare_cap is not _notset: +@@ -598,7 +600,7 @@ + def test_invalid_version_type(self): + target = oslo_messaging.Target(topic='sometopic') + transport = oslo_messaging.get_rpc_transport(self.conf, url='fake:') +- client = oslo_messaging.RPCClient(transport, target) ++ client = oslo_messaging.get_rpc_client(transport, target) + self.assertRaises(exceptions.MessagingException, + client.prepare, version='5') + self.assertRaises(exceptions.MessagingException, +@@ -612,7 +614,7 @@ + @mock.patch('oslo_messaging.rpc.client.LOG') + def test_warning_when_notifier_transport(self, log): + transport = oslo_messaging.get_notification_transport(self.conf) +- oslo_messaging.RPCClient(transport, oslo_messaging.Target()) ++ oslo_messaging.get_rpc_client(transport, oslo_messaging.Target()) + log.warning.assert_called_once_with( + "Using notification transport for RPC. Please use " + "get_rpc_transport to obtain an RPC transport " +diff --git a/oslo_messaging/tests/rpc/test_server.py b/oslo_messaging/tests/rpc/test_server.py +index 1fc6be8..06cf1c7 100644 +--- a/oslo_messaging/tests/rpc/test_server.py ++++ b/oslo_messaging/tests/rpc/test_server.py +@@ -102,8 +102,8 @@ + + def _setup_client(self, transport, topic='testtopic', exchange=None): + target = oslo_messaging.Target(topic=topic, exchange=exchange) +- return oslo_messaging.RPCClient(transport, target=target, +- serializer=self.serializer) ++ return oslo_messaging.get_rpc_client(transport, target=target, ++ serializer=self.serializer) + + + class TestRPCServer(test_utils.BaseTestCase, ServerSetupMixin): +diff --git a/releasenotes/notes/get-rpc-client-0b4aa62160864b29.yaml b/releasenotes/notes/get-rpc-client-0b4aa62160864b29.yaml +new file mode 100644 +index 0000000..3375cfc +--- /dev/null ++++ b/releasenotes/notes/get-rpc-client-0b4aa62160864b29.yaml +@@ -0,0 +1,11 @@ ++--- ++features: ++ - | ++ Added new ``get_rpc_client`` function to instantiate the RPCClient ++ class ++deprecations: ++ - | ++ Instantiating the RPCClient class directly is deprecated in favor ++ of using the new ``get_rpc_client`` function to expose a more ++ common API similar to existing functions such as ``get_rpc_server`` ++ and ``get_rpc_transport`` +diff --git a/tools/simulator.py b/tools/simulator.py +index 8b37f50..da9d05e 100755 +--- a/tools/simulator.py ++++ b/tools/simulator.py +@@ -410,7 +410,7 @@ + def __init__(self, client_id, transport, target, timeout, is_cast, + wait_after_msg, sync_mode=False): + +- client = rpc.RPCClient(transport, target) ++ client = rpc.get_rpc_client(transport, target) + method = _rpc_cast if is_cast else _rpc_call + + super(RPCClient, self).__init__(client_id, diff -Nru python-oslo.messaging-14.0.0/debian/patches/Support_overriding_class_for_get_rpc_helper_functions.patch python-oslo.messaging-14.0.3/debian/patches/Support_overriding_class_for_get_rpc_helper_functions.patch --- python-oslo.messaging-14.0.0/debian/patches/Support_overriding_class_for_get_rpc_helper_functions.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.messaging-14.0.3/debian/patches/Support_overriding_class_for_get_rpc_helper_functions.patch 2024-03-06 10:42:43.000000000 +0000 @@ -0,0 +1,110 @@ +From 687dea2e65814606bece8041fe94fb2674d2590b Mon Sep 17 00:00:00 2001 +From: Tobias Urdin +Date: Thu, 12 Jan 2023 07:19:46 +0000 +Subject: [PATCH] Support overriding class for get_rpc_* helper functions + +We currently do not support overriding the class being +instantiated in the RPC helper functions, this adds that +support so that projects that define their own classes +that inherit from oslo.messaging can use the helpers. + +For example neutron utilizes code from neutron-lib that +has it's own RPCClient implementation that inherits from +oslo.messaging, in order for them to use for example +the get_rpc_client helper they need support to override +the class being returned. The alternative would be to +modify the internal _manual_load variable which seems +counter-productive to extending the API provided to +consumers. + +Change-Id: Ie22f2ee47a4ca3f28a71272ee1ffdb88aaeb7758 +--- + +diff --git a/oslo_messaging/rpc/client.py b/oslo_messaging/rpc/client.py +index 8e997e9..b96e30f 100644 +--- a/oslo_messaging/rpc/client.py ++++ b/oslo_messaging/rpc/client.py +@@ -550,14 +550,16 @@ + return self.prepare(version=version).can_send_version() + + +-def get_rpc_client(transport, target, **kwargs): ++def get_rpc_client(transport, target, client_cls=RPCClient, **kwargs): + """Construct an RPC client. + + :param transport: the messaging transport + :type transport: Transport + :param target: the exchange, topic and server to listen on + :type target: Target ++ :param client_cls: The client class to instantiate ++ :type client_cls: class + :param **kwargs: The kwargs will be passed down to the +- RPCClient constructor ++ client_cls constructor + """ +- return RPCClient(transport, target, _manual_load=False, **kwargs) ++ return client_cls(transport, target, _manual_load=False, **kwargs) +diff --git a/oslo_messaging/rpc/server.py b/oslo_messaging/rpc/server.py +index 78557e2..94d4888 100644 +--- a/oslo_messaging/rpc/server.py ++++ b/oslo_messaging/rpc/server.py +@@ -200,7 +200,8 @@ + + + def get_rpc_server(transport, target, endpoints, +- executor=None, serializer=None, access_policy=None): ++ executor=None, serializer=None, access_policy=None, ++ server_cls=RPCServer): + """Construct an RPC server. + + :param transport: the messaging transport +@@ -217,10 +218,12 @@ + :param access_policy: an optional access policy. + Defaults to DefaultRPCAccessPolicy + :type access_policy: RPCAccessPolicyBase ++ :param server_cls: The server class to instantiate ++ :type server_cls: class + """ + dispatcher = rpc_dispatcher.RPCDispatcher(endpoints, serializer, + access_policy) +- return RPCServer(transport, target, dispatcher, executor) ++ return server_cls(transport, target, dispatcher, executor) + + + def expected_exceptions(*exceptions): +diff --git a/oslo_messaging/rpc/transport.py b/oslo_messaging/rpc/transport.py +index 121d617..8f08db5 100644 +--- a/oslo_messaging/rpc/transport.py ++++ b/oslo_messaging/rpc/transport.py +@@ -22,7 +22,8 @@ + + + def get_rpc_transport(conf, url=None, +- allowed_remote_exmods=None): ++ allowed_remote_exmods=None, ++ transport_cls=msg_transport.RPCTransport): + """A factory method for Transport objects for RPCs. + + This method should be used to ensure the correct messaging functionality +@@ -43,7 +44,9 @@ + transport will deserialize remote exceptions + from + :type allowed_remote_exmods: list ++ :param transport_cls: the transport class to instantiate ++ :type transport_cls: class + """ + return msg_transport._get_transport( + conf, url, allowed_remote_exmods, +- transport_cls=msg_transport.RPCTransport) ++ transport_cls=transport_cls) +diff --git a/releasenotes/notes/get-rpc-helpers-cls-8911826ac08aef2a.yaml b/releasenotes/notes/get-rpc-helpers-cls-8911826ac08aef2a.yaml +new file mode 100644 +index 0000000..6ac9aab +--- /dev/null ++++ b/releasenotes/notes/get-rpc-helpers-cls-8911826ac08aef2a.yaml +@@ -0,0 +1,5 @@ ++--- ++features: ++ - | ++ The ``get_rpc_transport``, ``get_rpc_server`` and ``get_rpc_client`` helper ++ functions now have support for overriding the class that is instantiated. diff -Nru python-oslo.messaging-14.0.0/debian/patches/series python-oslo.messaging-14.0.3/debian/patches/series --- python-oslo.messaging-14.0.0/debian/patches/series 2022-09-23 11:49:53.000000000 +0000 +++ python-oslo.messaging-14.0.3/debian/patches/series 2024-03-06 10:42:43.000000000 +0000 @@ -1 +1,4 @@ no-functional-test.patch +Implement_get_rpc_client_function.patch +Support_overriding_class_for_get_rpc_helper_functions.patch +CVE-2026-44393_OSSN-0096_Fix_RabbitMQ_TLS_hostname_verification.patch diff -Nru python-oslo.messaging-14.0.0/oslo_messaging/_drivers/amqpdriver.py python-oslo.messaging-14.0.3/oslo_messaging/_drivers/amqpdriver.py --- python-oslo.messaging-14.0.0/oslo_messaging/_drivers/amqpdriver.py 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/oslo_messaging/_drivers/amqpdriver.py 2023-11-09 05:31:32.000000000 +0000 @@ -37,7 +37,7 @@ # Maximum should be small enough to not get rejected ack, # minimum should be big enough to not burn the CPU. ACK_REQUEUE_EVERY_SECONDS_MIN = 0.001 -ACK_REQUEUE_EVERY_SECONDS_MAX = 1.0 +ACK_REQUEUE_EVERY_SECONDS_MAX = 5.0 class MessageOperationsHandler(object): diff -Nru python-oslo.messaging-14.0.0/oslo_messaging/_drivers/impl_rabbit.py python-oslo.messaging-14.0.3/oslo_messaging/_drivers/impl_rabbit.py --- python-oslo.messaging-14.0.0/oslo_messaging/_drivers/impl_rabbit.py 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/oslo_messaging/_drivers/impl_rabbit.py 2023-11-09 05:31:32.000000000 +0000 @@ -106,9 +106,11 @@ ), cfg.FloatOpt('kombu_reconnect_delay', default=1.0, + min=0.0, + max=amqpdriver.ACK_REQUEUE_EVERY_SECONDS_MAX * 0.9, deprecated_group='DEFAULT', - help='How long to wait before reconnecting in response to an ' - 'AMQP consumer cancel notification.'), + help='How long to wait (in seconds) before reconnecting in ' + 'response to an AMQP consumer cancel notification.'), cfg.StrOpt('kombu_compression', help="EXPERIMENTAL: Possible values are: gzip, bz2. If not " "set compression will not be used. This option may not " @@ -605,6 +607,8 @@ # if it was already monkey patched by eventlet/greenlet. global threading threading = _utils.stdlib_threading + amqpdriver.threading = _utils.stdlib_threading + amqpdriver.queue = _utils.stdlib_queue self.direct_mandatory_flag = driver_conf.direct_mandatory_flag diff -Nru python-oslo.messaging-14.0.0/oslo_messaging/_utils.py python-oslo.messaging-14.0.3/oslo_messaging/_utils.py --- python-oslo.messaging-14.0.0/oslo_messaging/_utils.py 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/oslo_messaging/_utils.py 2023-11-09 05:31:32.000000000 +0000 @@ -14,6 +14,7 @@ # under the License. import logging +import queue import threading from oslo_utils import eventletutils @@ -26,12 +27,14 @@ # Here we initialize module with the native python threading module # if it was already monkey patched by eventlet/greenlet. stdlib_threading = eventlet.patcher.original('threading') + stdlib_queue = eventlet.patcher.original('queue') else: # Manage the case where we run this driver in a non patched environment # and where user even so configure the driver to run heartbeat through # a python thread, if we don't do that when the heartbeat will start # we will facing an issue by trying to override the threading module. stdlib_threading = threading + stdlib_queue = queue def version_is_compatible(imp_version, version): diff -Nru python-oslo.messaging-14.0.0/oslo_messaging/notify/notifier.py python-oslo.messaging-14.0.3/oslo_messaging/notify/notifier.py --- python-oslo.messaging-14.0.0/oslo_messaging/notify/notifier.py 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/oslo_messaging/notify/notifier.py 2023-11-09 05:31:32.000000000 +0000 @@ -171,6 +171,51 @@ transport_cls=msg_transport.NotificationTransport) +def _sanitize_context(ctxt): + # NOTE(JayF): The below values are in the same order they are in + # oslo_context.context.RequestContext.__init__() + safe_keys = ( + 'user_id', + 'project_id', + 'domain_id', + 'user_domain_id', + 'project_domain_id', + # NOTE(JayF): Without is_admin; heat will make a roundtrip to policy + # to try to set it to a sane value when instantiating the + # replacement context. Instead, just pass it on. + 'is_admin', + 'request_id', + 'roles', + 'user_name', + 'project_name', + 'domain_name', + 'user_domain_name', + 'project_domain_name', + 'service_user_id', + 'service_user_domain_id', + 'service_user_domain_name', + 'service_project_id', + 'service_project_name', + 'service_project_domain_id', + 'service_project_domain_name', + 'service_roles', + 'global_request_id', + 'system_scope', + # NOTE(JayF) These have been renamed but may show up in notifications + 'user', + 'domain', + 'user_domain', + 'project_domain', + ) + ctxt_dict = ctxt if isinstance(ctxt, dict) else ctxt.to_dict() + safe_dict = {k: v for k, v in ctxt_dict.items() + if k in safe_keys} + if ctxt_dict is ctxt: + return safe_dict + else: + return ctxt.__class__.from_dict(safe_dict) + + class Notifier(object): """Send notification messages. @@ -296,7 +341,12 @@ def _notify(self, ctxt, event_type, payload, priority, publisher_id=None, retry=None): payload = self._serializer.serialize_entity(ctxt, payload) - ctxt = self._serializer.serialize_context(ctxt) + + # NOTE(JayF): We must remove secure information from notification + # payloads, otherwise we risk sending sensitive creds + # to a notification bus. + safe_ctxt = _sanitize_context(ctxt) + ctxt = self._serializer.serialize_context(safe_ctxt) msg = dict(message_id=str(uuid.uuid4()), publisher_id=publisher_id or self.publisher_id, diff -Nru python-oslo.messaging-14.0.0/oslo_messaging/tests/notify/test_listener.py python-oslo.messaging-14.0.3/oslo_messaging/tests/notify/test_listener.py --- python-oslo.messaging-14.0.0/oslo_messaging/tests/notify/test_listener.py 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/oslo_messaging/tests/notify/test_listener.py 2023-11-09 05:31:32.000000000 +0000 @@ -286,18 +286,18 @@ listener_thread = self._setup_listener(transport, [endpoint], targets=targets) notifier = self._setup_notifier(transport, topics=['topic1']) - notifier.info({'ctxt': '1'}, 'an_event.start1', 'test') + notifier.info({'user_name': 'bob'}, 'an_event.start1', 'test') notifier = self._setup_notifier(transport, topics=['topic2']) - notifier.info({'ctxt': '2'}, 'an_event.start2', 'test') + notifier.info({'user_name': 'bob2'}, 'an_event.start2', 'test') self.wait_for_messages(2) self.assertFalse(listener_thread.stop()) endpoint.info.assert_has_calls([ - mock.call({'ctxt': '1'}, 'testpublisher', + mock.call({'user_name': 'bob'}, 'testpublisher', 'an_event.start1', 'test', {'timestamp': mock.ANY, 'message_id': mock.ANY}), - mock.call({'ctxt': '2'}, 'testpublisher', + mock.call({'user_name': 'bob2'}, 'testpublisher', 'an_event.start2', 'test', {'timestamp': mock.ANY, 'message_id': mock.ANY})], any_order=True) @@ -326,23 +326,23 @@ transport._send_notification = mock.MagicMock( side_effect=side_effect) - notifier.info({'ctxt': '0'}, + notifier.info({'user_name': 'bob0'}, 'an_event.start', 'test message default exchange') mock_notifier_exchange('exchange1') - notifier.info({'ctxt': '1'}, + notifier.info({'user_name': 'bob1'}, 'an_event.start', 'test message exchange1') mock_notifier_exchange('exchange2') - notifier.info({'ctxt': '2'}, + notifier.info({'user_name': 'bob2'}, 'an_event.start', 'test message exchange2') self.wait_for_messages(2) self.assertFalse(listener_thread.stop()) endpoint.info.assert_has_calls([ - mock.call({'ctxt': '1'}, 'testpublisher', 'an_event.start', + mock.call({'user_name': 'bob1'}, 'testpublisher', 'an_event.start', 'test message exchange1', {'timestamp': mock.ANY, 'message_id': mock.ANY}), - mock.call({'ctxt': '2'}, 'testpublisher', 'an_event.start', + mock.call({'user_name': 'bob2'}, 'testpublisher', 'an_event.start', 'test message exchange2', {'timestamp': mock.ANY, 'message_id': mock.ANY})], any_order=True) @@ -414,8 +414,8 @@ targets=targets, pool="pool2") notifier = self._setup_notifier(transport, topics=["topic"]) - notifier.info({'ctxt': '0'}, 'an_event.start', 'test message0') - notifier.info({'ctxt': '1'}, 'an_event.start', 'test message1') + notifier.info({'user_name': 'bob0'}, 'an_event.start', 'test message0') + notifier.info({'user_name': 'bob1'}, 'an_event.start', 'test message1') self.wait_for_messages(2, "pool1") self.wait_for_messages(2, "pool2") @@ -423,7 +423,7 @@ self.assertFalse(listener1_thread.stop()) def mocked_endpoint_call(i): - return mock.call({'ctxt': '%d' % i}, 'testpublisher', + return mock.call({'user_name': 'bob%d' % i}, 'testpublisher', 'an_event.start', 'test message%d' % i, {'timestamp': mock.ANY, 'message_id': mock.ANY}) @@ -452,14 +452,14 @@ targets=targets, pool="pool2") def mocked_endpoint_call(i): - return mock.call({'ctxt': '%d' % i}, 'testpublisher', + return mock.call({'user_name': 'bob%d' % i}, 'testpublisher', 'an_event.start', 'test message%d' % i, {'timestamp': mock.ANY, 'message_id': mock.ANY}) notifier = self._setup_notifier(transport, topics=["topic"]) mocked_endpoint1_calls = [] for i in range(0, 25): - notifier.info({'ctxt': '%d' % i}, 'an_event.start', + notifier.info({'user_name': 'bob%d' % i}, 'an_event.start', 'test message%d' % i) mocked_endpoint1_calls.append(mocked_endpoint_call(i)) @@ -467,7 +467,7 @@ listener2_thread.stop() for i in range(0, 25): - notifier.info({'ctxt': '%d' % i}, 'an_event.start', + notifier.info({'user_name': 'bob%d' % i}, 'an_event.start', 'test message%d' % i) mocked_endpoint1_calls.append(mocked_endpoint_call(i)) @@ -476,7 +476,7 @@ listener3_thread.stop() for i in range(0, 25): - notifier.info({'ctxt': '%d' % i}, 'an_event.start', + notifier.info({'user_name': 'bob%d' % i}, 'an_event.start', 'test message%d' % i) mocked_endpoint1_calls.append(mocked_endpoint_call(i)) @@ -484,7 +484,7 @@ listener3_thread.start() for i in range(0, 25): - notifier.info({'ctxt': '%d' % i}, 'an_event.start', + notifier.info({'user_name': 'bob%d' % i}, 'an_event.start', 'test message%d' % i) mocked_endpoint1_calls.append(mocked_endpoint_call(i)) diff -Nru python-oslo.messaging-14.0.0/oslo_messaging/tests/notify/test_notifier.py python-oslo.messaging-14.0.3/oslo_messaging/tests/notify/test_notifier.py --- python-oslo.messaging-14.0.0/oslo_messaging/tests/notify/test_notifier.py 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/oslo_messaging/tests/notify/test_notifier.py 2023-11-09 05:31:32.000000000 +0000 @@ -122,7 +122,7 @@ ] _context = [ - ('ctxt', dict(ctxt={'user': 'bob'})), + ('ctxt', dict(ctxt={'user_name': 'bob'})), ] _retry = [ @@ -229,6 +229,157 @@ TestMessagingNotifier.generate_scenarios() +class TestMessagingNotifierContextFiltering(test_utils.BaseTestCase): + + _v1 = [ + ('v1', dict(v1=True)), + ('not_v1', dict(v1=False)), + ] + + _v2 = [ + ('v2', dict(v2=True)), + ('not_v2', dict(v2=False)), + ] + + _publisher_id = [ + ('ctor_pub_id', dict(ctor_pub_id='test', + expected_pub_id='test')), + ('prep_pub_id', dict(prep_pub_id='test.localhost', + expected_pub_id='test.localhost')), + ('override', dict(ctor_pub_id='test', + prep_pub_id='test.localhost', + expected_pub_id='test.localhost')), + ] + + _topics = [ + ('no_topics', dict(topics=[])), + ('single_topic', dict(topics=['notifications'])), + ('multiple_topic2', dict(topics=['foo', 'bar'])), + ] + + _priority = [ + ('audit', dict(priority='audit')), + ('debug', dict(priority='debug')), + ('info', dict(priority='info')), + ('warn', dict(priority='warn')), + ('error', dict(priority='error')), + ('sample', dict(priority='sample')), + ('critical', dict(priority='critical')), + ] + + _payload = [ + ('payload', dict(payload={'foo': 'bar'})), + ] + + _context = [ + ('ctxt', dict(ctxt={'user_name': 'bob'})), + ] + + _retry = [ + ('unconfigured', dict()), + ('None', dict(retry=None)), + ('0', dict(retry=0)), + ('5', dict(retry=5)), + ] + + @classmethod + def generate_scenarios(cls): + cls.scenarios = testscenarios.multiply_scenarios(cls._v1, + cls._v2, + cls._publisher_id, + cls._topics, + cls._priority, + cls._payload, + cls._retry) + + def setUp(self): + super(TestMessagingNotifierContextFiltering, self).setUp() + + self.logger = self.useFixture(_ReRaiseLoggedExceptionsFixture()).logger + self.useFixture(fixtures.MockPatchObject( + messaging, 'LOG', self.logger)) + self.useFixture(fixtures.MockPatchObject( + msg_notifier, '_LOG', self.logger)) + + @mock.patch('oslo_utils.timeutils.utcnow') + def test_notifier(self, mock_utcnow): + ctxt = {'user_name': 'bob', 'secret_data': 'redact_me'} + safe_ctxt = {'user_name': 'bob'} + drivers = [] + if self.v1: + drivers.append('messaging') + if self.v2: + drivers.append('messagingv2') + + self.config(driver=drivers, + topics=self.topics, + group='oslo_messaging_notifications') + + transport = oslo_messaging.get_notification_transport(self.conf, + url='fake:') + + if hasattr(self, 'ctor_pub_id'): + notifier = oslo_messaging.Notifier(transport, + publisher_id=self.ctor_pub_id) + else: + notifier = oslo_messaging.Notifier(transport) + + prepare_kwds = {} + if hasattr(self, 'retry'): + prepare_kwds['retry'] = self.retry + if hasattr(self, 'prep_pub_id'): + prepare_kwds['publisher_id'] = self.prep_pub_id + if prepare_kwds: + notifier = notifier.prepare(**prepare_kwds) + + transport._send_notification = mock.Mock() + + message_id = uuid.uuid4() + uuid.uuid4 = mock.Mock(return_value=message_id) + + mock_utcnow.return_value = datetime.datetime.utcnow() + + message = { + 'message_id': str(message_id), + 'publisher_id': self.expected_pub_id, + 'event_type': 'test.notify', + 'priority': self.priority.upper(), + 'payload': self.payload, + 'timestamp': str(timeutils.utcnow()), + } + + sends = [] + if self.v1: + sends.append(dict(version=1.0)) + if self.v2: + sends.append(dict(version=2.0)) + + calls = [] + for send_kwargs in sends: + for topic in self.topics: + if hasattr(self, 'retry'): + send_kwargs['retry'] = self.retry + else: + send_kwargs['retry'] = -1 + target = oslo_messaging.Target(topic='%s.%s' % (topic, + self.priority)) + calls.append(mock.call(target, + safe_ctxt, + message, + **send_kwargs)) + + method = getattr(notifier, self.priority) + method(ctxt, 'test.notify', self.payload) + + uuid.uuid4.assert_called_once_with() + transport._send_notification.assert_has_calls(calls, any_order=True) + + self.assertTrue(notifier.is_enabled()) + + +TestMessagingNotifierContextFiltering.generate_scenarios() + + class TestMessagingNotifierRetry(test_utils.BaseTestCase): class TestingException(BaseException): @@ -328,12 +479,12 @@ mock_utcnow.return_value = datetime.datetime.utcnow() serializer.serialize_context = mock.Mock() - serializer.serialize_context.return_value = dict(user='alice') + serializer.serialize_context.return_value = dict(user_name='alice') serializer.serialize_entity = mock.Mock() serializer.serialize_entity.return_value = 'sbar' - notifier.info(dict(user='bob'), 'test.notify', 'bar') + notifier.info(dict(user_name='bob'), 'test.notify', 'bar') message = { 'message_id': str(message_id), @@ -344,13 +495,14 @@ 'timestamp': str(timeutils.utcnow()), } - self.assertEqual([(dict(user='alice'), message, 'INFO', -1)], + self.assertEqual([(dict(user_name='alice'), message, 'INFO', -1)], _impl_test.NOTIFICATIONS) uuid.uuid4.assert_called_once_with() - serializer.serialize_context.assert_called_once_with(dict(user='bob')) - serializer.serialize_entity.assert_called_once_with(dict(user='bob'), - 'bar') + serializer.serialize_context.assert_called_once_with( + dict(user_name='bob')) + serializer.serialize_entity.assert_called_once_with( + dict(user_name='bob'), 'bar') class TestNotifierTopics(test_utils.BaseTestCase): diff -Nru python-oslo.messaging-14.0.0/oslo_messaging/tests/test_config_opts_proxy.py python-oslo.messaging-14.0.3/oslo_messaging/tests/test_config_opts_proxy.py --- python-oslo.messaging-14.0.0/oslo_messaging/tests/test_config_opts_proxy.py 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/oslo_messaging/tests/test_config_opts_proxy.py 2023-11-09 05:31:32.000000000 +0000 @@ -70,7 +70,7 @@ def test_invalid_value(self): group = 'oslo_messaging_rabbit' - self.config(kombu_reconnect_delay=5.0, + self.config(kombu_reconnect_delay=1.0, group=group) url = transport.TransportURL.parse( self.conf, "rabbit:///?kombu_reconnect_delay=invalid_value" diff -Nru python-oslo.messaging-14.0.0/releasenotes/notes/bug-1993149-e8b231791b65e938.yaml python-oslo.messaging-14.0.3/releasenotes/notes/bug-1993149-e8b231791b65e938.yaml --- python-oslo.messaging-14.0.0/releasenotes/notes/bug-1993149-e8b231791b65e938.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-oslo.messaging-14.0.3/releasenotes/notes/bug-1993149-e8b231791b65e938.yaml 2023-11-09 05:31:32.000000000 +0000 @@ -0,0 +1,9 @@ +--- +upgrade: + - | + If kombu_reconnect_delay is specified in the [oslo_messaging_rabbit] section, + ensure that it is less than 5.0, the value of ACK_REQUEUE_EVERY_SECONDS_MAX +fixes: + - | + Increased ACK_REQUEUE_EVERY_SECONDS_MAX to resolve issues with rabbitmq HA + failover. diff -Nru python-oslo.messaging-14.0.0/tox.ini python-oslo.messaging-14.0.3/tox.ini --- python-oslo.messaging-14.0.0/tox.ini 2022-08-16 14:14:29.000000000 +0000 +++ python-oslo.messaging-14.0.3/tox.ini 2023-11-09 05:31:32.000000000 +0000 @@ -12,7 +12,7 @@ REQUIREMENTS_PIP_LOCATION install_command = pip install {opts} {packages} deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/zed} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run --slowest {posargs}