Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: project restruct phase2 #311

Merged
merged 67 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
a01a875
minor update to .gitignore
Bodong-Yang Jun 2, 2024
230d446
pyproject: fix mistakenly include .gitignore files into the package
Bodong-Yang Jun 2, 2024
b894940
pyproject: not include .github folder in sdist
Bodong-Yang Jun 2, 2024
da19190
add new samples, remove old bootstrap folder
Bodong-Yang Jun 2, 2024
2b81a3f
update README.md
Bodong-Yang Jun 2, 2024
f70a3fd
proto: add a README.md
Bodong-Yang Jun 2, 2024
4d176cb
move proto_wrapper implementation into otaclient_common package
Bodong-Yang Jun 2, 2024
9e074a8
move proto generated codes and wrapper types into a new package otacl…
Bodong-Yang Jun 2, 2024
6b72458
minor update
Bodong-Yang Jun 2, 2024
c91adfb
now otaclient.app.proto is split as otaclient_api.v2 package
Bodong-Yang Jun 2, 2024
23af0ed
(WIP) ota_metadata now is a standalone package
Bodong-Yang Jun 2, 2024
ef6853f
move otaclient.app.common into otaclient_common.common
Bodong-Yang Jun 2, 2024
2b07113
move otaclient.app.downloader into otaclient_common
Bodong-Yang Jun 2, 2024
16d8b1c
move otaclient.app.ota_metadata as ota_metadata.legacy.parser
Bodong-Yang Jun 2, 2024
5ba4ad4
fix paths related for app.boot_control and app.create_standby
Bodong-Yang Jun 2, 2024
b48b364
remove otaclient_service module, merge it into the main module
Bodong-Yang Jun 2, 2024
28fc4c8
remove otaclient_call module, it is replaced by otaclient_api.v2.api_…
Bodong-Yang Jun 2, 2024
a251d98
fix up ota_client_stub module
Bodong-Yang Jun 2, 2024
465815c
minor update to otaclient_api.v2
Bodong-Yang Jun 2, 2024
f7c5f76
minor updates
Bodong-Yang Jun 2, 2024
cd3e887
remove ota_status, merge into otaclient
Bodong-Yang Jun 2, 2024
5677549
otaclient.app: fix up accoringly to modules paths changed
Bodong-Yang Jun 2, 2024
d0e4d54
fix up update_stats
Bodong-Yang Jun 2, 2024
9642d63
fix update otaclient.py accordingly
Bodong-Yang Jun 2, 2024
1231839
apply pre-commit
Bodong-Yang Jun 2, 2024
73ba635
create new test_otaclient_common by merging test_downloader and test_…
Bodong-Yang Jun 2, 2024
db72fad
create new test_ota_metadata
Bodong-Yang Jun 2, 2024
52a2291
test_proto -> test_otaclient_common.test_proto_wrapper
Bodong-Yang Jun 2, 2024
86dd8fe
create test_otaclient_api.test_v2
Bodong-Yang Jun 2, 2024
f996297
finish up create test_otaclient_api.test_v2
Bodong-Yang Jun 2, 2024
47b48dd
re-arrange test_otaclient
Bodong-Yang Jun 2, 2024
96e4524
fix up tests.utils
Bodong-Yang Jun 2, 2024
03f7bd3
fix up test_otaclient_service
Bodong-Yang Jun 2, 2024
d9e0e1f
fix up test_otaclient_stub
Bodong-Yang Jun 2, 2024
6a4a163
fix up test_otaclient and test_persist_file_handling
Bodong-Yang Jun 2, 2024
aaf3cea
fix up test_boot_control
Bodong-Yang Jun 2, 2024
7481498
fix up offline_ota_image_builder
Bodong-Yang Jun 2, 2024
691d927
fix up status_monitor
Bodong-Yang Jun 2, 2024
a91b1ba
apply isort fix
Bodong-Yang Jun 2, 2024
d8202dc
minor fix downloader
Bodong-Yang Jun 2, 2024
84f8049
fix test_ota_metadata and conftest
Bodong-Yang Jun 2, 2024
9f1de8e
fix otaclient_common.common
Bodong-Yang Jun 2, 2024
10290f7
fix up tests
Bodong-Yang Jun 2, 2024
7f0c838
minor docs update
Bodong-Yang Jun 2, 2024
e7b92a6
move otaclient._utils.logging into otaclient_common package
Bodong-Yang Jun 2, 2024
e56354b
move otaclient._utils.typing into otaclient_common package
Bodong-Yang Jun 2, 2024
1aedf9a
move otaclient._utils.linux into otaclient_common package
Bodong-Yang Jun 2, 2024
c2c4964
remove otaclient._utils as it is empty now
Bodong-Yang Jun 2, 2024
5817043
split retry_task_map from otaclient_common.common
Bodong-Yang Jun 2, 2024
4426fad
split persist_files_handling from otaclient_common.common
Bodong-Yang Jun 2, 2024
649321d
add missing otaclient._utils.__init__ back
Bodong-Yang Jun 2, 2024
536e2da
fix up import paths related
Bodong-Yang Jun 2, 2024
3478683
minor fix
Bodong-Yang Jun 2, 2024
a31f411
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
42578a3
otaclient_common: split typing related from __init__ to typing module
Bodong-Yang Jun 3, 2024
d6e7761
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
926a394
pyproject: minor fix
Bodong-Yang Jun 3, 2024
9cb04b7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 3, 2024
94190be
minor fix
Bodong-Yang Jun 3, 2024
108b7bc
pyproject: add more packages to track for coverage
Bodong-Yang Jun 4, 2024
fa35bc6
Merge remote-tracking branch 'origin/main' into refactor/project_rest…
Bodong-Yang Jun 4, 2024
2b2522c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 4, 2024
21f5aaa
move import_from_file method to otaclient_common
Bodong-Yang Jun 4, 2024
301ee98
minor fix
Bodong-Yang Jun 4, 2024
0389977
Merge branch 'main' into refactor/project_restruct_phase2
Bodong-Yang Jun 4, 2024
c23d5dd
app.configs: fix logging settings according to package layout change
Bodong-Yang Jun 5, 2024
0e496f4
not using relative import anymore
Bodong-Yang Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,6 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# build related
build
*.egg-info

