Add jsonrpc client port capability

Adds the capability for the topic to contain a port to which
the json rpc client will connect to. This allows for distinct
json-rpc targets to be configured in an environment.

Change-Id: I999316880639cd410543eb54475b0c647b35147b
This commit is contained in:
Julia Kreger 2023-03-31 15:50:14 -07:00
parent ec6ba65392
commit 0a62259018
3 changed files with 45 additions and 6 deletions

View File

@ -82,18 +82,34 @@ class Client(object):
return _can_send_version(version, self.version_cap) return _can_send_version(version, self.version_cap)
def prepare(self, topic, version=None): def prepare(self, topic, version=None):
"""Prepare the client to transmit a request.
:param topic: Topic which is being addressed. Typically this
is the hostname of the remote json-rpc service.
:param version: The RPC API version to utilize.
"""
host = topic.split('.', 1)[1] host = topic.split('.', 1)[1]
# NOTE(TheJulia): Originally, I was worried about ip addresses being
# used, but looking at the topic split, it would have never really
# worked.
host, port = netutils.parse_host_port(host)
return _CallContext( return _CallContext(
host, self.serializer, version=version, host, self.serializer, version=version,
version_cap=self.version_cap, version_cap=self.version_cap,
allowed_exception_namespaces=self.allowed_exception_namespaces) allowed_exception_namespaces=self.allowed_exception_namespaces,
port=port)
class _CallContext(object): class _CallContext(object):
"""Wrapper object for compatibility with oslo.messaging API.""" """Wrapper object for compatibility with oslo.messaging API."""
def __init__(self, host, serializer, version=None, version_cap=None, def __init__(self, host, serializer, version=None, version_cap=None,
allowed_exception_namespaces=()): allowed_exception_namespaces=(), port=None):
if not port:
self.port = CONF.json_rpc.port
else:
self.port = int(port)
self.host = host self.host = host
self.serializer = serializer self.serializer = serializer
self.version = version self.version = version
@ -190,7 +206,7 @@ class _CallContext(object):
scheme = 'https' scheme = 'https'
url = '%s://%s:%d' % (scheme, url = '%s://%s:%d' % (scheme,
netutils.escape_ipv6(self.host), netutils.escape_ipv6(self.host),
CONF.json_rpc.port) self.port)
LOG.debug("RPC %s to %s with %s", method, url, LOG.debug("RPC %s to %s with %s", method, url,
strutils.mask_dict_password(body)) strutils.mask_dict_password(body))
try: try:
@ -200,7 +216,6 @@ class _CallContext(object):
raise raise
LOG.debug('RPC %s to %s returned %s', method, url, LOG.debug('RPC %s to %s returned %s', method, url,
strutils.mask_password(result.text or '<None>')) strutils.mask_password(result.text or '<None>'))
if not cast: if not cast:
result = result.json() result = result.json()
self._handle_error(result.get('error')) self._handle_error(result.get('error'))

View File

@ -395,6 +395,7 @@ class TestClient(base.IronicLibTestCase):
} }
cctx = self.client.prepare('foo.2001:db8::1') cctx = self.client.prepare('foo.2001:db8::1')
self.assertEqual('2001:db8::1', cctx.host) self.assertEqual('2001:db8::1', cctx.host)
self.assertEqual(8089, cctx.port)
result = cctx.call(self.context, 'do_something', answer=42) result = cctx.call(self.context, 'do_something', answer=42)
self.assertEqual(42, result) self.assertEqual(42, result)
mock_session.return_value.post.assert_called_once_with( mock_session.return_value.post.assert_called_once_with(
@ -404,18 +405,35 @@ class TestClient(base.IronicLibTestCase):
'params': {'answer': 42, 'context': self.ctx_json}, 'params': {'answer': 42, 'context': self.ctx_json},
'id': self.context.request_id}) 'id': self.context.request_id})
def test_call_ipv6_success_rfc2732(self, mock_session):
response = mock_session.return_value.post.return_value
response.json.return_value = {
'jsonrpc': '2.0',
'result': 42
}
cctx = self.client.prepare('foo.[2001:db8::1]:8192')
self.assertEqual('2001:db8::1', cctx.host)
result = cctx.call(self.context, 'do_something', answer=42)
self.assertEqual(42, result)
mock_session.return_value.post.assert_called_once_with(
'http://[2001:db8::1]:8192',
json={'jsonrpc': '2.0',
'method': 'do_something',
'params': {'answer': 42, 'context': self.ctx_json},
'id': self.context.request_id})
def test_call_success_with_version(self, mock_session): def test_call_success_with_version(self, mock_session):
response = mock_session.return_value.post.return_value response = mock_session.return_value.post.return_value
response.json.return_value = { response.json.return_value = {
'jsonrpc': '2.0', 'jsonrpc': '2.0',
'result': 42 'result': 42
} }
cctx = self.client.prepare('foo.example.com', version='1.42') cctx = self.client.prepare('foo.example.com:8192', version='1.42')
self.assertEqual('example.com', cctx.host) self.assertEqual('example.com', cctx.host)
result = cctx.call(self.context, 'do_something', answer=42) result = cctx.call(self.context, 'do_something', answer=42)
self.assertEqual(42, result) self.assertEqual(42, result)
mock_session.return_value.post.assert_called_once_with( mock_session.return_value.post.assert_called_once_with(
'http://example.com:8089', 'http://example.com:8192',
json={'jsonrpc': '2.0', json={'jsonrpc': '2.0',
'method': 'do_something', 'method': 'do_something',
'params': {'answer': 42, 'context': self.ctx_json, 'params': {'answer': 42, 'context': self.ctx_json,

View File

@ -0,0 +1,6 @@
---
features:
- |
Adds the capability for the ``json_rpc`` client to identify and utilize
a specific port from the supplied ``topic`` field as opposed to the
default configured port.