Skip to content

Commit 86fc613

Browse files
committed
feat!: replace Pyro4/ZMQ transport with gRPC
Replace the three-port Pyro4+ZMQ architecture with a single-port gRPC service (default port 5556): - Add mtda/grpc/ package: mtda.proto defines all RPCs including a server-streaming Subscribe RPC (replaces ZMQ PUB/SUB console/event stream) and a client-streaming StorageWrite RPC (replaces ZMQ PUSH/PULL storage data path). - Add QueueDataStream in mtda/storage/datastream.py; remove dead NetworkDataStream and its zmq dependency. - Rewrite mtda/console/remote.py: RemoteConsole and RemoteMonitor now subscribe via gRPC instead of ZMQ SUB sockets; topic bytes normalisation ensures correct dispatch. - Fix ConsoleLogger._print(): always call publish() regardless of socket state so console data reaches subscriber queues. - Rewrite Client in mtda/client.py with _GrpcImpl and storage socket adapters; extract session-name generation into a static method (ASCII-only word list prevents invalid gRPC metadata); remove zmq. - Fix mtda-www WebConsole: normalise topic bytes, add reconnect loop, create reader tasks after mtda_start(). - Remove python3-zmq from mtda-service in debian/control; grpcio already present in mtda-common covers all packages transitively. - Update docs: remove obsolete console/data port entries from [remote] config docs; correct default www port (9080 -> 5000). BREAKING CHANGE: the console (5557) and data (5558) ports no longer exist; all traffic flows over the single control port (default 5556). Closes: #524 Signed-off-by: Cedric Hombourger <cedric.hombourger@siemens.com>
1 parent 59bcf7c commit 86fc613

File tree

23 files changed

+1693
-313
lines changed

23 files changed

+1693
-313
lines changed

.github/wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ gnutls
4545
googlegroups
4646
gpg
4747
GPIO
48+
gRPC
4849
GStreamer
4950
hombourger
5051
homekit

.github/workflows/proto.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# ---------------------------------------------------------------------------
2+
# Verify mtda.proto is valid and the build step can generate stubs from it
3+
# ---------------------------------------------------------------------------
4+
#
5+
# This software is a part of MTDA.
6+
# Copyright (C) 2026 Siemens AG
7+
#
8+
# ---------------------------------------------------------------------------
9+
# SPDX-License-Identifier: MIT
10+
# ---------------------------------------------------------------------------
11+
12+
name: Proto stubs
13+
14+
on:
15+
pull_request:
16+
paths:
17+
- 'mtda/grpc/mtda.proto'
18+
19+
jobs:
20+
check-stubs:
21+
name: Verify stubs can be generated from mtda.proto
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Install grpcio-tools
28+
run: pip3 install grpcio-tools
29+
30+
- name: Generate stubs
31+
run: |
32+
python3 -m grpc_tools.protoc \
33+
-I mtda/grpc \
34+
--python_out=mtda/grpc \
35+
--grpc_python_out=mtda/grpc \
36+
mtda/grpc/mtda.proto

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*.changes
66
*.img
77
*.pyc
8+
*_pb2.py
9+
*_pb2_grpc.py
810
*.swp
911
build
1012
cip-core

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ Contribution Checklist
1313

1414
- follow the existing coding style (run `pycodestyle` on changed files) [**required**]
1515

16+
- if `mtda/grpc/mtda.proto` is modified, the Python stubs
17+
(`mtda_pb2.py`, `mtda_pb2_grpc.py`) are **not** committed — they are
18+
generated automatically during `python setup.py build` / `pip install`.
19+
The CI will verify that `grpc_tools.protoc` can still consume the updated
20+
`.proto` without errors.
21+
1622
- add the required copyright header to each new file introduced, see
1723
[licensing information](COPYING) [**required**]
1824

configs/docker.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ debug = 2
1818
# ---------------------------------------------------------------------------
1919
# Remote settings
2020
# ---------------------------------------------------------------------------
21+
# Set "control" to the TCP/IP port number for the gRPC control interface
2122
[remote]
2223
control = 5556
23-
console = 5557
2424
host = localhost
2525

2626
# ---------------------------------------------------------------------------

configs/qemu.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ variant=qemu
2424
# ---------------------------------------------------------------------------
2525
# Remote settings
2626
# ---------------------------------------------------------------------------
27+
# Set "control" to the TCP/IP port number for the gRPC control interface
2728
[remote]
2829
control = 5556
2930
console = 5557