# local vscode configs
.devcontainer
.vscode
Expand Down
31 changes: 14 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
# OTA client
# OTAClient

## Overview

This OTA client is a client software to perform over-the-air software updates for linux devices.
To enable updating of software at any layer (kernel, kernel module, user library, user application), the OTA client targets the entire rootfs for updating.
When the OTA client receives an update request, it downloads a list from the OTA server that contains the file paths and the hash values of the files, etc., to be updated, and compares them with the files in its own storage and if there is a match, that file is used to update the rootfs. By this delta mechanism, it is possible to reduce the download size even if the entire rootfs is targeted and this mechanism does not require any specific server implementation, nor does it require the server to keep a delta for each version of the rootfs.
OTAClient is software to perform over-the-air software updates for linux devices.
It provides a set of APIs for user to start the OTA and monitor the progress and status.

It is designed to work with web.auto FMS OTA component.

## Feature

- Rootfs updating
- Delta updating
- Redundant configuration with A/B partition update
- Arbitrary files can be copied from A to B partition. This can be used to take over individual files.
- No specific server implementation is required. The server that supports HTTP GET is only required.
- TLS connection is also required.
- Delta management is not required for server side.
- To restrict access to the server, cookie can be used.
- All files to be updated are verified by the hash included in the metadata, and the metadata is also verified by X.509 certificate locally installed.
- Transfer data is encrypted by TLS
- Multiple ECU(Electronic Control Unit) support
- By the internal proxy cache mechanism, the cache can be used for the download requests to the same file from multiple ECU.
- A/B partition update with support for generic x86_64 device, NVIDIA Jetson series based devices and Raspberry Pi device.
- Full Rootfs update, with delta update support.
- Local delta calculation, allowing update to any version of OTA image without the need of a pre-generated delta OTA package.
- Support persist files from active slot to newly updated slot.
- Verification over OTA image by digital signature and PKI.
- Support for protected OTA server with cookie.
- Optional OTA proxy support and OTA cache support.
- Multiple ECU OTA supports.

## License

OTA client is licensed under the Apache License, Version 2.0.
OTAClient is licensed under the Apache License, Version 2.0.
7 changes: 0 additions & 7 deletions bootstrap/root/boot/ota/ecu_info.yaml

This file was deleted.

3 changes: 3 additions & 0 deletions proto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# OTA Service API proto

