diff --git a/examples/miniapps/monitoring-daemon-asyncio/config.yml b/examples/miniapps/monitoring-daemon-asyncio/config.yml index c7da7877..5bbe8795 100644 --- a/examples/miniapps/monitoring-daemon-asyncio/config.yml +++ b/examples/miniapps/monitoring-daemon-asyncio/config.yml @@ -7,11 +7,11 @@ monitors: example: method: "GET" url: "http://example.com" + timeout: 5 check_every: 5 - expected_codes: [200] httpbin: method: "GET" url: "http://httpbin.org/get" + timeout: 5 check_every: 5 - expected_codes: [200] diff --git a/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/dispatcher.py b/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/dispatcher.py index 6bbc797e..d12c22b9 100644 --- a/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/dispatcher.py +++ b/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/dispatcher.py @@ -16,27 +16,32 @@ class Dispatcher: def __init__(self, monitors: List[Monitor]) -> None: self._monitors = monitors - self._monitor_tasks: List[asyncio.Task] = [] # type: ignore + self._monitor_tasks: List[asyncio.Task] = [] self._stopping = False def run(self) -> None: - asyncio.run(self._do_work()) + asyncio.run(self.start()) - async def _do_work(self) -> None: + async def start(self) -> None: logger.info('Dispatcher is starting up') for monitor in self._monitors: - self._monitor_tasks.append(asyncio.create_task(self._run_monitor(monitor))) - logger.info('Monitoring task has been started %s', monitor.full_name) + self._monitor_tasks.append( + asyncio.create_task(self._run_monitor(monitor)), + ) + logger.info( + 'Monitoring task has been started %s', + monitor.full_name, + ) - asyncio.get_event_loop().add_signal_handler(signal.SIGTERM, self._stop) - asyncio.get_event_loop().add_signal_handler(signal.SIGINT, self._stop) + asyncio.get_event_loop().add_signal_handler(signal.SIGTERM, self.stop) + asyncio.get_event_loop().add_signal_handler(signal.SIGINT, self.stop) await asyncio.gather(*self._monitor_tasks, return_exceptions=True) - self._stop() + self.stop() - def _stop(self) -> None: + def stop(self) -> None: if self._stopping: return diff --git a/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/http.py b/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/http.py index 5076e5b5..9b07542a 100644 --- a/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/http.py +++ b/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/http.py @@ -1,11 +1,11 @@ """Http client module.""" -from aiohttp import ClientSession, ClientResponse +from aiohttp import ClientSession, ClientTimeout, ClientResponse class HttpClient: - async def request(self, method: str, url: str) -> ClientResponse: - async with ClientSession() as session: + async def request(self, method: str, url: str, timeout: int) -> ClientResponse: + async with ClientSession(timeout=ClientTimeout(timeout)) as session: async with session.request(method, url) as response: return response diff --git a/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/monitors.py b/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/monitors.py index a54bac21..3ffe71a7 100644 --- a/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/monitors.py +++ b/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/monitors.py @@ -1,6 +1,7 @@ """Monitors module.""" import logging +import time from typing import Dict, Any from .http import HttpClient @@ -30,7 +31,7 @@ class HttpMonitor(Monitor): self._client = http_client self._method = options.pop('method') self._url = options.pop('url') - self._expected_codes = options.pop('expected_codes') + self._timeout = options.pop('timeout') super().__init__(check_every=options.pop('check_every')) @property @@ -38,5 +39,20 @@ class HttpMonitor(Monitor): return '{0}.{1}(url="{2}")'.format(__name__, self.__class__.__name__, self._url) async def check(self) -> None: - response = await self._client.request(method=self._method, url=self._url) - self.logger.info('Return response code: %s', response.status) + time_start = time.time() + + response = await self._client.request( + method=self._method, + url=self._url, + timeout=self._timeout, + ) + + time_end = time.time() + time_took = time_end - time_start + + self.logger.info( + 'Response code: %s, content length: %s, request took: %s seconds', + response.status, + response.content_length, + round(time_took, 3) + ) diff --git a/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/tests.py b/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/tests.py index e69de29b..227075a2 100644 --- a/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/tests.py +++ b/examples/miniapps/monitoring-daemon-asyncio/monitoringdaemon/tests.py @@ -0,0 +1,101 @@ +"""Tests module.""" + +import asyncio +import dataclasses +from unittest import mock + +import pytest + +from .containers import ApplicationContainer + + +CONFIG = { + 'log': { + 'level': 'INFO', + 'formant': '[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s', + }, + 'monitors': { + 'example': { + 'method': 'GET', + 'url': 'http://fake-example.com', + 'timeout': 1, + 'check_every': 1, + }, + 'httpbin': { + 'method': 'GET', + 'url': 'http://fake-httpbin.org/get', + 'timeout': 1, + 'check_every': 1, + }, + }, +} + + +@dataclasses.dataclass +class RequestStub: + status: int + content_length: int + + +@pytest.fixture +def container(): + container = ApplicationContainer() + container.config.from_dict(CONFIG) + return container + + +@pytest.mark.asyncio +async def test_example_monitor(container, caplog): + caplog.set_level('INFO') + + http_client_mock = mock.AsyncMock() + http_client_mock.request.return_value = RequestStub( + status=200, + content_length=635, + ) + + with container.http_client.override(http_client_mock): + example_monitor = container.example_monitor() + await example_monitor.check() + + assert 'http://fake-example.com' in caplog.text + assert 'Response code: 200' in caplog.text + assert 'content length: 635' in caplog.text + + +@pytest.mark.asyncio +async def test_httpbin_monitor(container, caplog): + caplog.set_level('INFO') + + http_client_mock = mock.AsyncMock() + http_client_mock.request.return_value = RequestStub( + status=200, + content_length=482, + ) + + with container.http_client.override(http_client_mock): + httpbin_monitor = container.httpbin_monitor() + await httpbin_monitor.check() + + assert 'http://fake-httpbin.org/get' in caplog.text + assert 'Response code: 200' in caplog.text + assert 'content length: 482' in caplog.text + + +@pytest.mark.asyncio +async def test_dispatcher(container, caplog, event_loop): + caplog.set_level('INFO') + + example_monitor_mock = mock.AsyncMock() + httpbin_monitor_mock = mock.AsyncMock() + + with container.example_monitor.override(example_monitor_mock), \ + container.httpbin_monitor.override(httpbin_monitor_mock): + + dispatcher = container.dispatcher() + event_loop.create_task(dispatcher.start()) + await asyncio.sleep(0.1) + dispatcher.stop() + + assert example_monitor_mock.check.called + assert httpbin_monitor_mock.check.called diff --git a/examples/miniapps/monitoring-daemon-asyncio/requirements.txt b/examples/miniapps/monitoring-daemon-asyncio/requirements.txt index 03541e70..ebaadda6 100644 --- a/examples/miniapps/monitoring-daemon-asyncio/requirements.txt +++ b/examples/miniapps/monitoring-daemon-asyncio/requirements.txt @@ -1,3 +1,6 @@ dependency-injector aiohttp pyyaml +pytest +pytest-asyncio +pytest-cov