From 4d8d93f75e8b3c82c16d14874ecb1aab5369a18b Mon Sep 17 00:00:00 2001 From: Iain Sproat <68657+iainsproat@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:33:54 +0100 Subject: [PATCH] feat(ifc file importer): add healthcheck endpoint (#5232) * Serve path `/healthz` * Include readiness and startup probes --- .../src/nextGen/healthcheck.ts | 2 +- packages/ifc-import-service/main.py | 18 ++++++++++ .../src/ifc_importer/job_processor.py | 4 ++- .../fileimport_service/deployment.yml | 33 +++++++++++++++++++ .../ifc_import_service/deployment.yml | 30 +++++++++++++---- 5 files changed, 78 insertions(+), 9 deletions(-) diff --git a/packages/fileimport-service/src/nextGen/healthcheck.ts b/packages/fileimport-service/src/nextGen/healthcheck.ts index 92f4ba37a..a15f59c43 100644 --- a/packages/fileimport-service/src/nextGen/healthcheck.ts +++ b/packages/fileimport-service/src/nextGen/healthcheck.ts @@ -14,7 +14,7 @@ export const startHealthCheckServer = (params: { logger: Logger }) => { }) server.listen(9080, '0.0.0.0', () => { - logger.info('Server running, listening to 0.0.0.0 and path /healthz') + logger.info('Healthcheck server is running. Serving at 0.0.0.0:9080/healthz') }) return server diff --git a/packages/ifc-import-service/main.py b/packages/ifc-import-service/main.py index d62005402..a0b3a662a 100644 --- a/packages/ifc-import-service/main.py +++ b/packages/ifc-import-service/main.py @@ -1,6 +1,8 @@ import asyncio import logging import sys +import threading +from http.server import BaseHTTPRequestHandler, HTTPServer import structlog from structlog_to_seq import CelfProcessor @@ -31,9 +33,25 @@ def configure_logger() -> structlog.stdlib.BoundLogger: return logger +class HealthcheckHTTPRequestHandler(BaseHTTPRequestHandler): + def do_GET(self): # noqa: N802 + match self.path: + case "/healthz": + self.send_response(200) + self.send_header("Content-type", "application/json") + self.end_headers() + self.wfile.write(b'{"status": "OK"}') + case _: + self.send_response(404) + self.end_headers() + + async def main(): logger = configure_logger() task = asyncio.create_task(job_processor(logger)) + httpd = HTTPServer(("0.0.0.0", 9080), HealthcheckHTTPRequestHandler) + thread = threading.Thread(target=httpd.serve_forever, daemon=True) + thread.start() # we do not need any sort of signal handling logic, # cause if the context of the job transaction exits, diff --git a/packages/ifc-import-service/src/ifc_importer/job_processor.py b/packages/ifc-import-service/src/ifc_importer/job_processor.py index 88e4d0bc2..36484e40c 100644 --- a/packages/ifc-import-service/src/ifc_importer/job_processor.py +++ b/packages/ifc-import-service/src/ifc_importer/job_processor.py @@ -69,11 +69,13 @@ async def job_handler( async def job_processor(logger: structlog.stdlib.BoundLogger): parser = "speckle_ifc" + logger = logger.bind(parser=parser) connection = await setup_connection() + logger.info("job processor started") while True: job = await get_next_job(connection) if not job: - logger.info("no job found") + logger.debug("no job found") await asyncio.sleep(IDLE_TIMEOUT) continue diff --git a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml index 5aeb5ce48..0c77694d1 100644 --- a/utils/helm/speckle-server/templates/fileimport_service/deployment.yml +++ b/utils/helm/speckle-server/templates/fileimport_service/deployment.yml @@ -37,9 +37,42 @@ spec: containerPort: 9093 protocol: TCP + startupProbe: + periodSeconds: 10 + failureThreshold: 18 # 10 * 18 = 180s to startup + timeoutSeconds: 3 + {{- if .Values.featureFlags.nextGenFileImporterEnabled }} + httpGet: + path: /healthz + port: 9080 + {{- else }} + exec: + command: + - /usr/bin/node + - -e + - "process.exit((Date.now() - require('fs').readFileSync('/tmp/last_successful_query', 'utf8') > 25 * 60 * 1000) ? 1 : 0)" + {{- end }} + livenessProbe: initialDelaySeconds: 60 periodSeconds: 60 + timeoutSeconds: 3 + {{- if .Values.featureFlags.nextGenFileImporterEnabled }} + httpGet: + path: /healthz + port: 9080 + {{- else }} + exec: + command: + - /usr/bin/node + - -e + - "process.exit((Date.now() - require('fs').readFileSync('/tmp/last_successful_query', 'utf8') > 25 * 60 * 1000) ? 1 : 0)" + {{- end }} + + readinessProbe: + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 3 {{- if .Values.featureFlags.nextGenFileImporterEnabled }} httpGet: path: /healthz diff --git a/utils/helm/speckle-server/templates/ifc_import_service/deployment.yml b/utils/helm/speckle-server/templates/ifc_import_service/deployment.yml index b7c9188df..45256ac46 100644 --- a/utils/helm/speckle-server/templates/ifc_import_service/deployment.yml +++ b/utils/helm/speckle-server/templates/ifc_import_service/deployment.yml @@ -31,13 +31,29 @@ spec: containerPort: 9093 protocol: TCP - # TODO: Enable health checks - # livenessProbe: - # initialDelaySeconds: 60 - # periodSeconds: 60 - # httpGet: - # path: /healthz - # port: 9080 + startupProbe: + periodSeconds: 10 + failureThreshold: 18 # 10 * 18 = 180s to startup + timeoutSeconds: 3 + httpGet: + path: /healthz + port: 9080 + + livenessProbe: + initialDelaySeconds: 60 + periodSeconds: 60 + timeoutSeconds: 3 + httpGet: + path: /healthz + port: 9080 + + readinessProbe: + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 3 + httpGet: + path: /healthz + port: 9080 resources: {{- with .Values.ifc_import_service.requests }}