This folder includes the OTA service API proto file, and a set of tools to generate the python lib from the proto files.
14 changes: 10 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies = [
"pyopenssl==24.1",
"pyyaml>=3.12",
"requests<2.32,>=2.31",
"typing-extensions>=4.6.3; python_version<'3.11'",
"typing-extensions>=4.6.3",
"urllib3<2,>=1.26.8",
"uvicorn[standard]==0.20",
"zstandard==0.18",
Expand All @@ -60,9 +60,14 @@ version-file = "src/_otaclient_version.py"
[tool.hatch.build.targets.sdist]
exclude = [
"/tools",
".github",
]

[tool.hatch.build.targets.wheel]
exclude = [
"**/.gitignore",
"**/*README.md",
]
only-include = [
"src",
]
Expand Down Expand Up @@ -98,9 +103,6 @@ log_auto_indent = true
log_format = "%(asctime)s %(levelname)s %(filename)s %(funcName)s,%(lineno)d %(message)s"
log_cli = true
log_cli_level = "INFO"
pythonpath = [
"otaclient",
]
testpaths = [
"./tests",
]
Expand All @@ -110,6 +112,10 @@ branch = false
relative_files = true
source = [
"otaclient",
"otaclient_api",
"otaclient_common",
"ota_metadata",
"ota_proxy",
]

[tool.coverage.report]
Expand Down
3 changes: 3 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# OTAClient configuration files samples

This folder contains the sample otaclient configuration files **ecu_info.yaml**, **proxy_info.yaml** and systemd service unit file **otaclient.service** for a single ECU OTA setup.
7 changes: 7 additions & 0 deletions samples/ecu_info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This is the sample ecu_info.yaml for a single x86_64 ECU setup.
# Please check ecu_info.yaml spec for more details: https://tier4.atlassian.net/l/cp/AGmpqFFc.
format_version: 1
ecu_id: autoware
bootloader: grub
available_ecu_ids:
- autoware
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# otaclient.service

[Unit]
Description=OTA Client
After=network-online.target nss-lookup.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/bin/bash -c 'source /opt/ota/.venv/bin/activate && PYTHONPATH=/opt/ota python3 -m otaclient'
ExecStart=/opt/ota/client/venv/bin/python3 -m otaclient
Restart=always
RestartSec=10
RestartSec=16

[Install]
WantedBy=multi-user.target
9 changes: 9 additions & 0 deletions samples/proxy_info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# This is the sample proxy_info.yaml for a single ECU setup.
# Please check proxy_info.yaml spec for more details: https://tier4.atlassian.net/l/cp/qT4N4K0X.
format_version: 1
enable_local_ota_proxy: true
enable_local_ota_proxy_cache: true
local_ota_proxy_listen_addr: 127.0.0.1
local_ota_proxy_listen_port: 8082
# if otaclient-logger is installed locally
logging_server: "http://127.0.0.1:8083"
3 changes: 3 additions & 0 deletions src/ota_metadata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# OTA image metadata

Libs for parsing OTA image.
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,24 @@
# 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.
"""Modules for registering wrapped compiled protobuf types."""
"""OTA image metadata, legacy version."""


from ._common import * # noqa: F403, F401
from ._ota_metafiles_wrapper import * # noqa: F403, F401
from ._otaclient_v2_pb2_wrapper import * # noqa: F403, F401
from __future__ import annotations

import sys
from pathlib import Path

from otaclient_common import import_from_file

SUPORTED_COMPRESSION_TYPES = ("zst", "zstd")

# ------ dynamically import pb2 generated code ------ #

_PROTO_DIR = Path(__file__).parent
_PB2_FPATH = _PROTO_DIR / "ota_metafiles_pb2.py"
_PACKAGE_PREFIX = ".".join(__name__.split(".")[:-1])

_module_name, _module = import_from_file(_PB2_FPATH)
sys.modules[_module_name] = _module
sys.modules[f"{_PACKAGE_PREFIX}.{_module_name}"] = _module
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,17 @@
from typing_extensions import Self

from ota_proxy import OTAFileCacheControl

from .common import RetryTaskMap, get_backoff, urljoin_ensure_base
from .configs import config as cfg
from .downloader import Downloader
from .proto.streamer import Uint32LenDelimitedMsgReader, Uint32LenDelimitedMsgWriter
from .proto.wrapper import (
DirectoryInf,
MessageWrapper,
PersistentInf,
RegularInf,
SymbolicLinkInf,
from otaclient_common.common import get_backoff, urljoin_ensure_base
from otaclient_common.downloader import Downloader
from otaclient_common.proto_streamer import (
Uint32LenDelimitedMsgReader,
Uint32LenDelimitedMsgWriter,
)
from otaclient_common.proto_wrapper import MessageWrapper
from otaclient_common.retry_task_map import RetryTaskMap

from . import SUPORTED_COMPRESSION_TYPES
from .types import DirectoryInf, PersistentInf, RegularInf, SymbolicLinkInf

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -592,10 +591,25 @@ class OTAMetadata:
),
}

def __init__(self, *, url_base: str, downloader: Downloader) -> None:
MAX_COCURRENT = 2
BACKOFF_FACTOR = 1
BACKOFF_MAX = 6

