Add "none" RPC transport that disables the RPC bus
When using the new combined executable in a single-conductor scenario, it may make sense to completely disable the remote RPC. The new ``rpc_transport`` value ``none`` achieves that. Change-Id: I6a83358c65b3ed213c8a991d42660ca51fc3a8ec Story: #2009676 Task: #44104
This commit is contained in:
parent
9a6f2d101b
commit
019ed2d7b1
@ -92,6 +92,20 @@ You should make the following changes to ``/etc/ironic/ironic.conf``:
|
||||
username = myName
|
||||
password = myPassword
|
||||
|
||||
#. Starting with the Yoga release series, you can use a combined API+conductor
|
||||
service and completely disable the RPC. Set
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[DEFAULT]
|
||||
rpc_transport = none
|
||||
|
||||
and use the ``ironic`` executable to start the combined service.
|
||||
|
||||
.. note::
|
||||
The combined service also works with RPC enabled, which can be useful for
|
||||
some deployments, but may not be advisable for all security models.
|
||||
|
||||
Using CLI
|
||||
---------
|
||||
|
||||
|
@ -30,6 +30,7 @@ def initialize_wsgi_app(argv=sys.argv):
|
||||
i18n.install('ironic')
|
||||
|
||||
service.prepare_command(argv)
|
||||
service.ensure_rpc_transport()
|
||||
|
||||
LOG.debug("Configuration:")
|
||||
CONF.log_opt_values(LOG, log.DEBUG)
|
||||
|
@ -33,6 +33,7 @@ LOG = log.getLogger(__name__)
|
||||
def main():
|
||||
# Parse config file and command line options, then start logging
|
||||
ironic_service.prepare_service('ironic_api', sys.argv)
|
||||
ironic_service.ensure_rpc_transport()
|
||||
|
||||
# Build and start the WSGI app
|
||||
launcher = ironic_service.process_launcher()
|
||||
|
@ -58,6 +58,7 @@ def main():
|
||||
|
||||
# Parse config file and command line options, then start logging
|
||||
ironic_service.prepare_service('ironic_conductor', sys.argv)
|
||||
ironic_service.ensure_rpc_transport(CONF)
|
||||
|
||||
mgr = rpc_service.RPCService(CONF.host,
|
||||
'ironic.conductor.manager',
|
||||
|
@ -53,19 +53,22 @@ class RPCService(service.Service):
|
||||
if CONF.rpc_transport == 'json-rpc':
|
||||
self.rpcserver = json_rpc.WSGIService(
|
||||
self.manager, serializer, context.RequestContext.from_dict)
|
||||
else:
|
||||
elif CONF.rpc_transport != 'none':
|
||||
target = messaging.Target(topic=self.topic, server=self.host)
|
||||
endpoints = [self.manager]
|
||||
self.rpcserver = rpc.get_server(target, endpoints, serializer)
|
||||
self.rpcserver.start()
|
||||
|
||||
if self.rpcserver is not None:
|
||||
self.rpcserver.start()
|
||||
|
||||
self.handle_signal()
|
||||
self.manager.init_host(admin_context)
|
||||
rpc.set_global_manager(self.manager)
|
||||
|
||||
LOG.info('Created RPC server for service %(service)s on host '
|
||||
'%(host)s.',
|
||||
{'service': self.topic, 'host': self.host})
|
||||
LOG.info('Created RPC server with %(transport)s transport for service '
|
||||
'%(service)s on host %(host)s.',
|
||||
{'service': self.topic, 'host': self.host,
|
||||
'transport': CONF.rpc_transport})
|
||||
|
||||
def stop(self):
|
||||
try:
|
||||
|
@ -69,3 +69,11 @@ def prepare_service(name, argv=None, conf=CONF):
|
||||
|
||||
def process_launcher():
|
||||
return service.ProcessLauncher(CONF, restart_method='mutate')
|
||||
|
||||
|
||||
def ensure_rpc_transport(conf=CONF):
|
||||
# Only the combined ironic executable can use rpc_transport = none
|
||||
if conf.rpc_transport == 'none':
|
||||
raise RuntimeError("This service is not designed to work with "
|
||||
"rpc_transport = none. Please use the combined "
|
||||
"ironic executable or another RPC transport.")
|
||||
|
@ -174,10 +174,12 @@ class ConductorAPI(object):
|
||||
self.client = json_rpc.Client(serializer=serializer,
|
||||
version_cap=version_cap)
|
||||
self.topic = ''
|
||||
else:
|
||||
elif CONF.rpc_transport != 'none':
|
||||
target = messaging.Target(topic=self.topic, version='1.0')
|
||||
self.client = rpc.get_client(target, version_cap=version_cap,
|
||||
serializer=serializer)
|
||||
else:
|
||||
self.client = None
|
||||
|
||||
# NOTE(tenbrae): this is going to be buggy
|
||||
self.ring_manager = hash_ring.HashRingManager()
|
||||
@ -203,6 +205,13 @@ class ConductorAPI(object):
|
||||
# conductor.
|
||||
return _LOCAL_CONTEXT
|
||||
|
||||
# A safeguard for the case someone uses rpc_transport=None with no
|
||||
# built-in conductor.
|
||||
if self.client is None:
|
||||
raise exception.ServiceUnavailable(
|
||||
_("Cannot use 'none' RPC to connect to remote conductor %s")
|
||||
% host)
|
||||
|
||||
# Normal RPC path
|
||||
return self.client.prepare(topic=topic, version=version)
|
||||
|
||||
@ -276,13 +285,17 @@ class ConductorAPI(object):
|
||||
"""Get RPC topic name for the current conductor."""
|
||||
return self.topic + "." + CONF.host
|
||||
|
||||
def _can_send_version(self, version):
|
||||
return (self.client.can_send_version(version)
|
||||
if self.client is not None else True)
|
||||
|
||||
def can_send_create_port(self):
|
||||
"""Return whether the RPCAPI supports the create_port method."""
|
||||
return self.client.can_send_version("1.41")
|
||||
return self._can_send_version("1.41")
|
||||
|
||||
def can_send_rescue(self):
|
||||
"""Return whether the RPCAPI supports node rescue methods."""
|
||||
return self.client.can_send_version("1.43")
|
||||
return self._can_send_version("1.43")
|
||||
|
||||
def create_node(self, context, node_obj, topic=None):
|
||||
"""Synchronously, have a conductor validate and create a node.
|
||||
@ -1047,16 +1060,16 @@ class ConductorAPI(object):
|
||||
"""
|
||||
new_kws = {}
|
||||
version = '1.34'
|
||||
if self.client.can_send_version('1.42'):
|
||||
if self._can_send_version('1.42'):
|
||||
version = '1.42'
|
||||
new_kws['agent_version'] = agent_version
|
||||
if self.client.can_send_version('1.49'):
|
||||
if self._can_send_version('1.49'):
|
||||
version = '1.49'
|
||||
new_kws['agent_token'] = agent_token
|
||||
if self.client.can_send_version('1.51'):
|
||||
if self._can_send_version('1.51'):
|
||||
version = '1.51'
|
||||
new_kws['agent_verify_ca'] = agent_verify_ca
|
||||
if self.client.can_send_version('1.54'):
|
||||
if self._can_send_version('1.54'):
|
||||
version = '1.54'
|
||||
new_kws['agent_status'] = agent_status
|
||||
new_kws['agent_status_message'] = agent_status_message
|
||||
@ -1082,7 +1095,7 @@ class ConductorAPI(object):
|
||||
:returns: The result of the action method, which may (or may not)
|
||||
be an instance of the implementing VersionedObject class.
|
||||
"""
|
||||
if not self.client.can_send_version('1.31'):
|
||||
if not self._can_send_version('1.31'):
|
||||
raise NotImplementedError(_('Incompatible conductor version - '
|
||||
'please upgrade ironic-conductor '
|
||||
'first'))
|
||||
@ -1108,7 +1121,7 @@ class ConductorAPI(object):
|
||||
:returns: A tuple with the updates made to the object and
|
||||
the result of the action method
|
||||
"""
|
||||
if not self.client.can_send_version('1.31'):
|
||||
if not self._can_send_version('1.31'):
|
||||
raise NotImplementedError(_('Incompatible conductor version - '
|
||||
'please upgrade ironic-conductor '
|
||||
'first'))
|
||||
@ -1133,7 +1146,7 @@ class ConductorAPI(object):
|
||||
upgrade
|
||||
:returns: The downgraded instance of objinst
|
||||
"""
|
||||
if not self.client.can_send_version('1.31'):
|
||||
if not self._can_send_version('1.31'):
|
||||
raise NotImplementedError(_('Incompatible conductor version - '
|
||||
'please upgrade ironic-conductor '
|
||||
'first'))
|
||||
|
@ -362,7 +362,8 @@ service_opts = [
|
||||
cfg.StrOpt('rpc_transport',
|
||||
default='oslo',
|
||||
choices=[('oslo', _('use oslo.messaging transport')),
|
||||
('json-rpc', _('use JSON RPC transport'))],
|
||||
('json-rpc', _('use JSON RPC transport')),
|
||||
('none', _('No RPC, only use local conductor'))],
|
||||
help=_('Which RPC transport implementation to use between '
|
||||
'conductor and API services')),
|
||||
cfg.BoolOpt('minimum_memory_warning_only',
|
||||
|
@ -55,3 +55,25 @@ class TestRPCService(base.TestCase):
|
||||
mock_init_method.assert_called_once_with(self.rpc_svc.manager,
|
||||
mock_ctx.return_value)
|
||||
self.assertIs(rpc.GLOBAL_MANAGER, self.rpc_svc.manager)
|
||||
|
||||
@mock.patch.object(manager.ConductorManager, 'prepare_host', autospec=True)
|
||||
@mock.patch.object(oslo_messaging, 'Target', autospec=True)
|
||||
@mock.patch.object(objects_base, 'IronicObjectSerializer', autospec=True)
|
||||
@mock.patch.object(rpc, 'get_server', autospec=True)
|
||||
@mock.patch.object(manager.ConductorManager, 'init_host', autospec=True)
|
||||
@mock.patch.object(context, 'get_admin_context', autospec=True)
|
||||
def test_start_no_rpc(self, mock_ctx, mock_init_method,
|
||||
mock_rpc, mock_ios, mock_target,
|
||||
mock_prepare_method):
|
||||
CONF.set_override('rpc_transport', 'none')
|
||||
self.rpc_svc.start()
|
||||
|
||||
self.assertIsNone(self.rpc_svc.rpcserver)
|
||||
mock_ctx.assert_called_once_with()
|
||||
mock_target.assert_not_called()
|
||||
mock_rpc.assert_not_called()
|
||||
mock_ios.assert_called_once_with(is_server=True)
|
||||
mock_prepare_method.assert_called_once_with(self.rpc_svc.manager)
|
||||
mock_init_method.assert_called_once_with(self.rpc_svc.manager,
|
||||
mock_ctx.return_value)
|
||||
self.assertIs(rpc.GLOBAL_MANAGER, self.rpc_svc.manager)
|
||||
|
@ -77,6 +77,12 @@ class RPCAPITestCase(db_base.DbTestCase):
|
||||
self.context, objects.Node(), self.fake_node)
|
||||
self.fake_portgroup = db_utils.get_test_portgroup()
|
||||
|
||||
def test_rpc_disabled(self):
|
||||
CONF.set_override('rpc_transport', 'none')
|
||||
rpcapi = conductor_rpcapi.ConductorAPI(topic='fake-topic')
|
||||
self.assertIsNone(rpcapi.client)
|
||||
self.assertTrue(rpcapi._can_send_version('9.99'))
|
||||
|
||||
def test_serialized_instance_has_uuid(self):
|
||||
self.assertIn('uuid', self.fake_node)
|
||||
|
||||
@ -726,6 +732,17 @@ class RPCAPITestCase(db_base.DbTestCase):
|
||||
mock_manager.create_node.assert_called_once_with(
|
||||
mock.sentinel.context, node_obj=mock.sentinel.node)
|
||||
|
||||
@mock.patch.object(rpc, 'GLOBAL_MANAGER',
|
||||
spec_set=conductor_manager.ConductorManager)
|
||||
def test_local_call_with_rpc_disabled(self, mock_manager):
|
||||
CONF.set_override('host', 'fake.host')
|
||||
CONF.set_override('rpc_transport', 'none')
|
||||
rpcapi = conductor_rpcapi.ConductorAPI(topic='fake.topic')
|
||||
rpcapi.create_node(mock.sentinel.context, mock.sentinel.node,
|
||||
topic='fake.topic.fake.host')
|
||||
mock_manager.create_node.assert_called_once_with(
|
||||
mock.sentinel.context, node_obj=mock.sentinel.node)
|
||||
|
||||
@mock.patch.object(rpc, 'GLOBAL_MANAGER',
|
||||
spec_set=conductor_manager.ConductorManager)
|
||||
def test_local_call_host_mismatch(self, mock_manager):
|
||||
@ -738,6 +755,27 @@ class RPCAPITestCase(db_base.DbTestCase):
|
||||
rpcapi.client.prepare.assert_called_once_with(
|
||||
topic='fake.topic.not-fake.host', version=mock.ANY)
|
||||
|
||||
@mock.patch.object(rpc, 'GLOBAL_MANAGER',
|
||||
spec_set=conductor_manager.ConductorManager)
|
||||
def test_local_call_host_mismatch_with_rpc_disabled(self, mock_manager):
|
||||
CONF.set_override('host', 'fake.host')
|
||||
CONF.set_override('rpc_transport', 'none')
|
||||
rpcapi = conductor_rpcapi.ConductorAPI(topic='fake.topic')
|
||||
self.assertRaises(exception.ServiceUnavailable,
|
||||
rpcapi.create_node,
|
||||
mock.sentinel.context, mock.sentinel.node,
|
||||
topic='fake.topic.not-fake.host')
|
||||
|
||||
@mock.patch.object(rpc, 'GLOBAL_MANAGER', None)
|
||||
def test_local_call_no_conductor_with_rpc_disabled(self):
|
||||
CONF.set_override('host', 'fake.host')
|
||||
CONF.set_override('rpc_transport', 'none')
|
||||
rpcapi = conductor_rpcapi.ConductorAPI(topic='fake.topic')
|
||||
self.assertRaises(exception.ServiceUnavailable,
|
||||
rpcapi.create_node,
|
||||
mock.sentinel.context, mock.sentinel.node,
|
||||
topic='fake.topic.fake.host')
|
||||
|
||||
@mock.patch.object(rpc, 'GLOBAL_MANAGER',
|
||||
spec_set=conductor_manager.ConductorManager)
|
||||
def test_local_cast(self, mock_manager):
|
||||
|
5
releasenotes/notes/rpc-none-f05dac657eef4b66.yaml
Normal file
5
releasenotes/notes/rpc-none-f05dac657eef4b66.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds a new ``none`` RPC transport that can be used together with the
|
||||
combined ``ironic`` executable to completely disable the RPC bus.
|
Loading…
Reference in New Issue
Block a user