From 2889fbfe33600fe841472ac96c63af9023852da7 Mon Sep 17 00:00:00 2001 From: Emir Muhammad Date: Mon, 9 Mar 2026 15:42:07 +0100 Subject: [PATCH 1/3] Reorder exception handling --- src/drunc/process_manager/process_manager.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/drunc/process_manager/process_manager.py b/src/drunc/process_manager/process_manager.py index 827f2019c..cb9724488 100644 --- a/src/drunc/process_manager/process_manager.py +++ b/src/drunc/process_manager/process_manager.py @@ -551,14 +551,6 @@ def logs(self, request: LogRequest, context: ServicerContext) -> LogLines: message="Implementation missing", domain="ProcessManager.logs", ) - except Exception as e: - context_msg = f"Unhandled exception in ProcessManager.logs: {e}" - self.log.exception(context_msg) - - raise DruncCommandException( - message=f"{context_msg}: {e}", - domain="ProcessManager.logs", - ) except BadQuery as e: return LogLines( name=self.name, @@ -567,6 +559,14 @@ def logs(self, request: LogRequest, context: ServicerContext) -> LogLines: lines=[str(e)], flag=ResponseFlag.NOT_EXECUTED_BAD_REQUEST_FORMAT, ) + except Exception as e: + context_msg = f"Unhandled exception in ProcessManager.logs: {e}" + self.log.exception(context_msg) + + raise DruncCommandException( + message=f"{context_msg}: {e}", + domain="ProcessManager.logs", + ) return response From 38e092a9fe1687487e1af55da903c47b37d5a5c0 Mon Sep 17 00:00:00 2001 From: Emir Muhammad Date: Mon, 9 Mar 2026 15:53:20 +0100 Subject: [PATCH 2/3] Add tests --- .../test_process_manager_driver.py | 31 +++++++++++++++++++ .../test_process_manager_endpoints.py | 29 +++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/tests/process_manager/test_process_manager_driver.py b/tests/process_manager/test_process_manager_driver.py index 54946cf82..fc85158be 100644 --- a/tests/process_manager/test_process_manager_driver.py +++ b/tests/process_manager/test_process_manager_driver.py @@ -20,6 +20,7 @@ ProcessMetadata, ProcessRestriction, ) +from druncschema.request_response_pb2 import ResponseFlag from druncschema.token_pb2 import Token from drunc.connectivity_service.exceptions import ApplicationLookupUnsuccessful @@ -946,6 +947,36 @@ def test_logs_grpc_error(mock_driver, log_request): mock_handler.assert_called_once_with(grpc_error) +def test_logs_bad_query_target_not_found(mock_driver, mock_logger, log_request): + """Test that logs bad-query target misses are reported with a concise message.""" + mock_response = MagicMock() + mock_response.flag = ResponseFlag.NOT_EXECUTED_BAD_REQUEST_FORMAT + mock_response.lines = ["The process corresponding to the query doesn't exist"] + mock_driver._mock_stub.logs.return_value = mock_response + + response = mock_driver.logs(log_request) + + assert response is None + mock_logger.warning.assert_called_once_with( + "Bad query for logs: The process corresponding to the query doesn't exist" + ) + + +def test_logs_bad_query_generic_message(mock_driver, mock_logger, log_request): + """Test that logs bad-query non-missing-target errors keep their details.""" + mock_response = MagicMock() + mock_response.flag = ResponseFlag.NOT_EXECUTED_BAD_REQUEST_FORMAT + mock_response.lines = ["There are more than 1 processes corresponding to the query"] + mock_driver._mock_stub.logs.return_value = mock_response + + response = mock_driver.logs(log_request) + + assert response is None + mock_logger.warning.assert_called_once_with( + "Bad query for logs: There are more than 1 processes corresponding to the query" + ) + + def test_ps_success(mock_driver, process_query_request, ps_response): """ Test that ps method correctly calls stub.ps and returns response. diff --git a/tests/process_manager/test_process_manager_endpoints.py b/tests/process_manager/test_process_manager_endpoints.py index 92d1e4d08..e81266e85 100644 --- a/tests/process_manager/test_process_manager_endpoints.py +++ b/tests/process_manager/test_process_manager_endpoints.py @@ -17,7 +17,9 @@ import grpc_testing import pytest from druncschema.process_manager_pb2 import DESCRIPTOR +from druncschema.request_response_pb2 import ResponseFlag +from drunc.process_manager.process_manager import BadQuery from tests.process_manager.process_manager_mock_impls import ( ConcreteProcessManager, ) @@ -303,3 +305,30 @@ def test_logs_endpoint(grpc_test_server_factory, log_request, logs_response): # Verify all response fields match expected values assert expected_response == response + + +def test_logs_endpoint_bad_query(grpc_servicer, log_request): + """Test that BadQuery in logs returns a clean bad-request response.""" + error_message = "The process corresponding to the query doesn't exist" + mock_method = MagicMock(side_effect=BadQuery(error_message)) + setattr(grpc_servicer, "_logs_impl", mock_method) + + servicers = {DESCRIPTOR.services_by_name["ProcessManager"]: grpc_servicer} + grpc_test_server = grpc_testing.server_from_dictionary( + servicers, grpc_testing.strict_real_time() + ) + + logs_method = grpc_test_server.invoke_unary_unary( + method_descriptor=( + DESCRIPTOR.services_by_name["ProcessManager"].methods_by_name["logs"] + ), + invocation_metadata={}, + request=log_request, + timeout=1, + ) + + response, metadata, code, details = logs_method.termination() + + assert code == grpc.StatusCode.OK + assert response.flag == ResponseFlag.NOT_EXECUTED_BAD_REQUEST_FORMAT + assert list(response.lines) == [error_message] From d9383ed15d2a8ced26bb8d8268792d81ee3a5ec9 Mon Sep 17 00:00:00 2001 From: Emir Muhammad Date: Mon, 9 Mar 2026 16:19:41 +0100 Subject: [PATCH 3/3] [Feature]: The Run Info table does not have a time zone Fixes #799 --- src/drunc/controller/interface/shell_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/drunc/controller/interface/shell_utils.py b/src/drunc/controller/interface/shell_utils.py index 41a2b5e82..8280b3576 100644 --- a/src/drunc/controller/interface/shell_utils.py +++ b/src/drunc/controller/interface/shell_utils.py @@ -13,6 +13,7 @@ import click import grpc +from daqpytools.logging.formatter import DATE_TIME_BASE_FORMAT, TIME_ZONE from druncschema.controller_pb2 import ( Argument, DescribeResponse, @@ -151,9 +152,9 @@ def add_runinfo_to_table(table: Table, status: Status): table.add_row("Run type", status.run_info.run_type) table.add_row( "Start time", - datetime.datetime.fromtimestamp(status.run_info.run_time_at_start).strftime( - "%Y-%m-%d %H:%M:%S" - ), + datetime.datetime.fromtimestamp( + status.run_info.run_time_at_start, tz=TIME_ZONE + ).strftime(DATE_TIME_BASE_FORMAT), ) table.add_row( "Duration",