runtime: handle GRC servers without XML-RPC introspection

GRC's SimpleXMLRPCServer uses register_instance() which doesn't
expose system.listMethods. Wrap the connectivity check in a
try/except so a Fault is treated as "connected" while
ConnectionRefusedError still propagates.
This commit is contained in:
Ryan Malloy 2026-01-28 20:46:11 -07:00
parent b6a031acc2
commit c793208932
4 changed files with 32 additions and 7 deletions

View File

@ -235,7 +235,9 @@ class ThriftMiddleware:
units=prop.units if hasattr(prop, "units") else None,
min_value=prop.min.value if prop.min else None,
max_value=prop.max.value if prop.max else None,
default_value=prop.defaultvalue.value if prop.defaultvalue else None,
default_value=(
prop.defaultvalue.value if prop.defaultvalue else None
),
knob_type=knob_type,
)
)
@ -287,9 +289,7 @@ class ThriftMiddleware:
avg_work_time_us=metrics.get("avg work time", 0.0),
total_work_time_us=metrics.get("total work time", 0.0),
avg_nproduced=metrics.get("avg nproduced", 0.0),
input_buffer_pct=self._to_list(
metrics.get("avg input % full", [])
),
input_buffer_pct=self._to_list(metrics.get("avg input % full", [])),
output_buffer_pct=self._to_list(
metrics.get("avg output % full", [])
),

View File

@ -31,8 +31,16 @@ class XmlRpcMiddleware:
transport = xmlrpc.client.Transport()
transport.timeout = XMLRPC_TIMEOUT
proxy = xmlrpc.client.ServerProxy(url, transport=transport)
# Verify connectivity
proxy.system.listMethods()
# Verify connectivity — GRC's SimpleXMLRPCServer uses
# register_instance() which doesn't enable system.listMethods.
# A Fault means the server responded (connected); only network
# errors like ConnectionRefused indicate no server.
try:
proxy.system.listMethods()
except ConnectionRefusedError:
raise # actual connectivity failure — propagate
except Exception as e:
logger.debug("Introspection unavailable (normal for GRC servers): %s", e)
logger.info("Connected to XML-RPC at %s", url)
return cls(proxy, url)

View File

@ -125,7 +125,9 @@ class TestThriftMiddlewareVariables:
"""list_variables excludes performance counter knobs."""
mock_client.getKnobs.return_value = {
"sig_source0::frequency": MockKnob("sig_source0::frequency", 1e6, 5),
"null_sink0::avg throughput": MockKnob("null_sink0::avg throughput", 1e9, 5),
"null_sink0::avg throughput": MockKnob(
"null_sink0::avg throughput", 1e9, 5
),
}
variables = thrift_middleware.list_variables()

View File

@ -53,6 +53,21 @@ class TestConnect:
with pytest.raises(ConnectionRefusedError):
XmlRpcMiddleware.connect("http://localhost:8080")
def test_connect_without_introspection(self):
"""GRC servers don't enable system.listMethods — connect should still succeed."""
from xmlrpc.client import Fault
with patch("gnuradio_mcp.middlewares.xmlrpc.xmlrpc.client") as mock_xmlrpc:
mock_proxy = MagicMock()
mock_proxy.system.listMethods.side_effect = Fault(
1, "method 'system.listMethods' is not supported"
)
mock_xmlrpc.ServerProxy.return_value = mock_proxy
mock_xmlrpc.Transport.return_value = MagicMock()
mw = XmlRpcMiddleware.connect("http://localhost:8080")
assert mw is not None
class TestConnectionInfo:
def test_get_connection_info(self, xmlrpc_mw, mock_proxy):