debian/control

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ Depends: mtda-common,
3232
python3-systemd,
3333
python3-usb,
3434
python3-zeroconf,
35-
python3-zmq,
3635
python3-zstandard,
3736
usbrelay,
3837
${misc:Depends},
@@ -82,7 +81,7 @@ Package: mtda-common
8281
Architecture: all
8382
Multi-Arch: foreign
8483
Depends: python3:any (>= 3.7~),
85-
python3-pyro5 | python3-pyro4
84+
python3-grpcio
8685
Description: common modules for Multi-Tenant Device Access
8786
Modules shared between the service and the client.
8887

docs/config.rst

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,13 @@ General settings
8383
``pduclient``, ``qemu``, ``shellcmd`` and ``usbrelay``.
8484

8585
* ``remote``: section [optional]
86-
Specify the host and ports to connect to when using a MTDA client (such as
86+
Specify the host and port to connect to when using a MTDA client (such as
8787
``mtda-cli``).
8888

8989
* ``control``: integer [optional]
90-
Remote port listening for control commands (defaults to ``5556``).
91-
92-
* ``console``: integer [optional]
93-
Remote port to connect to in order to get console messages (defaults to
94-
``5557``).
95-
96-
* ``data``: integer [optional]
97-
Remote port for data transfers between the client and agent (defaults to
98-
``0`` for a dynamic port assignment).
90+
Remote port listening for gRPC requests (defaults to ``5556``). All
91+
traffic — control commands, console streaming and data transfers — flows
92+
over this single port.
9993

10094
* ``host``: string [optional]
10195
Remote host name or ip to connect to as a client to interact with the

docs/usage.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ Usage
279279
``http://<MTDA-AGENT-IPC>:<PORT>``
280280

281281
Where <PORT> is as configured in the [www] section of the configuration settings.
282-
Default is 9080.
282+
Default is 5000.
283283
* A web UI with MTDA control options and video stream console will be loaded as shown:
284284

285285
.. image:: www-1.png

mtda-service

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
# System imports
1414
import argparse
15+
import concurrent.futures
1516
import lockfile
1617
import netifaces
1718
import os
@@ -22,16 +23,15 @@ import socket
2223
import zeroconf
2324
from systemd import daemon as sd
2425

26+
# gRPC
27+
import grpc
28+
2529
# Local imports
2630
from mtda.main import MultiTenantDeviceAccess
31+
from mtda.grpc import mtda_pb2_grpc
32+
from mtda.grpc.servicer import MtdaServicer
2733
import mtda.constants as CONSTS
2834

29-
# Pyro
30-
try:
31-
from Pyro5.compatibility import Pyro4
32-
except ImportError:
33-
import Pyro4
34-
3535

3636
class Application:
3737

@@ -79,11 +79,13 @@ class Application:
7979
if status is False:
8080
return False
8181

82-
# Start our RPC server
83-
Pyro4.config.HOST = "0.0.0.0"
84-
Pyro4.config.SERIALIZER = "marshal"
85-
daemon = Pyro4.Daemon(port=self.agent.ctrlport)
86-
daemon.register(self.agent, objectId="mtda.main")
82+
# Start our gRPC server
83+
self._grpc_server = grpc.server(
84+
concurrent.futures.ThreadPoolExecutor(max_workers=10))
85+
mtda_pb2_grpc.add_MtdaServiceServicer_to_server(
86+
MtdaServicer(self.agent), self._grpc_server)
87+
self._grpc_server.add_insecure_port(f'[::]:{self.agent.ctrlport}')
88+
self._grpc_server.start()
8789

8890
# Initialize ZeroConf
8991
interfaces = zeroconf.InterfaceChoice.All
@@ -111,14 +113,16 @@ class Application:
111113

112114
try:
113115
sd.notify('READY=1')
114-
daemon.requestLoop()
116+
self._grpc_server.wait_for_termination()
115117
except KeyboardInterrupt:
116118
self.stop()
117119
return True
118120

119121
def stop(self, signum=0, frame=None):
120122
if self.zerosrv is not None:
121123
self.zeroconf.unregister_service(self.zerosrv)
124+
if hasattr(self, '_grpc_server'):
125+
self._grpc_server.stop(grace=5)
122126
if self.agent is not None:
123127
self.agent.stop()
124128
sys.exit(signum)

0 commit comments

Comments
 (0)