From 80916fba4c0c10177fecd80a26190e7eb52b9ea7 Mon Sep 17 00:00:00 2001 From: Bodong Yang <86948717+Bodong-Yang@users.noreply.github.com> Date: Sat, 19 Oct 2024 13:33:01 +0900 Subject: [PATCH] feat: re-implement otaclient configs, add support for dynamic root and runtime configurable settings (#398) Re-implement the otaclient.app.configs into otaclient.configs, add support for dynamic root and runtime configurable settings. The dynamic root feature is needed for enabling otaclient-in-container in the future. Runtime configurable settings feature allows the user to configure the otaclient's behavior via environmental variables. Other changes include: 1. now importing otaclient.configs module and its sub modules with _ prefix will not have side-effect(i.e., ecu_info.yaml being loaded, etc.), the configs loading are done by the otaclient.configs.cfg module. 2. cleanup, re-arrange, merge and simplify some of the settings. 3. ecu_info and proxy_info config objects now are exposed via otaclient.configs.cfg. --- src/otaclient/app/configs.py | 6 +- src/otaclient/app/ota_client_stub.py | 5 +- src/otaclient/boot_control/configs.py | 2 +- src/otaclient/configs/__init__.py | 22 +++ src/otaclient/configs/_cfg_configurable.py | 134 ++++++++++++++++++ src/otaclient/configs/_cfg_consts.py | 94 ++++++++++++ .../configs/{ecu_info.py => _ecu_info.py} | 5 - .../configs/{proxy_info.py => _proxy_info.py} | 5 - src/otaclient/configs/cfg.py | 61 ++++++++ .../test_configs/test_cfg_configurable.py | 50 +++++++ .../test_configs/test_cfg_consts.py | 31 ++++ .../test_configs/test_ecu_info.py | 12 +- .../test_configs/test_proxy_info.py | 3 +- tests/test_otaclient/test_ota_client.py | 2 +- .../test_otaclient/test_ota_client_service.py | 2 +- tests/test_otaclient/test_ota_client_stub.py | 5 +- 16 files changed, 411 insertions(+), 28 deletions(-) create mode 100644 src/otaclient/configs/_cfg_configurable.py create mode 100644 src/otaclient/configs/_cfg_consts.py rename src/otaclient/configs/{ecu_info.py => _ecu_info.py} (96%) rename src/otaclient/configs/{proxy_info.py => _proxy_info.py} (97%) create mode 100644 src/otaclient/configs/cfg.py create mode 100644 tests/test_otaclient/test_configs/test_cfg_configurable.py create mode 100644 tests/test_otaclient/test_configs/test_cfg_consts.py diff --git a/src/otaclient/app/configs.py b/src/otaclient/app/configs.py index 9a76ab8b7..84420636d 100644 --- a/src/otaclient/app/configs.py +++ b/src/otaclient/app/configs.py @@ -17,8 +17,10 @@ from logging import INFO from typing import Dict, Tuple -from otaclient.configs.ecu_info import ecu_info # noqa -from otaclient.configs.proxy_info import proxy_info # noqa +from otaclient.configs.cfg import ( + ecu_info, # noqa + proxy_info, # noqa +) class CreateStandbyMechanism(Enum): diff --git a/src/otaclient/app/ota_client_stub.py b/src/otaclient/app/ota_client_stub.py index 9371413ca..864b5c680 100644 --- a/src/otaclient/app/ota_client_stub.py +++ b/src/otaclient/app/ota_client_stub.py @@ -32,13 +32,14 @@ from ota_proxy import config as local_otaproxy_cfg from otaclient import log_setting from otaclient.boot_control._common import CMDHelperFuncs -from otaclient.configs.ecu_info import ECUContact +from otaclient.configs import ECUContact +from otaclient.configs.cfg import ecu_info, proxy_info from otaclient_api.v2 import types as api_types from otaclient_api.v2.api_caller import ECUNoResponse, OTAClientCall from otaclient_common.common import ensure_otaproxy_start from .configs import config as cfg -from .configs import ecu_info, proxy_info, server_cfg +from .configs import server_cfg from .ota_client import OTAClientControlFlags, OTAServicer logger = logging.getLogger(__name__) diff --git a/src/otaclient/boot_control/configs.py b/src/otaclient/boot_control/configs.py index 852f2854a..126088e91 100644 --- a/src/otaclient/boot_control/configs.py +++ b/src/otaclient/boot_control/configs.py @@ -18,7 +18,7 @@ from dataclasses import dataclass from otaclient.app.configs import BaseConfig -from otaclient.configs.ecu_info import BootloaderType +from otaclient.configs import BootloaderType @dataclass diff --git a/src/otaclient/configs/__init__.py b/src/otaclient/configs/__init__.py index 55ed90a28..177d4a0ee 100644 --- a/src/otaclient/configs/__init__.py +++ b/src/otaclient/configs/__init__.py @@ -12,3 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. """otaclient configs package.""" + +from otaclient.configs._cfg_configurable import ( + ENV_PREFIX, + ConfigurableSettings, + set_configs, +) +from otaclient.configs._cfg_consts import Consts, CreateStandbyMechanism, dynamic_root +from otaclient.configs._ecu_info import BootloaderType, ECUContact, ECUInfo +from otaclient.configs._proxy_info import ProxyInfo + +__all__ = [ + "ENV_PREFIX", + "ConfigurableSettings", + "Consts", + "CreateStandbyMechanism", + "BootloaderType", + "ECUContact", + "ECUInfo", + "ProxyInfo", + "set_configs", + "dynamic_root", +] diff --git a/src/otaclient/configs/_cfg_configurable.py b/src/otaclient/configs/_cfg_configurable.py new file mode 100644 index 000000000..b59f40730 --- /dev/null +++ b/src/otaclient/configs/_cfg_configurable.py @@ -0,0 +1,134 @@ +# Copyright 2022 TIER IV, INC. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Runtime configurable configs for otaclient.""" + + +from __future__ import annotations + +import logging +from typing import Dict, Literal + +from pydantic import BaseModel +from pydantic_settings import BaseSettings, SettingsConfigDict + +from otaclient.configs._cfg_consts import cfg_consts + +logger = logging.getLogger(__name__) + +ENV_PREFIX = "OTACLIENT_" +LOG_LEVEL_LITERAL = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] +CREATE_STANDBY_METHOD_LTIERAL = Literal["REBUILD", "IN_PLACE"] + + +class _OTAClientSettings(BaseModel): + # + # ------ logging settings ------ # + # + DEFAULT_LOG_LEVEL: LOG_LEVEL_LITERAL = "INFO" + LOG_LEVEL_TABLE: Dict[str, LOG_LEVEL_LITERAL] = { + "ota_metadata": "INFO", + "otaclient": "INFO", + "otaclient_api": "INFO", + "otaclient_common": "INFO", + "otaproxy": "INFO", + } + LOG_FORMAT: str = ( + "[%(asctime)s][%(levelname)s]-%(name)s:%(funcName)s:%(lineno)d,%(message)s" + ) + + # + # ------ downloading settings ------ # + # + DOWNLOAD_RETRY_PRE_REQUEST: int = 3 + DOWNLOAD_BACKOFF_MAX: int = 3 # seconds + DOWNLOAD_BACKOFF_FACTOR: float = 0.1 # seconds + + DOWNLOAD_THREADS: int = 6 + MAX_CONCURRENT_DOWNLOAD_TASKS: int = 128 + DOWNLOAD_INACTIVE_TIMEOUT: int = 5 * 60 # seconds + + # + # ------ create standby settings ------ # + # + CREATE_STANDBY_METHOD: CREATE_STANDBY_METHOD_LTIERAL = "REBUILD" + MAX_CONCURRENT_PROCESS_FILE_TASKS: int = 512 + MAX_PROCESS_FILE_THREAD: int = 6 + CREATE_STANDBY_RETRY_MAX: int = 1024 + + # + # ------ debug flags ------ # + # + DEBUG_ENABLE_FAILURE_TRACEBACK_IN_STATUS_RESP: bool = False + DEBUG_DISABLE_OTAPROXY_HTTPS_VERIFY: bool = False + DEBUG_DISABLE_OTAMETA_CERT_CHECK: bool = False + DEBUG_DISABLE_OTAMETA_SIGN_CHECK: bool = False + + +class _MultipleECUSettings(BaseModel): + # The timeout of waiting sub ECU acks the OTA request. + WAITING_SUBECU_ACK_REQ_TIMEOUT: int = 6 + + # The timeout of waiting sub ECU responds to status API request + QUERYING_SUBECU_STATUS_TIMEOUT: int = 6 + + # The ECU status storage will summarize the stored ECUs' status report + # and generate overall status report for all ECUs every seconds. + OVERALL_ECUS_STATUS_UPDATE_INTERVAL: int = 6 # seconds + + # If ECU has been disconnected longer than seconds, it will be + # treated as UNREACHABLE, and will not be counted when generating overall + # ECUs status report. + # NOTE: unreachable_timeout should be larger than + # downloading_group timeout + ECU_UNREACHABLE_TIMEOUT: int = 20 * 60 # seconds + + # Otaproxy should not be shutdowned with less than seconds + # after it just starts to prevent repeatedly start/stop cycle. + OTAPROXY_MINIMUM_SHUTDOWN_INTERVAL: int = 1 * 60 # seconds + + # When any ECU acks update request, this ECU will directly set the overall ECU status + # to any_in_update=True, any_requires_network=True, all_success=False, to prevent + # pre-mature overall ECU status changed caused by child ECU delayed ack to update request. + # + # This pre-set overall ECU status will be kept for seconds. + # This value is expected to be larger than the time cost for subECU acks the OTA request. + PAUSED_OVERALL_ECUS_STATUS_CHANGE_ON_UPDATE_REQ_ACKED: int = 60 # seconds + + +class _OTAProxySettings(BaseModel): + OTAPROXY_ENABLE_EXTERNAL_CACHE: bool = True + EXTERNAL_CACHE_DEV_FSLABEL: str = "ota_cache_src" + EXTERNAL_CACHE_DEV_MOUNTPOINT: str = f"{cfg_consts.MOUNT_SPACE}/external_cache" + EXTERNAL_CACHE_SRC_PATH: str = f"{EXTERNAL_CACHE_DEV_MOUNTPOINT}/data" + + +class ConfigurableSettings(_OTAClientSettings, _MultipleECUSettings, _OTAProxySettings): + """otaclient runtime configuration settings.""" + + +def set_configs() -> ConfigurableSettings: + try: + + class _SettingParser(ConfigurableSettings, BaseSettings): + model_config = SettingsConfigDict( + validate_default=True, + env_prefix=ENV_PREFIX, + ) + + _parsed_setting = _SettingParser() + return ConfigurableSettings.model_construct(**_parsed_setting.model_dump()) + except Exception as e: + logger.error(f"failed to parse otaclient configurable settings: {e!r}") + logger.warning("use default settings ...") + return ConfigurableSettings() diff --git a/src/otaclient/configs/_cfg_consts.py b/src/otaclient/configs/_cfg_consts.py new file mode 100644 index 000000000..37e7f0853 --- /dev/null +++ b/src/otaclient/configs/_cfg_consts.py @@ -0,0 +1,94 @@ +# Copyright 2022 TIER IV, INC. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""otaclient internal uses consts, should not be changed from external.""" + + +from __future__ import annotations + +from enum import Enum + +from otaclient_common import replace_root + +CANONICAL_ROOT = "/" + + +class CreateStandbyMechanism(str, Enum): + LEGACY = "LEGACY" # deprecated and removed + REBUILD = "REBUILD" # default + IN_PLACE = "IN_PLACE" # not yet implemented + + +class Consts: + + @property + def ACTIVE_ROOT(self) -> str: # NOSONAR + return self._ACTIVE_ROOT + + # + # ------ paths ------ # + # + RUN_DIR = "/run/otaclient" + OTACLIENT_PID_FILE = "/run/otaclient.pid" + + # runtime folder for holding ota related files + RUNTIME_OTA_SESSION = "/run/otaclient/ota" + + MOUNT_SPACE = "/run/otaclient/mnt" + ACTIVE_SLOT_MNT = "/run/otaclient/mnt/active_slot" + STANDBY_SLOT_MNT = "/run/otaclient/mnt/standby_slot" + + OTA_TMP_STORE = "/.ota-tmp" + """tmp store for local copy, located at standby slot.""" + + OPT_OTA_DPATH = "/opt/ota" + OTACLIENT_INSTALLATION = "/opt/ota/client" + CERT_DPATH = "/opt/ota/client/certs" + IMAGE_META_DPATH = "/opt/ota/image-meta" + + BOOT_DPATH = "/boot" + OTA_DPATH = "/boot/ota" + ECU_INFO_FPATH = "/boot/ota/ecu_info.yaml" + PROXY_INFO_FPATH = "/boot/ota/proxy_info.yaml" + + ETC_DPATH = "/etc" + PASSWD_FPATH = "/etc/passwd" + GROUP_FPATH = "/etc/group" + FSTAB_FPATH = "/etc/fstab" + + # + # ------ consts ------ # + # + # ota status files + OTA_STATUS_FNAME = "status" + OTA_VERSION_FNAME = "version" + SLOT_IN_USE_FNAME = "slot_in_use" + + OTA_API_SERVER_PORT = 50051 + OTAPROXY_LISTEN_PORT = 8082 + + def __init__(self) -> None: + """For future updating the ACTIVE_ROOT.""" + + # TODO: detect rootfs here + self._ACTIVE_ROOT = CANONICAL_ROOT + + +cfg_consts = Consts() + + +def dynamic_root(canonical_path: str) -> str: + """Re-root the input path with the actual ACTIVE_ROOT.""" + if cfg_consts.ACTIVE_ROOT == CANONICAL_ROOT: + return canonical_path + return replace_root(canonical_path, CANONICAL_ROOT, cfg_consts.ACTIVE_ROOT) diff --git a/src/otaclient/configs/ecu_info.py b/src/otaclient/configs/_ecu_info.py similarity index 96% rename from src/otaclient/configs/ecu_info.py rename to src/otaclient/configs/_ecu_info.py index 08b850bb7..ffd294dce 100644 --- a/src/otaclient/configs/ecu_info.py +++ b/src/otaclient/configs/_ecu_info.py @@ -133,8 +133,3 @@ def parse_ecu_info(ecu_info_file: StrOrPath) -> ECUInfo: logger.warning(f"{ecu_info_file=} is invalid: {e!r}\n{_raw_yaml_str=}") logger.warning(f"use default ecu_info: {DEFAULT_ECU_INFO}") return DEFAULT_ECU_INFO - - -# NOTE(20240327): set the default as literal for now, -# in the future this will be app_cfg.ECU_INFO_FPATH -ecu_info = parse_ecu_info(ecu_info_file="/boot/ota/ecu_info.yaml") diff --git a/src/otaclient/configs/proxy_info.py b/src/otaclient/configs/_proxy_info.py similarity index 97% rename from src/otaclient/configs/proxy_info.py rename to src/otaclient/configs/_proxy_info.py index db16a1844..f3e49da24 100644 --- a/src/otaclient/configs/proxy_info.py +++ b/src/otaclient/configs/_proxy_info.py @@ -147,8 +147,3 @@ def parse_proxy_info(proxy_info_file: StrOrPath) -> ProxyInfo: logger.warning(f"{proxy_info_file=} is invalid: {e!r}\n{_raw_yaml_str=}") logger.warning(f"use default proxy_info: {DEFAULT_PROXY_INFO}") return DEFAULT_PROXY_INFO - - -# NOTE(20240327): set the default as literal for now, -# in the future this will be app_cfg.PROXY_INFO_FPATH -proxy_info = parse_proxy_info("/boot/ota/proxy_info.yaml") diff --git a/src/otaclient/configs/cfg.py b/src/otaclient/configs/cfg.py new file mode 100644 index 000000000..0c5cdce9b --- /dev/null +++ b/src/otaclient/configs/cfg.py @@ -0,0 +1,61 @@ +# Copyright 2022 TIER IV, INC. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Load ecu_info, proxy_info and otaclient configs.""" + +from typing import TYPE_CHECKING, Any + +from otaclient.configs._cfg_configurable import ConfigurableSettings, set_configs +from otaclient.configs._cfg_consts import Consts +from otaclient.configs._ecu_info import ( + BootloaderType, + ECUContact, + ECUInfo, + parse_ecu_info, +) +from otaclient.configs._proxy_info import ProxyInfo, parse_proxy_info + +__all__ = [ + "BootloaderType", + "ECUContact", + "ECUInfo", + "ecu_info", + "ProxyInfo", + "proxy_info", + "cfg", +] + +cfg_configurable = set_configs() +cfg_consts = Consts() + +if TYPE_CHECKING: + + class _OTAClientConfigs(ConfigurableSettings, Consts): + """OTAClient configs.""" + +else: + + class _OTAClientConfigs: + + def __getattribute__(self, name: str) -> Any: + for _cfg in [cfg_consts, cfg_configurable]: + try: + return getattr(_cfg, name) + except AttributeError: + continue + raise AttributeError(f"no such config field: {name=}") + + +cfg = _OTAClientConfigs() +ecu_info = parse_ecu_info(ecu_info_file=cfg.ECU_INFO_FPATH) +proxy_info = parse_proxy_info(proxy_info_file=cfg.PROXY_INFO_FPATH) diff --git a/tests/test_otaclient/test_configs/test_cfg_configurable.py b/tests/test_otaclient/test_configs/test_cfg_configurable.py new file mode 100644 index 000000000..0c4daaa87 --- /dev/null +++ b/tests/test_otaclient/test_configs/test_cfg_configurable.py @@ -0,0 +1,50 @@ +# Copyright 2022 TIER IV, INC. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import annotations + +import pytest +from pytest import MonkeyPatch + +from otaclient.configs import ENV_PREFIX, set_configs + + +@pytest.mark.parametrize( + "setting, env_value, value", + ( + ("DEFAULT_LOG_LEVEL", "CRITICAL", "CRITICAL"), + ( + "LOG_LEVEL_TABLE", + r"""{"ota_metadata": "DEBUG"}""", + {"ota_metadata": "DEBUG"}, + ), + ("DEBUG_DISABLE_OTAPROXY_HTTPS_VERIFY", "true", True), + ("DOWNLOAD_INACTIVE_TIMEOUT", "200", 200), + ), +) +def test_load_configs(setting, env_value, value, monkeypatch: MonkeyPatch): + monkeypatch.setenv(f"{ENV_PREFIX}{setting}", env_value, value) + + mocked_configs = set_configs() + assert getattr(mocked_configs, setting) == value + + +def test_load_default_on_invalid_envs(monkeypatch: MonkeyPatch) -> None: + # first get a normal configs without any envs + normal_cfg = set_configs() + # patch envs + monkeypatch.setenv(f"{ENV_PREFIX}DOWNLOAD_INACTIVE_TIMEOUT", "not_an_int") + # check if config is the defualt one + assert normal_cfg == set_configs() diff --git a/tests/test_otaclient/test_configs/test_cfg_consts.py b/tests/test_otaclient/test_configs/test_cfg_consts.py new file mode 100644 index 000000000..e0f97c3ae --- /dev/null +++ b/tests/test_otaclient/test_configs/test_cfg_consts.py @@ -0,0 +1,31 @@ +# Copyright 2022 TIER IV, INC. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import annotations + +from pytest_mock import MockerFixture + +from otaclient.configs import Consts, _cfg_consts, dynamic_root + + +def test_cfg_consts_dynamic_root(mocker: MockerFixture): + _real_root = "/host_root" + mocked_consts = Consts() + mocker.patch.object(mocked_consts, "_ACTIVE_ROOT", _real_root) + mocker.patch.object(_cfg_consts, "cfg_consts", mocked_consts) + + assert mocked_consts.ACTIVE_ROOT == _real_root + assert dynamic_root(mocked_consts.BOOT_DPATH) == "/host_root/boot" + assert mocked_consts.OTA_API_SERVER_PORT == 50051 diff --git a/tests/test_otaclient/test_configs/test_ecu_info.py b/tests/test_otaclient/test_configs/test_ecu_info.py index fed9ca83a..5c4b640f1 100644 --- a/tests/test_otaclient/test_configs/test_ecu_info.py +++ b/tests/test_otaclient/test_configs/test_ecu_info.py @@ -20,13 +20,8 @@ import pytest -from otaclient.configs.ecu_info import ( - DEFAULT_ECU_INFO, - BootloaderType, - ECUContact, - ECUInfo, - parse_ecu_info, -) +from otaclient.configs import BootloaderType, ECUContact, ECUInfo +from otaclient.configs._ecu_info import DEFAULT_ECU_INFO, parse_ecu_info @pytest.mark.parametrize( @@ -84,6 +79,7 @@ "format_version: 1\n" 'ecu_id: "autoware"\n' 'ip_addr: "192.168.1.1"\n' + "bootloader: grub\n" 'available_ecu_ids: ["autoware", "p1", "p2"]\n' "secondaries: \n" '- ecu_id: "p1"\n' @@ -94,7 +90,7 @@ ECUInfo( ecu_id="autoware", ip_addr=IPv4Address("192.168.1.1"), - bootloader=BootloaderType.AUTO_DETECT, + bootloader=BootloaderType.GRUB, available_ecu_ids=["autoware", "p1", "p2"], secondaries=[ ECUContact( diff --git a/tests/test_otaclient/test_configs/test_proxy_info.py b/tests/test_otaclient/test_configs/test_proxy_info.py index 57a539be3..5f66e985b 100644 --- a/tests/test_otaclient/test_configs/test_proxy_info.py +++ b/tests/test_otaclient/test_configs/test_proxy_info.py @@ -20,7 +20,8 @@ import pytest -from otaclient.configs.proxy_info import DEFAULT_PROXY_INFO, ProxyInfo, parse_proxy_info +from otaclient.configs import ProxyInfo +from otaclient.configs._proxy_info import DEFAULT_PROXY_INFO, parse_proxy_info logger = logging.getLogger(__name__) diff --git a/tests/test_otaclient/test_ota_client.py b/tests/test_otaclient/test_ota_client.py index 0ec96fa89..52bfe4ae9 100644 --- a/tests/test_otaclient/test_ota_client.py +++ b/tests/test_otaclient/test_ota_client.py @@ -39,7 +39,7 @@ ) from otaclient.boot_control import BootControllerProtocol from otaclient.boot_control.configs import BootloaderType -from otaclient.configs.ecu_info import ECUInfo +from otaclient.configs import ECUInfo from otaclient.create_standby import StandbySlotCreatorProtocol from otaclient.create_standby.common import DeltaBundle, RegularDelta from otaclient_api.v2 import types as api_types diff --git a/tests/test_otaclient/test_ota_client_service.py b/tests/test_otaclient/test_ota_client_service.py index 76810d737..42dc4505a 100644 --- a/tests/test_otaclient/test_ota_client_service.py +++ b/tests/test_otaclient/test_ota_client_service.py @@ -22,7 +22,7 @@ from otaclient.app.configs import server_cfg from otaclient.app.main import create_otaclient_grpc_server -from otaclient.configs.ecu_info import ECUInfo +from otaclient.configs import ECUInfo from otaclient_api.v2 import types as api_types from otaclient_api.v2.api_caller import OTAClientCall from tests.utils import compare_message diff --git a/tests/test_otaclient/test_ota_client_stub.py b/tests/test_otaclient/test_ota_client_stub.py index 3c57cc7bb..6a4ea2ca5 100644 --- a/tests/test_otaclient/test_ota_client_stub.py +++ b/tests/test_otaclient/test_ota_client_stub.py @@ -32,8 +32,9 @@ OTAClientServiceStub, OTAProxyLauncher, ) -from otaclient.configs.ecu_info import ECUInfo, parse_ecu_info -from otaclient.configs.proxy_info import ProxyInfo, parse_proxy_info +from otaclient.configs import ECUInfo, ProxyInfo +from otaclient.configs._ecu_info import parse_ecu_info +from otaclient.configs._proxy_info import parse_proxy_info from otaclient_api.v2 import types as api_types from otaclient_api.v2.api_caller import OTAClientCall from tests.conftest import cfg