From 7aeb00f662d96d0535656c234a37e13cfc3c30e3 Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 04:49:40 +0000 Subject: [PATCH 01/14] grpc.api_v2: implement special treatment for ecu_info broken --- src/otaclient/grpc/api_v2/servicer.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/otaclient/grpc/api_v2/servicer.py b/src/otaclient/grpc/api_v2/servicer.py index a44ccb1a5..0d6a8c48b 100644 --- a/src/otaclient/grpc/api_v2/servicer.py +++ b/src/otaclient/grpc/api_v2/servicer.py @@ -21,6 +21,7 @@ import multiprocessing.queues as mp_queue from concurrent.futures import ThreadPoolExecutor +import otaclient.configs.cfg as otaclient_cfg from otaclient._types import ( IPCRequest, IPCResEnum, @@ -111,6 +112,27 @@ async def update( update_acked_ecus = set() response = api_types.UpdateResponse() + # NOTE(20241220): due to the fact that OTA Service API doesn't have field + # in UpdateResponseEcu msg, the only way to pass the failure_msg + # to upper is by status API. + # Here we only immediately accept the OTA update request for main ECU, + # fails other ECUs, and let main ECU's OTA status tells what is going on. + if not otaclient_cfg.ECU_INFO_LOADED_SUCCESSFULLY: + response.add_ecu( + api_types.UpdateResponseEcu( + ecu_id=self.my_ecu_id, + result=api_types.FailureType.NO_FAILURE, + ) + ) + for _update_req in request.iter_ecu(): + response.add_ecu( + api_types.UpdateResponseEcu( + ecu_id=_update_req.ecu_id, + result=api_types.FailureType.RECOVERABLE, + ) + ) + return response + # first: dispatch update request to all directly connected subECUs tasks: dict[asyncio.Task, ECUContact] = {} for ecu_contact in self.sub_ecus: From d1d8c8bdd8a7bd545ded8ff32853efaab3aa35b9 Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 05:56:49 +0000 Subject: [PATCH 02/14] ota_core: donot set otaclient is started on ecu_info.yaml loaded failed, reject any OTA update request on not started --- src/otaclient/ota_core.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/otaclient/ota_core.py b/src/otaclient/ota_core.py index 203f719c1..c916e1ec1 100644 --- a/src/otaclient/ota_core.py +++ b/src/otaclient/ota_core.py @@ -36,6 +36,7 @@ import requests.exceptions as requests_exc from requests import Response +import otaclient.configs.cfg as otaclient_cfg from ota_metadata.legacy import parser as ota_metadata_parser from ota_metadata.legacy import types as ota_metadata_types from ota_metadata.utils.cert_store import ( @@ -701,6 +702,21 @@ def __init__( self.ca_chains_store = CAChainStore() + if not otaclient_cfg.ECU_INFO_LOADED_SUCCESSFULLY: + logger.error( + "ecu_info.yaml file is not loaded properly, will reject any OTA request." + ) + self._live_ota_status = OTAStatus.FAILURE + status_report_queue.put_nowait( + StatusReport( + payload=OTAStatusChangeReport( + new_ota_status=OTAStatus.FAILURE, + failure_reason="ecu_info.yaml file is broken, reject any OTA request.", + ), + ) + ) + return + self.started = True logger.info("otaclient started") logger.info(f"firmware_version: {self.current_version}") @@ -828,6 +844,16 @@ def main( except Empty: continue + if not self.started: + resp_queue.put_nowait( + IPCResponse( + res=IPCResEnum.REJECT_OTHER, + msg="otaclient is not started", + session_id=request.session_id, + ) + ) + continue + if _now < _allow_request_after or self.is_busy: _err_msg = ( f"otaclient is busy at {self._live_ota_status} or " From be4b70159447c4cd18e7911ca75a8edc2c1dc22b Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 07:50:44 +0000 Subject: [PATCH 03/14] minor update --- src/otaclient/ota_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/otaclient/ota_core.py b/src/otaclient/ota_core.py index c916e1ec1..eedce1c40 100644 --- a/src/otaclient/ota_core.py +++ b/src/otaclient/ota_core.py @@ -848,7 +848,7 @@ def main( resp_queue.put_nowait( IPCResponse( res=IPCResEnum.REJECT_OTHER, - msg="otaclient is not started", + msg="otaclient is not started, might be due to broken ecu_info.yaml", session_id=request.session_id, ) ) From 2bf897bc44da0b99754fb43b1f47a32fa9b9bddf Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 07:56:24 +0000 Subject: [PATCH 04/14] minor update --- src/otaclient/ota_core.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/otaclient/ota_core.py b/src/otaclient/ota_core.py index eedce1c40..5efbc18c5 100644 --- a/src/otaclient/ota_core.py +++ b/src/otaclient/ota_core.py @@ -673,17 +673,7 @@ def __init__( ) return - # load and report booted OTA status - _boot_ctrl_loaded_ota_status = self.boot_controller.get_booted_ota_status() - self._live_ota_status = _boot_ctrl_loaded_ota_status - status_report_queue.put_nowait( - StatusReport( - payload=OTAStatusChangeReport( - new_ota_status=_boot_ctrl_loaded_ota_status, - ), - ) - ) - + # ------ load firmware version ------ # self.current_version = self.boot_controller.load_version() status_report_queue.put_nowait( StatusReport( @@ -693,6 +683,7 @@ def __init__( ) ) + # ------ load CA store ------ # self.ca_chains_store = None try: self.ca_chains_store = load_ca_cert_chains(cfg.CERT_DPATH) @@ -702,6 +693,8 @@ def __init__( self.ca_chains_store = CAChainStore() + # load and report booted OTA status + _boot_ctrl_loaded_ota_status = self.boot_controller.get_booted_ota_status() if not otaclient_cfg.ECU_INFO_LOADED_SUCCESSFULLY: logger.error( "ecu_info.yaml file is not loaded properly, will reject any OTA request." @@ -715,11 +708,19 @@ def __init__( ), ) ) - return + else: + self._live_ota_status = _boot_ctrl_loaded_ota_status + status_report_queue.put_nowait( + StatusReport( + payload=OTAStatusChangeReport( + new_ota_status=_boot_ctrl_loaded_ota_status, + ), + ) + ) - self.started = True - logger.info("otaclient started") - logger.info(f"firmware_version: {self.current_version}") + self.started = True + logger.info("otaclient started") + logger.info(f"firmware_version: {self.current_version}") def _on_failure( self, From 36764ae821413aced9885f970c8b764db4e2e789 Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 08:00:05 +0000 Subject: [PATCH 05/14] reject all request on ecu_info.yaml load failed --- src/otaclient/grpc/api_v2/servicer.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/otaclient/grpc/api_v2/servicer.py b/src/otaclient/grpc/api_v2/servicer.py index 0d6a8c48b..449ed721f 100644 --- a/src/otaclient/grpc/api_v2/servicer.py +++ b/src/otaclient/grpc/api_v2/servicer.py @@ -115,15 +115,7 @@ async def update( # NOTE(20241220): due to the fact that OTA Service API doesn't have field # in UpdateResponseEcu msg, the only way to pass the failure_msg # to upper is by status API. - # Here we only immediately accept the OTA update request for main ECU, - # fails other ECUs, and let main ECU's OTA status tells what is going on. if not otaclient_cfg.ECU_INFO_LOADED_SUCCESSFULLY: - response.add_ecu( - api_types.UpdateResponseEcu( - ecu_id=self.my_ecu_id, - result=api_types.FailureType.NO_FAILURE, - ) - ) for _update_req in request.iter_ecu(): response.add_ecu( api_types.UpdateResponseEcu( From 62b5572ebf4bef8b7b65cfd9a1d5a173306c35da Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 08:04:09 +0000 Subject: [PATCH 06/14] ota_core: fix failure_status report --- src/otaclient/ota_core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/otaclient/ota_core.py b/src/otaclient/ota_core.py index 5efbc18c5..4e046a725 100644 --- a/src/otaclient/ota_core.py +++ b/src/otaclient/ota_core.py @@ -704,7 +704,9 @@ def __init__( StatusReport( payload=OTAStatusChangeReport( new_ota_status=OTAStatus.FAILURE, - failure_reason="ecu_info.yaml file is broken, reject any OTA request.", + failure_type=FailureType.UNRECOVERABLE, + failure_reason="ecu_info.yaml file is broken, please check /boot/ota/ecu_info.yaml. " + "reject any OTA request.", ), ) ) From 679e91dcc8783c20bcec604af34cf9b895c6f1e9 Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 09:03:22 +0000 Subject: [PATCH 07/14] status_monitor: always push OTAStatus chang report --- src/otaclient/_status_monitor.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/otaclient/_status_monitor.py b/src/otaclient/_status_monitor.py index 9db09cfe0..0f85db627 100644 --- a/src/otaclient/_status_monitor.py +++ b/src/otaclient/_status_monitor.py @@ -318,7 +318,15 @@ def _status_collector_thread(self) -> None: break # ------ push status on load_report ------ # - if self.load_report(report) and self._status and _now > _next_shm_push: + # NOTE: always push OTAStatus change report + if ( + self.load_report(report) + and self._status + and ( + isinstance(report, OTAStatusChangeReport) + or _now > _next_shm_push + ) + ): try: self._shm_status.write_msg(self._status) _next_shm_push = _now + self.shm_push_interval From a274805c3c5c60fc411f185f5dc5b57cac31a721 Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 09:03:45 +0000 Subject: [PATCH 08/14] minor update --- src/otaclient/ota_core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/otaclient/ota_core.py b/src/otaclient/ota_core.py index 4e046a725..c86fd0228 100644 --- a/src/otaclient/ota_core.py +++ b/src/otaclient/ota_core.py @@ -682,6 +682,7 @@ def __init__( ), ) ) + logger.info(f"firmware_version: {self.current_version}") # ------ load CA store ------ # self.ca_chains_store = None @@ -699,6 +700,7 @@ def __init__( logger.error( "ecu_info.yaml file is not loaded properly, will reject any OTA request." ) + logger.error(f"set live_ota_status to {OTAStatus.FAILURE!r}") self._live_ota_status = OTAStatus.FAILURE status_report_queue.put_nowait( StatusReport( @@ -722,7 +724,6 @@ def __init__( self.started = True logger.info("otaclient started") - logger.info(f"firmware_version: {self.current_version}") def _on_failure( self, From 19f9efa47c6343f86310d480abd0119108763c48 Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 09:08:45 +0000 Subject: [PATCH 09/14] minor fix --- src/otaclient/_status_monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/otaclient/_status_monitor.py b/src/otaclient/_status_monitor.py index 0f85db627..e0cd69fcf 100644 --- a/src/otaclient/_status_monitor.py +++ b/src/otaclient/_status_monitor.py @@ -323,7 +323,7 @@ def _status_collector_thread(self) -> None: self.load_report(report) and self._status and ( - isinstance(report, OTAStatusChangeReport) + isinstance(report.payload, OTAStatusChangeReport) or _now > _next_shm_push ) ): From 2c190cc130a93b4203d4ee7e926f2d26b95e891a Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 09:17:12 +0000 Subject: [PATCH 10/14] ota_core: place the otaclient started check behind the request burst limit check --- src/otaclient/ota_core.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/otaclient/ota_core.py b/src/otaclient/ota_core.py index c86fd0228..39b1917b9 100644 --- a/src/otaclient/ota_core.py +++ b/src/otaclient/ota_core.py @@ -848,16 +848,6 @@ def main( except Empty: continue - if not self.started: - resp_queue.put_nowait( - IPCResponse( - res=IPCResEnum.REJECT_OTHER, - msg="otaclient is not started, might be due to broken ecu_info.yaml", - session_id=request.session_id, - ) - ) - continue - if _now < _allow_request_after or self.is_busy: _err_msg = ( f"otaclient is busy at {self._live_ota_status} or " @@ -873,6 +863,19 @@ def main( ) ) + elif not self.started: + _err_msg = ( + "otaclient is not started, might be due to broken ecu_info.yaml" + ) + logger.error(_err_msg) + resp_queue.put_nowait( + IPCResponse( + res=IPCResEnum.REJECT_OTHER, + msg=_err_msg, + session_id=request.session_id, + ) + ) + elif isinstance(request, UpdateRequestV2): _update_thread = threading.Thread( From 681b79154667fa25a96bf742d4184a281c62dac3 Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Fri, 20 Dec 2024 09:21:13 +0000 Subject: [PATCH 11/14] servicer: also reject rollback request on ecu_info.yaml broken --- src/otaclient/grpc/api_v2/servicer.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/otaclient/grpc/api_v2/servicer.py b/src/otaclient/grpc/api_v2/servicer.py index 449ed721f..16bcd34d8 100644 --- a/src/otaclient/grpc/api_v2/servicer.py +++ b/src/otaclient/grpc/api_v2/servicer.py @@ -116,11 +116,14 @@ async def update( # in UpdateResponseEcu msg, the only way to pass the failure_msg # to upper is by status API. if not otaclient_cfg.ECU_INFO_LOADED_SUCCESSFULLY: + logger.error( + "ecu_info.yaml is not loaded properly, reject any update request" + ) for _update_req in request.iter_ecu(): response.add_ecu( api_types.UpdateResponseEcu( ecu_id=_update_req.ecu_id, - result=api_types.FailureType.RECOVERABLE, + result=api_types.FailureType.UNRECOVERABLE, ) ) return response @@ -239,6 +242,22 @@ async def rollback( logger.info(f"receive rollback request: {request}") response = api_types.RollbackResponse() + # NOTE(20241220): due to the fact that OTA Service API doesn't have field + # in UpdateResponseEcu msg, the only way to pass the failure_msg + # to upper is by status API. + if not otaclient_cfg.ECU_INFO_LOADED_SUCCESSFULLY: + logger.error( + "ecu_info.yaml is not loaded properly, reject any rollback request" + ) + for _rollback_req in request.iter_ecu(): + response.add_ecu( + api_types.RollbackResponseEcu( + ecu_id=_rollback_req.ecu_id, + result=api_types.FailureType.UNRECOVERABLE, + ) + ) + return response + # first: dispatch rollback request to all directly connected subECUs tasks: dict[asyncio.Task, ECUContact] = {} for ecu_contact in self.sub_ecus: From 10a1bf1abcfe392f753f302879b0cacf96683caa Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Mon, 23 Dec 2024 01:41:30 +0000 Subject: [PATCH 12/14] update the wording of failure_reason --- src/otaclient/ota_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/otaclient/ota_core.py b/src/otaclient/ota_core.py index 39b1917b9..b787c0d29 100644 --- a/src/otaclient/ota_core.py +++ b/src/otaclient/ota_core.py @@ -707,7 +707,7 @@ def __init__( payload=OTAStatusChangeReport( new_ota_status=OTAStatus.FAILURE, failure_type=FailureType.UNRECOVERABLE, - failure_reason="ecu_info.yaml file is broken, please check /boot/ota/ecu_info.yaml. " + failure_reason=f"ecu_info.yaml file is broken or missing, please check {cfg.ECU_INFO_FPATH}. " "reject any OTA request.", ), ) From a06972ae2d8867800783035dcba2a6d3b5dc04a3 Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Wed, 25 Dec 2024 02:33:53 +0000 Subject: [PATCH 13/14] implement tests that covers the changes --- .../test_configs/test_ecu_info.py | 104 ++++++++++-------- .../test_configs/test_proxy_info.py | 53 +++++---- 2 files changed, 93 insertions(+), 64 deletions(-) diff --git a/tests/test_otaclient/test_configs/test_ecu_info.py b/tests/test_otaclient/test_configs/test_ecu_info.py index b85c31e0b..d21522659 100644 --- a/tests/test_otaclient/test_configs/test_ecu_info.py +++ b/tests/test_otaclient/test_configs/test_ecu_info.py @@ -25,23 +25,23 @@ @pytest.mark.parametrize( - "ecu_info_yaml, expected_ecu_info", + "ecu_info_yaml, expected_res", ( # --- case 1: invalid ecu_info --- # # case 1.1: valid yaml(empty file), invalid ecu_info ( "# this is an empty file", - DEFAULT_ECU_INFO, + (False, DEFAULT_ECU_INFO), ), # case 1.2: valid yaml(array), invalid ecu_info ( ("- this is an\n- yaml file that\n- contains a array\n"), - DEFAULT_ECU_INFO, + (False, DEFAULT_ECU_INFO), ), # case 1.2: invalid yaml ( " - \n not a \n [ valid yaml", - DEFAULT_ECU_INFO, + (False, DEFAULT_ECU_INFO), ), # --- case 2: single ECU --- # # case 2.1: basic single ECU @@ -52,10 +52,13 @@ 'ip_addr: "192.168.1.1"\n' "bootloader: jetson-cboot\n" ), - ECUInfo( - ecu_id="autoware", - ip_addr=IPv4Address("192.168.1.1"), - bootloader=BootloaderType.JETSON_CBOOT, + ( + True, + ECUInfo( + ecu_id="autoware", + ip_addr=IPv4Address("192.168.1.1"), + bootloader=BootloaderType.JETSON_CBOOT, + ), ), ), # case 2.2: single ECU with bootloader type specified @@ -66,10 +69,13 @@ 'ip_addr: "192.168.1.1"\n' 'bootloader: "grub"\n' ), - ECUInfo( - ecu_id="autoware", - ip_addr=IPv4Address("192.168.1.1"), - bootloader=BootloaderType.GRUB, + ( + True, + ECUInfo( + ecu_id="autoware", + ip_addr=IPv4Address("192.168.1.1"), + bootloader=BootloaderType.GRUB, + ), ), ), # --- case 3: multiple ECUs --- # @@ -87,21 +93,24 @@ '- ecu_id: "p2"\n' ' ip_addr: "192.168.0.12"\n' ), - ECUInfo( - ecu_id="autoware", - ip_addr=IPv4Address("192.168.1.1"), - bootloader=BootloaderType.GRUB, - available_ecu_ids=["autoware", "p1", "p2"], - secondaries=[ - ECUContact( - ecu_id="p1", - ip_addr=IPv4Address("192.168.0.11"), - ), - ECUContact( - ecu_id="p2", - ip_addr=IPv4Address("192.168.0.12"), - ), - ], + ( + True, + ECUInfo( + ecu_id="autoware", + ip_addr=IPv4Address("192.168.1.1"), + bootloader=BootloaderType.GRUB, + available_ecu_ids=["autoware", "p1", "p2"], + secondaries=[ + ECUContact( + ecu_id="p1", + ip_addr=IPv4Address("192.168.0.11"), + ), + ECUContact( + ecu_id="p2", + ip_addr=IPv4Address("192.168.0.12"), + ), + ], + ), ), ), # case 3.2: multiple ECUs, with main ECU's bootloader specified @@ -118,32 +127,37 @@ '- ecu_id: "p2"\n' ' ip_addr: "192.168.0.12"\n' ), - ECUInfo( - ecu_id="autoware", - ip_addr=IPv4Address("192.168.1.1"), - bootloader=BootloaderType.JETSON_UEFI, - available_ecu_ids=["autoware", "p1", "p2"], - secondaries=[ - ECUContact( - ecu_id="p1", - ip_addr=IPv4Address("192.168.0.11"), - ), - ECUContact( - ecu_id="p2", - ip_addr=IPv4Address("192.168.0.12"), - ), - ], + ( + True, + ECUInfo( + ecu_id="autoware", + ip_addr=IPv4Address("192.168.1.1"), + bootloader=BootloaderType.JETSON_UEFI, + available_ecu_ids=["autoware", "p1", "p2"], + secondaries=[ + ECUContact( + ecu_id="p1", + ip_addr=IPv4Address("192.168.0.11"), + ), + ECUContact( + ecu_id="p2", + ip_addr=IPv4Address("192.168.0.12"), + ), + ], + ), ), ), ), ) -def test_ecu_info(tmp_path: Path, ecu_info_yaml: str, expected_ecu_info: ECUInfo): +def test_ecu_info( + tmp_path: Path, ecu_info_yaml: str, expected_res: tuple[bool, ECUInfo] +): # --- preparation --- # (ota_dir := tmp_path / "boot" / "ota").mkdir(parents=True, exist_ok=True) (ecu_info_file := ota_dir / "ecu_info.yaml").write_text(ecu_info_yaml) # --- execution --- # - _, loaded_ecu_info = parse_ecu_info(ecu_info_file) + res = parse_ecu_info(ecu_info_file) # --- assertion --- # - assert loaded_ecu_info == expected_ecu_info + assert res == expected_res diff --git a/tests/test_otaclient/test_configs/test_proxy_info.py b/tests/test_otaclient/test_configs/test_proxy_info.py index e2b2d40f3..38a639485 100644 --- a/tests/test_otaclient/test_configs/test_proxy_info.py +++ b/tests/test_otaclient/test_configs/test_proxy_info.py @@ -19,12 +19,16 @@ from pathlib import Path import pytest +import pytest_mock +import otaclient.configs._proxy_info as _proxy_info_module from otaclient.configs import ProxyInfo from otaclient.configs._proxy_info import DEFAULT_PROXY_INFO, parse_proxy_info logger = logging.getLogger(__name__) +MODULE_NAME = _proxy_info_module.__name__ + @pytest.mark.parametrize( "_input_yaml, _expected", @@ -35,7 +39,7 @@ # NOTE: this default value is for x1 backward compatibility. ( "# this is an empty file", - DEFAULT_PROXY_INFO, + (False, DEFAULT_PROXY_INFO), ), # ------ case 2: typical sub ECU's proxy_info.yaml ------ # ( @@ -45,16 +49,19 @@ "enable_local_ota_proxy_cache: true\n" 'logging_server: "http://10.0.0.1:8083"\n' ), - ProxyInfo( - **{ - "format_version": 1, - "upper_ota_proxy": "http://10.0.0.1:8082", - "enable_local_ota_proxy": True, - "enable_local_ota_proxy_cache": True, - "local_ota_proxy_listen_addr": "0.0.0.0", - "local_ota_proxy_listen_port": 8082, - "logging_server": "http://10.0.0.1:8083", - } + ( + True, + ProxyInfo( + **{ + "format_version": 1, + "upper_ota_proxy": "http://10.0.0.1:8082", + "enable_local_ota_proxy": True, + "enable_local_ota_proxy_cache": True, + "local_ota_proxy_listen_addr": "0.0.0.0", + "local_ota_proxy_listen_port": 8082, + "logging_server": "http://10.0.0.1:8083", + } + ), ), ), # ------ case 3: invalid/corrupted proxy_info.yaml ------ # @@ -63,7 +70,7 @@ # proxy_info.yaml will be used. ( "not a valid proxy_info.yaml", - DEFAULT_PROXY_INFO, + (False, DEFAULT_PROXY_INFO), ), # ------ case 4: proxy_info.yaml is valid yaml but contains invalid fields ------ # # in this case, default predefined default proxy_info.yaml will be loaded @@ -81,31 +88,39 @@ "local_ota_proxy_listen_addr: 123\n" 'local_ota_proxy_listen_port: "2808"\n' ), - DEFAULT_PROXY_INFO, + (False, DEFAULT_PROXY_INFO), ), # ------ case 5: corrupted/invalid yaml ------ # # in this case, default predefined default proxy_info.yaml will be loaded ( "/t/t/t/t/t/t/t/tyaml file should not contain tabs/t/t/t/", - DEFAULT_PROXY_INFO, + (False, DEFAULT_PROXY_INFO), ), # ------ case 6: backward compatibility test ------ # # for superseded field, the corresponding field should be assigned. # for removed field, it should not impact the config file loading. ( "enable_ota_proxy: true\ngateway: false\n", - DEFAULT_PROXY_INFO, + (True, DEFAULT_PROXY_INFO), ), # ------ case 7(20240626): NetworkPort allow str value ------ # ( 'local_ota_proxy_listen_port: "8082"', - ProxyInfo(local_ota_proxy_listen_port=8082), + (True, ProxyInfo(local_ota_proxy_listen_port=8082)), ), ), ) -def test_proxy_info(tmp_path: Path, _input_yaml: str, _expected: ProxyInfo): +def test_proxy_info( + tmp_path: Path, + _input_yaml: str, + _expected: tuple[bool, ProxyInfo], + mocker: pytest_mock.MockerFixture, +) -> None: + # disable deprecation warning on test + mocker.patch(f"{MODULE_NAME}._deprecation_check") + proxy_info_file = tmp_path / "proxy_info.yml" proxy_info_file.write_text(_input_yaml) - _, _proxy_info = parse_proxy_info(str(proxy_info_file)) + _res = parse_proxy_info(str(proxy_info_file)) - assert _proxy_info == _expected + assert _res == _expected From 5f0086611e2b8ca5bbf8873a931171ece8dc6cba Mon Sep 17 00:00:00 2001 From: "bodong.yang" Date: Wed, 25 Dec 2024 02:38:53 +0000 Subject: [PATCH 14/14] ota_core.OTAClient: refine the OTA request reject msg on otaclient not started --- src/otaclient/ota_core.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/otaclient/ota_core.py b/src/otaclient/ota_core.py index b787c0d29..f42b4c665 100644 --- a/src/otaclient/ota_core.py +++ b/src/otaclient/ota_core.py @@ -67,7 +67,12 @@ ) from otaclient._utils import SharedOTAClientStatusWriter, get_traceback, wait_and_log from otaclient.boot_control import BootControllerProtocol, get_boot_controller -from otaclient.configs.cfg import cfg, ecu_info, proxy_info +from otaclient.configs.cfg import ( + ECU_INFO_LOADED_SUCCESSFULLY, + cfg, + ecu_info, + proxy_info, +) from otaclient.create_standby import ( StandbySlotCreatorProtocol, get_standby_slot_creator, @@ -864,9 +869,10 @@ def main( ) elif not self.started: - _err_msg = ( - "otaclient is not started, might be due to broken ecu_info.yaml" - ) + _err_msg = "reject OTA request due to otaclient is not (yet) started." + if not ECU_INFO_LOADED_SUCCESSFULLY: + _err_msg = f"reject OTA request due to {cfg.ECU_INFO_FPATH} missing or broken" + logger.error(_err_msg) resp_queue.put_nowait( IPCResponse(