diff --git a/.editorconfig b/.editorconfig index dba68101b0c71d2aa813d59c4dd8a5c410068da8..df0525fcbe8afa97ae30d6a22e3b0cb7332a7e0e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -149,3 +149,6 @@ ij_yaml_spaces_within_brackets = true [{*.hcl,*.hcl.tmpl,*.nomad,*.hcl.j2}] indent_size = 2 + +[*.proto] +indent_size = 2 diff --git a/.gitignore b/.gitignore index e653de55b5cddd83321df34349a06e3d277ace06..d6ac0c2e4e550e7005cae51a6dacd05dd0e5bb1a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ **/vscode-server.tar **/*.orig **/venv +**/*_pb2.* +**/*_pb2_grpc.* tangostationcontrol/build tangostationcontrol/cover diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 848c5c00ba5d83c39038194886d0cd528899cf4f..f293ca1a0c1a4e9149ec80fb0f7281de746ef4c6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -391,6 +391,7 @@ deploy_nomad: - jupyter - snmp-exporter - landing-page + - rpc-server environment: name: $STATION script: diff --git a/README.md b/README.md index 743942ee22ebc6b18ebefb17414d798ab6326d97..19f96a99e3c6c2801906e925be81a6be9a487237 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ Next change the version in the following places: # Release Notes +* 0.34.0 Add gRPC based 'external' station control service * 0.33.3 Add support for new PCON devices (ACX) * 0.33.2 Fix for XSTs in Observations: Also write FPGA_xst_nof_crosslets_RW * 0.33.1 SDPFirmware: replace FPGA_ucp monitoring points with new TR_ucp ones diff --git a/infra/jobs/station/rpc-server.levant.nomad b/infra/jobs/station/rpc-server.levant.nomad new file mode 100644 index 0000000000000000000000000000000000000000..2ff5431b872f5d5f16d102277f3e1f34a75f25af --- /dev/null +++ b/infra/jobs/station/rpc-server.levant.nomad @@ -0,0 +1,42 @@ +job "rpc-server" { + datacenters = ["stat"] + type = "service" + reschedule { + unlimited = true + delay = "30s" + delay_function = "constant" + } + group "station-rpc" { + network { + mode = "bridge" + + port "grpc" { + static = "50051" + host_network = "external" + } + } + + service { + name = "rpc" + port = "grpc" + } + + task "l2ss-rpc-server" { + driver = "docker" + + config { + image = "[[ $.registry.astron.url ]]/lofar-device-base:[[ $.image_tag ]]" + entrypoint = [""] + command = "l2ss-rpc-server" + } + env { + TANGO_HOST = "tango.service.consul:10000" + } + + resources { + cpu = 25 + memory = 512 + } + } + } +} diff --git a/tangostationcontrol/VERSION b/tangostationcontrol/VERSION index 90d7d2b462d7ddd7b29c887c866893205e8f30b9..85e60ed180c43f96ad25d7f1ecab8ef2461a75d9 100644 --- a/tangostationcontrol/VERSION +++ b/tangostationcontrol/VERSION @@ -1 +1 @@ -0.33.3 +0.34.0 diff --git a/tangostationcontrol/proto/observation.proto b/tangostationcontrol/proto/observation.proto new file mode 100644 index 0000000000000000000000000000000000000000..3a08fe61c83d546fcda1012a641227ff7246e00d --- /dev/null +++ b/tangostationcontrol/proto/observation.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +service Observation { + rpc StartObservation(StartObservationRequest) returns (ObservationReply){} + rpc StopObservation(StopObservationRequest) returns (ObservationReply) {} +} + +message StartObservationRequest { + string configuration = 1; +} +message StopObservationRequest { + int64 observation_id = 1; +} + +message ObservationReply { + bool success = 1; + string exception = 2; +} diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt index d6d27e18c61951a01b1fc234b4314b087c6ff155..6b628f3e0c3c3cd3f7dde3b2f0171b534b600c91 100644 --- a/tangostationcontrol/requirements.txt +++ b/tangostationcontrol/requirements.txt @@ -23,3 +23,5 @@ prometheus-client # Apache 2 dnspython # ISC logfmter # MIT psutil >= 5.4.6 # BSD3 +grpcio # Apache 2 +grpcio-tools # Apache 2 diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg index f9594fe96de49f5420aad7bf8ba5850ff30f1edb..4779dcb7c988133388cff6d6fbf21f6ff3bb5b45 100644 --- a/tangostationcontrol/setup.cfg +++ b/tangostationcontrol/setup.cfg @@ -35,6 +35,7 @@ console_scripts = l2ss-analyse-dsconfig-hierarchies = tangostationcontrol.toolkit.analyse_dsconfig_hierarchies:main l2ss-version = tangostationcontrol:print_version l2ss-health = tangostationcontrol.common.health:main + l2ss-rpc-server = tangostationcontrol.rpc.server:main # The following entry points should eventually be removed / replaced l2ss-hardware-device-template = tangostationcontrol.examples.HW_device_template:main diff --git a/tangostationcontrol/tangostationcontrol/rpc/__init__.py b/tangostationcontrol/tangostationcontrol/rpc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/rpc/_proto/__init__.py b/tangostationcontrol/tangostationcontrol/rpc/_proto/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/rpc/server.py b/tangostationcontrol/tangostationcontrol/rpc/server.py new file mode 100644 index 0000000000000000000000000000000000000000..23a220bbea175c948ea2439743703135ff9f32f4 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/rpc/server.py @@ -0,0 +1,57 @@ +from concurrent import futures + +import grpc +import logging + +from ._proto import observation_pb2 +from ._proto import observation_pb2_grpc +from tangostationcontrol.common.lofar_logging import configure_logger, exception_to_str +from tangostationcontrol.common.proxy import create_proxy + +logger = logging.getLogger() + +TIMEOUT = 5000 +PORT = 50051 + + +class Observation(observation_pb2_grpc.ObservationServicer): + def StartObservation( + self, request: observation_pb2.StartObservationRequest, context + ): + logger.info("Start observation") + try: + observation_control = create_proxy("STAT/ObservationControl/1", TIMEOUT) + observation_control.add_observation(request.configuration) + return observation_pb2.ObservationReply(success=True) + except Exception as e: + logger.exception(f"Starting observation failed: {exception_to_str(e)}") + return observation_pb2.ObservationReply( + success=False, exception=exception_to_str(e) + ) + + def StopObservation(self, request: observation_pb2.StopObservationRequest, context): + logger.info(f"Stop observation {request.observation_id}") + try: + observation_control = create_proxy("STAT/ObservationControl/1", TIMEOUT) + observation_control.stop_observation_now(request.observation_id) + return observation_pb2.ObservationReply(success=True) + except Exception as e: + logger.exception(f"Stopping observation failed: {exception_to_str(e)}") + return observation_pb2.ObservationReply( + success=False, exception=exception_to_str(e) + ) + + +def main(): + configure_logger() + logger.info(f"Start server on insecure port 0.0.0.0:{PORT}") + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + observation_pb2_grpc.add_ObservationServicer_to_server(Observation(), server) + server.add_insecure_port(f"0.0.0.0:{PORT}") + server.start() + logger.info(f"Server started, listening on port {PORT}") + server.wait_for_termination() + + +if __name__ == "__main__": + main() diff --git a/tangostationcontrol/tox.ini b/tangostationcontrol/tox.ini index 499efc4f571bfa6e6626dda90a79c1724afe4d8a..79aa5947d0458c6d1c3ee6202c38ffc255c18be8 100644 --- a/tangostationcontrol/tox.ini +++ b/tangostationcontrol/tox.ini @@ -94,7 +94,9 @@ commands = [testenv:build] usedevelop = False -commands = {envpython} -m build +commands = + {envpython} -m grpc_tools.protoc -Itangostationcontrol/rpc/_proto=proto --python_out=. --pyi_out=. --grpc_python_out=. proto/observation.proto + {envpython} -m build [testenv:docs] envdir = {toxworkdir}/docs