From 71dd34a7bdd4cfeaf81b9cd1459dc8d492591cf0 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Mon, 25 Aug 2025 19:17:09 +0200 Subject: [PATCH] Launch API in the same process as conductor for singleprocess Ironic Indirection proved to impose very high performance costs. Even though we've identified and fixed a few major contributors to the regression, using RPC for a process to access itself has always been a stopgap measure rather than a proper architecture. This change moves the API from a separate service under the Launcher to a separate thread in the conductor, similarly to how the JSON RPC server is started. This paves the way to remove the local RPC entirely in the next patch. Change-Id: I0f3d854336bcc7ea1062f9a995e6d8979cb0cc22 Signed-off-by: Dmitry Tantsur --- ironic/command/singleprocess.py | 15 ++------------- ironic/conductor/rpc_service.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/ironic/command/singleprocess.py b/ironic/command/singleprocess.py index f2d37a5b25..27237d0176 100644 --- a/ironic/command/singleprocess.py +++ b/ironic/command/singleprocess.py @@ -19,12 +19,9 @@ from oslo_service import service from ironic.command import conductor as conductor_cmd from ironic.command import utils from ironic.common import service as ironic_service -from ironic.common import wsgi_service from ironic.conductor import local_rpc from ironic.conductor import rpc_service from ironic.console import novncproxy_service -from ironic.objects import base as objects_base -from ironic.objects import indirection CONF = cfg.CONF @@ -53,19 +50,11 @@ def main(): mgr = rpc_service.RPCService(CONF.host, 'ironic.conductor.manager', - 'ConductorManager') + 'ConductorManager', + embed_api=True) conductor_cmd.issue_startup_warnings(CONF) launcher.launch_service(mgr) - # Sets the indirection API to direct API calls for objects across the - # RPC layer. - if CONF.rpc_transport in ['local', 'none'] or CONF.use_rpc_for_database: - objects_base.IronicObject.indirection_api = \ - indirection.IronicObjectIndirectionAPI() - - wsgi = wsgi_service.WSGIService('ironic_api', CONF.api.enable_ssl_api) - launcher.launch_service(wsgi) - # NOTE(TheJulia): By default, vnc is disabled, and depending on that # overall process behavior will change. i.e. we're not going to force # single process which breaks systemd process launch detection. diff --git a/ironic/conductor/rpc_service.py b/ironic/conductor/rpc_service.py index c01b6e89ae..89dfd08522 100644 --- a/ironic/conductor/rpc_service.py +++ b/ironic/conductor/rpc_service.py @@ -21,6 +21,7 @@ from oslo_utils import timeutils from ironic.common import console_factory from ironic.common import rpc from ironic.common import rpc_service +from ironic.common import wsgi_service LOG = log.getLogger(__name__) CONF = cfg.CONF @@ -43,8 +44,11 @@ DRAIN = multiprocessing.Event() class RPCService(rpc_service.BaseRPCService): - def __init__(self, host, manager_module, manager_class): + def __init__(self, host, manager_module, manager_class, + embed_api=False): super().__init__(host, manager_module, manager_class) + self.apiserver = None + self._embed_api = embed_api @property def deregister_on_shutdown(self): @@ -57,6 +61,11 @@ class RPCService(rpc_service.BaseRPCService): super()._real_start() rpc.set_global_manager(self.manager) + if self._embed_api: + self.apiserver = wsgi_service.WSGIService( + 'ironic_api', CONF.api.enable_ssl_api) + self.apiserver.start() + # Start in a known state of no console containers running. # Any enabled console managed by this conductor will be started # after this @@ -67,6 +76,14 @@ class RPCService(rpc_service.BaseRPCService): extend_time = initial_time + datetime.timedelta( seconds=CONF.hash_ring_reset_interval) + # Stop serving the embedded API first to avoid any new requests + try: + if self.apiserver is not None: + self.apiserver.stop() + self.apiserver.wait() + except Exception: + LOG.exception('Service error occurred when stopping the API') + try: self.manager.del_host( deregister=self.deregister_on_shutdown,