def __init__(
self,
*,
url_base: str,
downloader: Downloader,
run_dir: Path,
certs_dir: Path,
download_max_idle_time: int,
) -> None:
self.url_base = url_base
self._downloader = downloader
self._tmp_dir = TemporaryDirectory(prefix="ota_metadata", dir=cfg.RUN_DIR)
self.run_dir = run_dir
self.certs_dir = certs_dir
self.download_max_idle_time = download_max_idle_time
self._tmp_dir = TemporaryDirectory(prefix="ota_metadata", dir=run_dir)
self._tmp_dir_path = Path(self._tmp_dir.name)

# download and parse the metadata.jwt
Expand All @@ -622,7 +636,7 @@ def _process_metadata_jwt(self) -> _MetadataJWTClaimsLayout:
"""Download, loading and parsing metadata.jwt."""
logger.debug("process metadata.jwt...")
# download and parse metadata.jwt
with NamedTemporaryFile(prefix="metadata_jwt", dir=cfg.RUN_DIR) as meta_f:
with NamedTemporaryFile(prefix="metadata_jwt", dir=self.run_dir) as meta_f:
_downloaded_meta_f = Path(meta_f.name)
self._downloader.download_retry_inf(
urljoin_ensure_base(self.url_base, self.METADATA_JWT),
Expand All @@ -636,13 +650,13 @@ def _process_metadata_jwt(self) -> _MetadataJWTClaimsLayout:
)

_parser = _MetadataJWTParser(
_downloaded_meta_f.read_text(), certs_dir=cfg.CERTS_DIR
_downloaded_meta_f.read_text(), certs_dir=self.certs_dir
)
# get not yet verified parsed ota_metadata
_ota_metadata = _parser.get_otametadata()

# download certificate and verify metadata against this certificate
with NamedTemporaryFile(prefix="metadata_cert", dir=cfg.RUN_DIR) as cert_f:
with NamedTemporaryFile(prefix="metadata_cert", dir=self.run_dir) as cert_f:
cert_info = _ota_metadata.certificate
cert_fname, cert_hash = cert_info.file, cert_info.hash
cert_file = Path(cert_f.name)
Expand Down Expand Up @@ -696,11 +710,11 @@ def _process_text_base_otameta_file(_metafile: MetaFile):

last_active_timestamp = int(time.time())
_mapper = RetryTaskMap(
max_concurrent=cfg.MAX_CONCURRENT_DOWNLOAD_TASKS,
max_concurrent=self.MAX_COCURRENT,
backoff_func=partial(
get_backoff,
factor=cfg.DOWNLOAD_GROUP_BACKOFF_FACTOR,
_max=cfg.DOWNLOAD_GROUP_BACKOFF_MAX,
factor=self.BACKOFF_FACTOR,
_max=self.BACKOFF_MAX,
),
max_retry=0, # NOTE: we use another strategy below
)
Expand All @@ -718,12 +732,9 @@ def _process_text_base_otameta_file(_metafile: MetaFile):
last_active_timestamp = max(
last_active_timestamp, self._downloader.last_active_timestamp
)
if (
int(time.time()) - last_active_timestamp
> cfg.DOWNLOAD_GROUP_INACTIVE_TIMEOUT
):
if int(time.time()) - last_active_timestamp > self.download_max_idle_time:
logger.error(
f"downloader becomes stuck for {cfg.DOWNLOAD_GROUP_INACTIVE_TIMEOUT=} seconds, abort"
f"downloader becomes stuck for {self.download_max_idle_time=} seconds, abort"
)
_mapper.shutdown(raise_last_exc=True)

Expand Down Expand Up @@ -753,7 +764,7 @@ def get_download_url(self, reg_inf: RegularInf) -> Tuple[str, Optional[str]]:
if (
self.image_compressed_rootfs_url
and reg_inf.compressed_alg
and reg_inf.compressed_alg in cfg.SUPPORTED_COMPRESS_ALG
and reg_inf.compressed_alg in SUPORTED_COMPRESSION_TYPES
):
return (
urljoin_ensure_base(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
from pathlib import Path
from typing import Union

import ota_metafiles_pb2 as ota_metafiles

from ._common import MessageWrapper, calculate_slots
from ota_metadata.legacy import ota_metafiles_pb2 as ota_metafiles
from otaclient_common.proto_wrapper import MessageWrapper, calculate_slots

# helper mixin

Expand Down
2 changes: 1 addition & 1 deletion src/ota_proxy/cache_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from typing_extensions import Self

from otaclient._utils import copy_callable_typehint_to_method
from otaclient_common.typing import copy_callable_typehint_to_method

_FIELDS = "_fields"

Expand Down
2 changes: 1 addition & 1 deletion src/ota_proxy/server_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

import aiohttp

from otaclient._utils.logging import BurstSuppressFilter
from otaclient_common.logging import BurstSuppressFilter

from ._consts import (
BHEADER_AUTHORIZATION,
Expand Down
Loading
Loading