diff --git a/.gitignore b/.gitignore index 5011af1..f59f8c4 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ temp/ coverage.xml setup.py +/extension \ No newline at end of file diff --git a/connect/eaas/dataclasses.py b/connect/eaas/dataclasses.py index dcb59fe..15333aa 100644 --- a/connect/eaas/dataclasses.py +++ b/connect/eaas/dataclasses.py @@ -59,6 +59,7 @@ class TaskPayload: output: Optional[str] = None correlation_id: Optional[str] = None reply_to: Optional[str] = None + runtime: Optional[float] = 0.0 @dataclasses.dataclass @@ -82,6 +83,7 @@ class CapabilitiesPayload: schedulables: Optional[list] = None readme_url: Optional[str] = None changelog_url: Optional[str] = None + runner_version: Optional[str] = None @dataclasses.dataclass diff --git a/connect/eaas/managers/background.py b/connect/eaas/managers/background.py index 1ed55e8..5a61b4e 100644 --- a/connect/eaas/managers/background.py +++ b/connect/eaas/managers/background.py @@ -65,9 +65,10 @@ async def build_response(self, task_data, future): timeout=self.config.get_timeout('background'), ) result_message.result = result.status - elapsed = time.monotonic() - begin_ts + result_message.runtime = time.monotonic() - begin_ts logger.info( - f'background task {task_data.task_id} result: {result.status}, tooks: {elapsed}', + f'background task {task_data.task_id} result: {result.status}, tooks:' + f' {result_message.runtime}', ) if result.status in (ResultType.SKIP, ResultType.FAIL): result_message.output = result.output diff --git a/connect/eaas/managers/interactive.py b/connect/eaas/managers/interactive.py index 134bd8c..fe1fd1b 100644 --- a/connect/eaas/managers/interactive.py +++ b/connect/eaas/managers/interactive.py @@ -45,9 +45,10 @@ async def build_response(self, task_data, future): result_message.result = result.status result_message.data = result.data result_message.output = result.output - elapsed = time.monotonic() - begin_ts + result_message.runtime = time.monotonic() - begin_ts logger.info( - f'interactive task {task_data.task_id} result: {result.status}, tooks: {elapsed}', + f'interactive task {task_data.task_id} result: {result.status}, tooks:' + f' {result_message.runtime}', ) except Exception as e: self.log_exception(task_data, e) diff --git a/connect/eaas/managers/scheduled.py b/connect/eaas/managers/scheduled.py index 01a9710..5d2e2f5 100644 --- a/connect/eaas/managers/scheduled.py +++ b/connect/eaas/managers/scheduled.py @@ -6,6 +6,7 @@ import asyncio import dataclasses import logging +import time import traceback from connect.eaas.dataclasses import ( @@ -39,12 +40,18 @@ async def build_response(self, task_data, future): result = None result_message = TaskPayload(**dataclasses.asdict(task_data)) try: + begin_ts = time.monotonic() result = await asyncio.wait_for( future, timeout=self.config.get_timeout('scheduled'), ) result_message.result = result.status result_message.output = result.output + result_message.runtime = time.monotonic() - begin_ts + logger.info( + f'interactive task {task_data.task_id} result: {result.status}, tooks:' + f' {result_message.runtime}', + ) except Exception as e: self.log_exception(task_data, e) result_message.result = ResultType.RETRY diff --git a/connect/eaas/worker.py b/connect/eaas/worker.py index 8abc7cf..1f6c5e8 100644 --- a/connect/eaas/worker.py +++ b/connect/eaas/worker.py @@ -40,7 +40,7 @@ StopBackoffError, ) from connect.eaas.handler import ExtensionHandler -from connect.eaas.helpers import to_ordinal +from connect.eaas.helpers import get_version, to_ordinal from connect.eaas.managers import ( BackgroundTasksManager, InteractiveTasksManager, @@ -196,6 +196,7 @@ def get_capabilities(self): self.handler.schedulables, self.handler.readme, self.handler.changelog, + get_version(), ), ), ) diff --git a/tests/conftest.py b/tests/conftest.py index c61d5e2..af7c9c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -77,11 +77,12 @@ def config_payload(): @pytest.fixture def task_payload(): - def _task_payload(task_category, task_type, object_id): + def _task_payload(task_category, task_type, object_id, runtime=0.0): return { 'task_id': 'TQ-000', 'task_category': task_category, 'task_type': task_type, 'object_id': object_id, + 'runtime': runtime, } return _task_payload diff --git a/tests/managers/test_background.py b/tests/managers/test_background.py index 9bba52f..f405639 100644 --- a/tests/managers/test_background.py +++ b/tests/managers/test_background.py @@ -1,4 +1,5 @@ import asyncio +import time import pytest @@ -40,6 +41,9 @@ async def test_sync(mocker, extension_cls, task_type, config_payload): ) mocker.patch('connect.eaas.handler.get_extension_class') mocker.patch('connect.eaas.handler.get_extension_type') + mocked_time = mocker.patch('connect.eaas.managers.background.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = ExtensionHandler(config) handler.extension_class = extension_cls(TASK_TYPE_EXT_METHOD_MAP[task_type]) handler.extension_type = 'sync' @@ -53,6 +57,7 @@ async def test_sync(mocker, extension_cls, task_type, config_payload): TaskCategory.BACKGROUND, task_type, 'PR-000', + runtime=1.0, ) await manager.submit(task) @@ -78,6 +83,9 @@ async def test_async(mocker, extension_cls, task_type, config_payload): ) mocker.patch('connect.eaas.handler.get_extension_class') mocker.patch('connect.eaas.handler.get_extension_type') + mocked_time = mocker.patch('connect.eaas.managers.background.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = ExtensionHandler(config) handler.extension_class = extension_cls(TASK_TYPE_EXT_METHOD_MAP[task_type], async_impl=True) handler.extension_type = 'async' @@ -91,6 +99,7 @@ async def test_async(mocker, extension_cls, task_type, config_payload): TaskCategory.BACKGROUND, task_type, 'PR-000', + runtime=1.0, ) await manager.submit(task) @@ -970,11 +979,14 @@ async def test_build_response_exception(mocker, task_payload): async def test_send_skip_response(mocker, task_payload): config = ConfigHelper() mocked_put = mocker.AsyncMock() + mocked_time = mocker.patch('connect.eaas.managers.background.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) manager = BackgroundTasksManager(config, None, mocked_put) task = TaskPayload( **task_payload( - TaskCategory.BACKGROUND, TaskType.PART_USAGE_FILE_REQUEST_PROCESSING, 'UFC-000', + TaskCategory.BACKGROUND, TaskType.PART_USAGE_FILE_REQUEST_PROCESSING, 'UFC-000', 1.0, ), ) diff --git a/tests/managers/test_interactive.py b/tests/managers/test_interactive.py index d20e5e3..4f567f8 100644 --- a/tests/managers/test_interactive.py +++ b/tests/managers/test_interactive.py @@ -1,4 +1,5 @@ import asyncio +import time import pytest @@ -43,6 +44,9 @@ async def test_validation_sync(mocker, extension_cls, task_type, config_payload) ) mocker.patch('connect.eaas.handler.get_extension_class') mocker.patch('connect.eaas.handler.get_extension_type') + mocked_time = mocker.patch('connect.eaas.managers.interactive.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = ExtensionHandler(config) task_response_data = {'task': 'data', 'valid': True} @@ -61,6 +65,7 @@ async def test_validation_sync(mocker, extension_cls, task_type, config_payload) TaskCategory.INTERACTIVE, task_type, 'ID-000', + runtime=1.0, ) task.data = {'task': 'data'} @@ -89,6 +94,9 @@ async def test_validation_async(mocker, extension_cls, task_type, config_payload ) mocker.patch('connect.eaas.handler.get_extension_class') mocker.patch('connect.eaas.handler.get_extension_type') + mocked_time = mocker.patch('connect.eaas.managers.interactive.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = ExtensionHandler(config) task_response_data = {'task': 'data', 'valid': True} @@ -108,6 +116,7 @@ async def test_validation_async(mocker, extension_cls, task_type, config_payload TaskCategory.INTERACTIVE, task_type, 'ID-000', + runtime=1.0, ) task.data = {'task': 'data'} @@ -145,6 +154,9 @@ async def test_others_sync(mocker, extension_cls, task_type, result, config_payl ) mocker.patch('connect.eaas.handler.get_extension_class') mocker.patch('connect.eaas.handler.get_extension_type') + mocked_time = mocker.patch('connect.eaas.managers.interactive.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = ExtensionHandler(config) handler.extension_class = extension_cls( @@ -161,6 +173,7 @@ async def test_others_sync(mocker, extension_cls, task_type, result, config_payl TaskCategory.INTERACTIVE, task_type, 'ID-000', + runtime=1.0, ) task.data = {'task': 'data'} @@ -202,6 +215,9 @@ async def test_others_async(mocker, extension_cls, task_type, result, config_pay ) mocker.patch('connect.eaas.handler.get_extension_class') mocker.patch('connect.eaas.handler.get_extension_type') + mocked_time = mocker.patch('connect.eaas.managers.interactive.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = ExtensionHandler(config) handler.extension_class = extension_cls( @@ -219,6 +235,7 @@ async def test_others_async(mocker, extension_cls, task_type, result, config_pay TaskCategory.INTERACTIVE, task_type, 'ID-000', + runtime=1.0, ) task.data = {'task': 'data'} diff --git a/tests/managers/test_scheduled.py b/tests/managers/test_scheduled.py index 6059c62..441e7c8 100644 --- a/tests/managers/test_scheduled.py +++ b/tests/managers/test_scheduled.py @@ -1,4 +1,5 @@ import asyncio +import time import pytest @@ -24,6 +25,9 @@ async def test_sync(mocker, extension_cls, config_payload): config.update_dynamic_config(ConfigurationPayload(**config_payload)) mocker.patch('connect.eaas.handler.get_extension_class') mocker.patch('connect.eaas.handler.get_extension_type') + mocked_time = mocker.patch('connect.eaas.managers.scheduled.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = ExtensionHandler(config) handler.extension_class = extension_cls( 'my_sync_schedulable_method', @@ -42,6 +46,7 @@ async def test_sync(mocker, extension_cls, config_payload): TaskCategory.SCHEDULED, TaskType.SCHEDULED_EXECUTION, 'EFS-000', + runtime=1.0, ) await manager.submit(task) @@ -58,6 +63,9 @@ async def test_async(mocker, extension_cls, config_payload): config.update_dynamic_config(ConfigurationPayload(**config_payload)) mocker.patch('connect.eaas.handler.get_extension_class') mocker.patch('connect.eaas.handler.get_extension_type') + mocked_time = mocker.patch('connect.eaas.managers.scheduled.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = ExtensionHandler(config) handler.extension_class = extension_cls( 'my_async_schedulable_method', @@ -77,6 +85,7 @@ async def test_async(mocker, extension_cls, config_payload): TaskCategory.SCHEDULED, TaskType.SCHEDULED_EXECUTION, 'EFS-000', + runtime=1.0, ) await manager.submit(task) diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py index 3f59405..86fbb54 100644 --- a/tests/test_dataclasses.py +++ b/tests/test_dataclasses.py @@ -40,6 +40,7 @@ def test_parse_task_message(): 'output': 'output', 'correlation_id': 'correlation_id', 'reply_to': 'reply_to', + 'runtime': 2.2, }, } @@ -61,6 +62,7 @@ def test_parse_capabilities_message(): 'schedulables': [], 'readme_url': 'https://read.me', 'changelog_url': 'https://change.log', + 'runner_version': '1.1', }, } @@ -108,6 +110,7 @@ def test_parse_capabilities_message_with_vars(): 'schedulables': None, 'readme_url': 'https://read.me', 'changelog_url': 'https://change.log', + 'runner_version': None, }, } @@ -138,6 +141,7 @@ def test_parse_capabilities_message_with_schedulables(): ], 'readme_url': 'https://read.me', 'changelog_url': 'https://change.log', + 'runner_version': '1.2', }, } diff --git a/tests/test_worker.py b/tests/test_worker.py index ab14998..749f62f 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -1,6 +1,7 @@ import asyncio import dataclasses import logging +import time import pytest from websockets.exceptions import ConnectionClosedError, InvalidStatusCode, WebSocketException @@ -57,6 +58,7 @@ def get_descriptor(cls): mocker.patch('connect.eaas.handler.get_extension_class', return_value=MyExtension) mocker.patch('connect.eaas.handler.get_extension_type', return_value='sync') + mocker.patch('connect.eaas.worker.get_version', return_value='24.1') data_to_send = dataclasses.asdict( Message( @@ -88,6 +90,7 @@ def get_descriptor(cls): [], 'https://example.com/README.md', 'https://example.com/CHANGELOG.md', + '24.1', ), ), ), @@ -160,6 +163,7 @@ def process_asset_purchase_request(self, request): mocker.patch('connect.eaas.handler.get_extension_class', return_value=MyExtension) mocker.patch('connect.eaas.handler.get_extension_type', return_value='sync') + mocker.patch('connect.eaas.worker.get_version', return_value='24.1') dyn_config = ConfigurationPayload(**config_payload) @@ -173,6 +177,9 @@ def process_asset_purchase_request(self, request): ))), ] + mocked_time = mocker.patch('connect.eaas.managers.background.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = WSHandler( '/public/v1/devops/ws/ENV-000-0001/INS-000-0002?running_tasks=0&running_scheduled_tasks=0', data_to_send, @@ -195,10 +202,12 @@ def process_asset_purchase_request(self, request): [], 'https://example.com/README.md', 'https://example.com/CHANGELOG.md', + '24.1', ), ), ), ) + handler.assert_received( dataclasses.asdict( Message(MessageType.TASK, TaskPayload( @@ -207,6 +216,7 @@ def process_asset_purchase_request(self, request): TaskType.ASSET_PURCHASE_REQUEST_PROCESSING, 'PR-000', result=ResultType.SUCCESS, + runtime=1.0, )), ), ) @@ -271,6 +281,7 @@ def process_tier_config_setup_request(self, request): mocker.patch('connect.eaas.handler.get_extension_class', return_value=MyExtension) mocker.patch('connect.eaas.handler.get_extension_type', return_value='sync') + mocker.patch('connect.eaas.worker.get_version', return_value='24.1') data_to_send = [ dataclasses.asdict(Message(MessageType.CONFIGURATION, ConfigurationPayload( @@ -285,7 +296,9 @@ def process_tier_config_setup_request(self, request): )), ), ] - + mocked_time = mocker.patch('connect.eaas.managers.background.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = WSHandler( '/public/v1/devops/ws/ENV-000-0001/INS-000-0002?running_tasks=0&running_scheduled_tasks=0', data_to_send, @@ -309,6 +322,7 @@ def process_tier_config_setup_request(self, request): [], 'https://example.com/README.md', 'https://example.com/CHANGELOG.md', + '24.1', ), ), ), @@ -321,6 +335,7 @@ def process_tier_config_setup_request(self, request): TaskType.TIER_CONFIG_SETUP_REQUEST_PROCESSING, 'TCR-000', result=ResultType.SUCCESS, + runtime=1.0, )), ), ) @@ -388,6 +403,7 @@ def run_scheduled_task(self, schedule): mocker.patch('connect.eaas.handler.get_extension_class', return_value=MyExtension) mocker.patch('connect.eaas.handler.get_extension_type', return_value='sync') + mocker.patch('connect.eaas.worker.get_version', return_value='24.1') data_to_send = [ dataclasses.asdict(Message(MessageType.CONFIGURATION, ConfigurationPayload( @@ -403,6 +419,9 @@ def run_scheduled_task(self, schedule): ), ] + mocked_time = mocker.patch('connect.eaas.managers.scheduled.time') + mocked_time.sleep = time.sleep + mocked_time.monotonic.side_effect = (1.0, 2.0) handler = WSHandler( '/public/v1/devops/ws/ENV-000-0001/INS-000-0002?running_tasks=0&running_scheduled_tasks=0', data_to_send, @@ -432,10 +451,12 @@ def run_scheduled_task(self, schedule): ], 'https://example.com/README.md', 'https://example.com/CHANGELOG.md', + '24.1', ), ), ), ) + handler.assert_received( dataclasses.asdict( Message(MessageType.TASK, TaskPayload( @@ -444,6 +465,7 @@ def run_scheduled_task(self, schedule): TaskType.SCHEDULED_EXECUTION, schedule_data['id'], result=ResultType.SUCCESS, + runtime=1.0, )), ), ) @@ -754,6 +776,7 @@ def get_descriptor(cls): mocker.patch('connect.eaas.handler.get_extension_class', return_value=MyExtension) mocker.patch('connect.eaas.handler.get_extension_type', return_value='sync') + mocker.patch('connect.eaas.worker.get_version', return_value='24.1') data_to_send = dataclasses.asdict( Message( @@ -792,6 +815,7 @@ def get_descriptor(cls): [], 'https://example.com/README.md', 'https://example.com/CHANGELOG.md', + '24.1', ), ), ), @@ -830,6 +854,7 @@ def get_descriptor(cls): mocker.patch('connect.eaas.handler.get_extension_class', return_value=MyExtension) mocker.patch('connect.eaas.handler.get_extension_type', return_value='sync') + mocker.patch('connect.eaas.worker.get_version', return_value='24.1') data_to_send = dataclasses.asdict( Message( @@ -868,6 +893,7 @@ def get_descriptor(cls): None, 'https://example.com/README.md', 'https://example.com/CHANGELOG.md', + '24.1', ), ), ),