diff --git a/.gitignore b/.gitignore index d9393d0..4754ae4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /config.json -*.egginfo +*.egg-info build dist -.pytest_cache \ No newline at end of file +.pytest_cache +__pycache__ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f8b1f1b..0de07c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,17 +7,22 @@ ENV OVOS_CONFIG_BASE_FOLDER=neon ENV OVOS_CONFIG_FILENAME=diana.yaml ENV OVOS_DEFAULT_CONFIG=/opt/neon/diana.yaml ENV XDG_CONFIG_HOME=/config +ENV HEALTHCHECK_PORT=8000 + COPY docker_overlay/ / RUN apt-get update && \ apt-get install -y \ gcc \ + curl \ + jq \ python3 \ python3-dev \ && pip install wheel -ADD . /neon_api_proxy +COPY . /neon_api_proxy WORKDIR /neon_api_proxy -RUN pip install . +RUN pip install --no-cache-dir . -CMD ["neon_api_proxy"] \ No newline at end of file +HEALTHCHECK CMD "/opt/neon/healthcheck.sh" +CMD ["neon_api_proxy"] diff --git a/docker_overlay/opt/neon/healthcheck.sh b/docker_overlay/opt/neon/healthcheck.sh new file mode 100755 index 0000000..94c11a5 --- /dev/null +++ b/docker_overlay/opt/neon/healthcheck.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2025 Neongecko.com Inc. +# BSD-3 +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +port="${HEALTHCHECK_PORT:-8000}" +# Perform the health check using curl +resp_content=$(curl -s http://localhost:${port}/status) +status=$(echo "${resp_content}" | jq -r '.status') +if [ "${status}" == "Ready" ]; then + exit 0 # Success +else + echo "Health check failed with response: ${resp_content}" >&2 + exit 1 # Failure +fi diff --git a/neon_api_proxy/__main__.py b/neon_api_proxy/__main__.py index de48642..92a34a2 100644 --- a/neon_api_proxy/__main__.py +++ b/neon_api_proxy/__main__.py @@ -26,8 +26,10 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from os import environ from ovos_utils import wait_for_exit_signal from ovos_utils.log import init_service_logger +from neon_utils.process_utils import start_health_check_server from neon_api_proxy.api_connector import NeonAPIMQConnector from neon_api_proxy.controller import NeonAPIProxyController @@ -43,6 +45,11 @@ def run_mq_handler(): connector = NeonAPIMQConnector(config=None, service_name='neon_api_connector', proxy=proxy) + if status_port := environ.get("HEALTHCHECK_PORT"): + start_health_check_server( + connector.status, + int(status_port), + connector.check_health) connector.run() wait_for_exit_signal() diff --git a/neon_api_proxy/api_connector.py b/neon_api_proxy/api_connector.py index daae3b8..1a9a042 100644 --- a/neon_api_proxy/api_connector.py +++ b/neon_api_proxy/api_connector.py @@ -30,6 +30,7 @@ from typing import Optional from ovos_utils.log import LOG +from ovos_utils.process_utils import ProcessStatus from neon_mq_connector.utils.network_utils import b64_to_dict, dict_to_b64 from neon_mq_connector.connector import MQConnector @@ -48,10 +49,17 @@ def __init__(self, config: Optional[dict], service_name: str, :param service_name: name of the service instance """ super().__init__(config, service_name) - + self.status = ProcessStatus(self.service_name) + self.status.set_alive() self.vhost = '/neon_api' self.proxy = proxy + def check_health(self) -> bool: + if not MQConnector.check_health(self): + self.status.set_error("MQConnector health check failed") + return False + return self.status.check_ready() + def handle_api_input(self, channel: pika.channel.Channel, method: pika.spec.Basic.Deliver, @@ -126,10 +134,19 @@ def extract_agent_tokens(msg_data: dict) -> dict: def handle_error(self, thread, exception): LOG.error(f"{exception} occurred in {thread}") - LOG.info(f"Restarting Consumers") + LOG.info("Restarting Consumers") self.stop() self.run() + def stop(self): + self.status.set_stopping() + MQConnector.stop(self) + + def run(self): + MQConnector.run(self) + LOG.info("API Connector is running") + self.status.set_ready() + def pre_run(self, **kwargs): self.register_consumer("neon_api_consumer", self.vhost, 'neon_api_input', self.handle_api_input, diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 2a42eeb..61fb6e1 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,6 +1,6 @@ requests-cache~=0.6,>=0.6.4 requests~=2.20 -neon-utils[network,sentry]~=1.0 +neon-utils[network,sentry]~=1.0, >=1.12.2a2 ovos-utils>=0.0.31,<0.2.0 ovos-config~=0.1 neon-mq-connector~=0.8,>=0.8.1a3