Compare commits

..

10 Commits

Author SHA1 Message Date
Stefan Allius
f8ac43f183 set BUILD_ID only for dev and debug versions 2025-04-13 23:44:59 +02:00
Stefan Allius
279a6c030c bump version to 0.14.0 2025-04-13 23:44:25 +02:00
Stefan Allius
586a5d5c3d Merge branch 'main' of https://github.com/s-allius/tsun-gen3-proxy into start-version-0.14 2025-04-13 23:42:57 +02:00
Stefan Allius
00657c31f3 Fix rel build (#372)
* build rel without BUILD_ID

* update changelog
2025-04-13 21:22:45 +02:00
Stefan Allius
22ebad2edb Update rel 0.13.0 (#371)
* update compose help link

(cherry picked from commit 6d4ff0d508)

* fix link

(cherry picked from commit 3d422f9249)

* retrigger sonar qube test run

* fix rel build run

* bump version
2025-04-13 20:57:31 +02:00
Stefan Allius
e3c2672ea9 Fix rel build (#369)
* disable cache for rc build

* bump python version to 3.12.10-r0
2025-04-13 20:37:34 +02:00
Stefan Allius
86d9fc8c8f Update rel 0.13.0 (#366)
* update compose help link

(cherry picked from commit 6d4ff0d508)

* fix link

(cherry picked from commit 3d422f9249)

* fix rel build run
2025-04-13 20:02:10 +02:00
Stefan Allius
9031b5c793 Update rel 0.13.0 (#365)
* update compose help link

(cherry picked from commit 6d4ff0d508)

* fix link

(cherry picked from commit 3d422f9249)
2025-04-13 19:20:08 +02:00
Stefan Allius
9f27c5a582 fix link
(cherry picked from commit 3d422f9249)
2025-04-13 18:55:16 +02:00
Stefan Allius
1445268b70 Merge pull request #357 from s-allius/main
define the value 2 for the out status (#356)
2025-04-08 00:11:18 +02:00
4 changed files with 72 additions and 94 deletions

View File

@@ -1,4 +1,4 @@
aiomqtt==2.3.2
schema==0.7.7
aiocron==2.1
quart==0.20
aiohttp==3.11.16

View File

@@ -96,8 +96,8 @@ class Proxy():
Infos.new_stat_data[key] = False
@classmethod
async def class_close(cls, loop) -> None: # pragma: no cover
def class_close(cls, loop) -> None: # pragma: no cover
logging.debug('Proxy.class_close')
logging.info('Close MQTT Task')
await cls.mqtt.close()
loop.run_until_complete(cls.mqtt.close())
cls.mqtt = None

View File

@@ -1,10 +1,11 @@
import logging
import asyncio
import logging.handlers
import signal
import os
import argparse
from asyncio import StreamReader, StreamWriter
from quart import Quart, Response
from aiohttp import web
from logging import config # noqa F401
from proxy import Proxy
from inverter_ifc import InverterIfc
@@ -17,52 +18,61 @@ from cnf.config_read_toml import ConfigReadToml
from cnf.config_read_json import ConfigReadJson
from modbus_tcp import ModbusTcp
class ProxyState:
_is_up = False
@staticmethod
def is_up() -> bool:
return ProxyState._is_up
@staticmethod
def set_up(value: bool):
ProxyState._is_up = value
routes = web.RouteTableDef()
proxy_is_up = False
app = Quart(__name__)
@routes.get('/')
async def hello(request):
return web.Response(text="Hello, world")
@app.route('/')
async def hello():
return Response(response="Hello, world")
@app.route('/-/ready')
async def ready():
if ProxyState.is_up():
@routes.get('/-/ready')
async def ready(request):
if proxy_is_up:
status = 200
text = 'Is ready'
else:
status = 503
text = 'Not ready'
return Response(status=status, response=text)
return web.Response(status=status, text=text)
@app.route('/-/healthy')
async def healthy():
@routes.get('/-/healthy')
async def healthy(request):
if ProxyState.is_up():
if proxy_is_up:
# logging.info('web reqeust healthy()')
for inverter in InverterIfc:
try:
res = inverter.healthy()
if not res:
return Response(status=503, response="I have a problem")
return web.Response(status=503, text="I have a problem")
except Exception as err:
logging.info(f'Exception:{err}')
return Response(status=200, response="I'm fine")
return web.Response(status=200, text="I'm fine")
async def webserver(addr, port):
'''coro running our webserver'''
app = web.Application()
app.add_routes(routes)
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, addr, port)
await site.start()
logging.info(f'HTTP server listen on port: {port}')
try:
# Normal interaction with aiohttp
while True:
await asyncio.sleep(3600) # sleep forever
except asyncio.CancelledError:
logging.info('HTTP server cancelled')
await runner.cleanup()
logging.debug('HTTP cleanup done')
async def handle_client(reader: StreamReader, writer: StreamWriter, inv_class):
@@ -72,13 +82,12 @@ async def handle_client(reader: StreamReader, writer: StreamWriter, inv_class):
await inv.local.ifc.server_loop()
@app.after_serving
async def handle_shutdown(): # pragma: no cover
async def handle_shutdown(loop, web_task):
'''Close all TCP connections and stop the event loop'''
logging.info('Shutdown due to SIGTERM')
loop = asyncio.get_event_loop()
ProxyState.set_up(False)
global proxy_is_up
proxy_is_up = False
#
# first, disc all open TCP connections gracefully
@@ -88,16 +97,24 @@ async def handle_shutdown(): # pragma: no cover
logging.info('Proxy disconnecting done')
#
# second, cancel the web server
#
web_task.cancel()
await web_task
#
# now cancel all remaining (pending) tasks
#
for task in asyncio.all_tasks():
if task == asyncio.current_task():
continue
pending = asyncio.all_tasks()
for task in pending:
task.cancel()
logging.info('Proxy cancelling done')
await Proxy.class_close(loop)
#
# at last, start a coro for stopping the loop
#
logging.debug("Stop event loop")
loop.stop()
def get_log_level() -> int | None:
@@ -197,20 +214,27 @@ def main(): # pragma: no cover
loop.create_task(asyncio.start_server(lambda r, w, i=inv_class:
handle_client(r, w, i),
'0.0.0.0', port))
web_task = loop.create_task(webserver('0.0.0.0', 8127))
#
# Register some UNIX Signal handler for a gracefully server shutdown
# on Docker restart and stop
#
for signame in ('SIGINT', 'SIGTERM'):
loop.add_signal_handler(getattr(signal, signame),
lambda loop=loop: asyncio.create_task(
handle_shutdown(loop, web_task)))
loop.set_debug(log_level == logging.DEBUG)
try:
ProxyState.set_up(True)
logging.info("Start Quart")
app.run(host='0.0.0.0', port=8127, use_reloader=False, loop=loop)
logging.info("Quart stopped")
global proxy_is_up
proxy_is_up = True
loop.run_forever()
except KeyboardInterrupt:
pass
except asyncio.exceptions.CancelledError:
logging.info("Quart cancelled")
finally:
logging.info("Event loop is stopped")
Proxy.class_close(loop)
logging.debug('Close event loop')
loop.close()
logging.info(f'Finally, exit Server "{serv_name}"')

View File

@@ -3,9 +3,7 @@ import pytest
import logging
import os
from mock import patch
from server import get_log_level, app, ProxyState
pytest_plugins = ('pytest_asyncio',)
from server import get_log_level
def test_get_log_level():
@@ -32,47 +30,3 @@ def test_get_log_level():
with patch.dict(os.environ, {'LOG_LVL': 'UNKNOWN'}):
log_lvl = get_log_level()
assert log_lvl == None
@pytest.mark.asyncio
async def test_home():
"""Test the home route."""
client = app.test_client()
response = await client.get('/')
assert response.status_code == 200
result = await response.get_data()
assert result == b"Hello, world"
@pytest.mark.asyncio
async def test_ready():
"""Test the ready route."""
ProxyState.set_up(False)
client = app.test_client()
response = await client.get('/-/ready')
assert response.status_code == 503
result = await response.get_data()
assert result == b"Not ready"
ProxyState.set_up(True)
response = await client.get('/-/ready')
assert response.status_code == 200
result = await response.get_data()
assert result == b"Is ready"
@pytest.mark.asyncio
async def test_healthy():
"""Test the healthy route."""
ProxyState.set_up(False)
client = app.test_client()
response = await client.get('/-/healthy')
assert response.status_code == 200
result = await response.get_data()
assert result == b"I'm fine"
ProxyState.set_up(True)
response = await client.get('/-/healthy')
assert response.status_code == 200
result = await response.get_data()
assert result == b"I'm fine"