From d2b3421ab2dc4d132aa12df13d95d135e19d82ee Mon Sep 17 00:00:00 2001 From: Bodong Yang <86948717+Bodong-Yang@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:53:13 +0900 Subject: [PATCH] refactor: move app.main as otaclient.main, cleanup (#423) As part of the efforts to disassemble the otaclient.app package, this PR moves the app.main module as otaclient.main module, and does cleanup correspondingly. --- src/otaclient/__main__.py | 2 +- src/otaclient/app/__main__.py | 19 ------------- src/otaclient/{app => }/main.py | 30 ++++---------------- src/otaclient/utils.py | 32 ++++++++++++++++++++++ tests/conftest.py | 1 - tests/test_otaclient/test_main.py | 11 ++++---- tests/test_otaclient/test_otaclient_api.py | 4 +-- 7 files changed, 46 insertions(+), 53 deletions(-) delete mode 100644 src/otaclient/app/__main__.py rename src/otaclient/{app => }/main.py (63%) diff --git a/src/otaclient/__main__.py b/src/otaclient/__main__.py index 32e00d349..9cf1a2ad1 100644 --- a/src/otaclient/__main__.py +++ b/src/otaclient/__main__.py @@ -13,7 +13,7 @@ # limitations under the License. -from otaclient.app import main +from otaclient import main if __name__ == "__main__": main.main() diff --git a/src/otaclient/app/__main__.py b/src/otaclient/app/__main__.py deleted file mode 100644 index b6b9ea884..000000000 --- a/src/otaclient/app/__main__.py +++ /dev/null @@ -1,19 +0,0 @@ -# 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 .main import main - -if __name__ == "__main__": - main() diff --git a/src/otaclient/app/main.py b/src/otaclient/main.py similarity index 63% rename from src/otaclient/app/main.py rename to src/otaclient/main.py index 58ec322fa..3c0830965 100644 --- a/src/otaclient/app/main.py +++ b/src/otaclient/main.py @@ -11,15 +11,13 @@ # 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. +"""Entrypoint of otaclient.""" from __future__ import annotations import asyncio import logging -import os -import sys -from pathlib import Path import grpc.aio @@ -27,34 +25,15 @@ from otaclient.configs.cfg import cfg, ecu_info from otaclient.grpc.api_v2.servicer import OTAClientAPIServicer from otaclient.log_setting import configure_logging +from otaclient.utils import check_other_otaclient, create_otaclient_rundir from otaclient_api.v2 import otaclient_v2_pb2_grpc as v2_grpc from otaclient_api.v2.api_stub import OtaClientServiceV2 -from otaclient_common._io import read_str_from_file, write_str_to_file_atomic # configure logging before any code being executed configure_logging() logger = logging.getLogger(__name__) -def _check_other_otaclient(): - """Check if there is another otaclient instance running.""" - # create a lock file to prevent multiple ota-client instances start - if pid := read_str_from_file(cfg.OTACLIENT_PID_FILE, _default=""): - # running process will have a folder under /proc - if Path(f"/proc/{pid}").is_dir(): - logger.error(f"another instance of ota-client({pid=}) is running, abort") - sys.exit() - else: - logger.warning(f"dangling otaclient lock file({pid=}) detected, cleanup") - Path(cfg.OTACLIENT_PID_FILE).unlink(missing_ok=True) - # create run dir - _run_dir = Path(cfg.RUN_DIR) - _run_dir.mkdir(parents=True, exist_ok=True) - os.chmod(_run_dir, 0o550) - # write our pid to the lock file - write_str_to_file_atomic(cfg.OTACLIENT_PID_FILE, f"{os.getpid()}") - - def create_otaclient_grpc_server(): service_stub = OTAClientAPIServicer() ota_client_service_v2 = OtaClientServiceV2(service_stub) @@ -78,6 +57,7 @@ def main(): logger.info(f"otaclient version: {__version__}") logger.info(f"ecu_info.yaml: \n{ecu_info}") - # start the otaclient grpc server - _check_other_otaclient() + check_other_otaclient(cfg.OTACLIENT_PID_FILE) + create_otaclient_rundir(cfg.RUN_DIR) + asyncio.run(launch_otaclient_grpc_server()) diff --git a/src/otaclient/utils.py b/src/otaclient/utils.py index 0b655de9f..0d8388261 100644 --- a/src/otaclient/utils.py +++ b/src/otaclient/utils.py @@ -18,10 +18,16 @@ import itertools import logging +import os +import sys import time from abc import abstractmethod +from pathlib import Path from typing import Callable, Protocol +from otaclient_common._io import read_str_from_file, write_str_to_file_atomic +from otaclient_common.typing import StrOrPath + logger = logging.getLogger(__name__) @@ -50,3 +56,29 @@ def wait_and_log( log_func(f"wait for {message}: {seconds}s passed ...") log_round = _new_log_round time.sleep(check_interval) + + +def check_other_otaclient(pid_fpath: StrOrPath) -> None: + """Check if there is another otaclient instance running, and then + create a pid lock file for this otaclient instance.""" + pid_fpath = Path(pid_fpath) + + if pid := read_str_from_file(pid_fpath, _default=""): + # running process will have a folder under /proc + if Path(f"/proc/{pid}").is_dir(): + logger.error(f"another instance of ota-client({pid=}) is running, abort") + sys.exit() + + logger.warning(f"dangling otaclient lock file({pid=}) detected, cleanup") + Path(pid_fpath).unlink(missing_ok=True) + + write_str_to_file_atomic(pid_fpath, f"{os.getpid()}") + + +def create_otaclient_rundir(run_dir: StrOrPath = "/run/otaclient"): + """Create the otaclient runtime working dir. + + TODO: make a helper class for managing otaclient runtime dir. + """ + run_dir = Path(run_dir) + run_dir.mkdir(exist_ok=True, parents=True) diff --git a/tests/conftest.py b/tests/conftest.py index fad19f593..38bc50b67 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -62,7 +62,6 @@ class TestConfiguration: OTACLIENT_MODULE_PATH = "otaclient.app.ota_client" OTACLIENT_STUB_MODULE_PATH = "otaclient.app.ota_client_stub" OTAMETA_MODULE_PATH = "ota_metadata.legacy.parser" - MAIN_MODULE_PATH = "otaclient.app.main" # dummy ota-image setting OTA_IMAGE_DIR = "/ota-image" diff --git a/tests/test_otaclient/test_main.py b/tests/test_otaclient/test_main.py index 2efdc5467..1d11649bf 100644 --- a/tests/test_otaclient/test_main.py +++ b/tests/test_otaclient/test_main.py @@ -23,18 +23,19 @@ from pytest_mock import MockerFixture from otaclient.configs.cfg import cfg as otaclient_cfg -from tests.conftest import TestConfiguration as cfg FIRST_LINE_LOG = "d3b6bdb | 2021-10-27 09:36:48 +0900 | Initial commit" +MAIN_MODULE = "otaclient.main" +UTILS_MODULE = "otaclient.utils" class TestMain: @pytest.fixture(autouse=True) def patch_main(self, mocker: MockerFixture, tmp_path: Path): - mocker.patch(f"{cfg.MAIN_MODULE_PATH}.launch_otaclient_grpc_server") + mocker.patch(f"{MAIN_MODULE}.launch_otaclient_grpc_server") self._sys_exit_mocker = mocker.MagicMock(side_effect=ValueError) - mocker.patch(f"{cfg.MAIN_MODULE_PATH}.sys.exit", self._sys_exit_mocker) + mocker.patch(f"{UTILS_MODULE}.sys.exit", self._sys_exit_mocker) @pytest.fixture def background_process(self): @@ -50,14 +51,14 @@ def _waiting(): _p.kill() def test_main(self, caplog: LogCaptureFixture): - from otaclient.app.main import main + from otaclient.main import main main() assert caplog.records[0].msg == "started" assert Path(otaclient_cfg.OTACLIENT_PID_FILE).read_text() == f"{os.getpid()}" def test_with_other_otaclient_started(self, background_process): - from otaclient.app.main import main + from otaclient.main import main _other_pid = f"{background_process}" with pytest.raises(ValueError): diff --git a/tests/test_otaclient/test_otaclient_api.py b/tests/test_otaclient/test_otaclient_api.py index 4e97533db..24dfaae78 100644 --- a/tests/test_otaclient/test_otaclient_api.py +++ b/tests/test_otaclient/test_otaclient_api.py @@ -20,14 +20,14 @@ import pytest import pytest_mock -from otaclient.app.main import create_otaclient_grpc_server from otaclient.configs import ECUInfo from otaclient.configs.cfg import cfg as otaclient_cfg +from otaclient.main import create_otaclient_grpc_server from otaclient_api.v2 import types as api_types from otaclient_api.v2.api_caller import OTAClientCall from tests.utils import compare_message -OTACLIENT_APP_MAIN = "otaclient.app.main" +OTACLIENT_APP_MAIN = "otaclient.main" class _MockedOTAClientAPIServicer: