Changes Initial commit
This commit is contained in:
41
config.example.yaml
Normal file
41
config.example.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 19090
|
||||
|
||||
vehicle:
|
||||
vehicle_id: vehicle-test-001
|
||||
vin: vehicle-test-001
|
||||
current_release: vehicle-release-0.0.1
|
||||
|
||||
cloud:
|
||||
base_url: http://your-ota-server:8080
|
||||
heartbeat_path: /api/agent/heartbeat
|
||||
update_check_path: /api/agent/update-check
|
||||
report_path: /api/agent/report
|
||||
timeout_seconds: 10
|
||||
token: change-me
|
||||
token_header: X-OTA-TOKEN
|
||||
|
||||
compose:
|
||||
working_dir: D:/Procedure/noblelift/hangzhou/OTA-Agent/runtime
|
||||
file: docker-compose.yml
|
||||
env_file: ota-images.env
|
||||
backup_env_file: ota-images.env.bak
|
||||
pull_command: docker compose pull
|
||||
up_command: docker compose up -d
|
||||
health_check_seconds: 30
|
||||
healthcheck_interval_seconds: 3
|
||||
healthcheck_url: http://127.0.0.1:8081/actuator/health
|
||||
|
||||
polling:
|
||||
update_interval_seconds: 30
|
||||
heartbeat_interval_seconds: 30
|
||||
|
||||
storage:
|
||||
state_file: D:/Procedure/noblelift/hangzhou/OTA-Agent/runtime/state.json
|
||||
manifest_dir: D:/Procedure/noblelift/hangzhou/OTA-Agent/runtime/manifests
|
||||
log_dir: D:/Procedure/noblelift/hangzhou/OTA-Agent/runtime/logsmysql_backup:
|
||||
enabled: false
|
||||
backup_dir: D:/Procedure/noblelift/hangzhou/OTA-Agent/runtime/mysql-backups
|
||||
dump_command: docker exec mysql mysqldump -uroot -p123456 app_db > {backup_file}
|
||||
timeout_seconds: 120
|
||||
50
config.yaml
Normal file
50
config.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 19090
|
||||
|
||||
vehicle:
|
||||
vehicle_id: agent-001
|
||||
vin: agent-001
|
||||
current_release: vehicle-release-0.0.1
|
||||
|
||||
cloud:
|
||||
base_url: http://192.168.10.193:8080
|
||||
heartbeat_path: /api/agent/heartbeat
|
||||
update_check_path: /api/agent/update-check
|
||||
report_path: /api/agent/report
|
||||
timeout_seconds: 10
|
||||
token: f47ac10b-58cc-4372-a567-0e02b2c3d479
|
||||
token_header: X-OTA-TOKEN
|
||||
|
||||
registry:
|
||||
enabled: true
|
||||
server: 125.122.25.219:5000
|
||||
username: admin
|
||||
password: "123456"
|
||||
timeout_seconds: 30
|
||||
|
||||
compose:
|
||||
working_dir: /home/liejiu/ota-agent/runtime
|
||||
file: docker-compose.yml
|
||||
env_file: ota-images.env
|
||||
backup_env_file: ota-images.env.bak
|
||||
pull_command: docker compose pull
|
||||
up_command: docker compose up -d
|
||||
health_check_seconds: 60
|
||||
healthcheck_interval_seconds: 3
|
||||
healthcheck_url: http://127.0.0.1:8011/actuator/health
|
||||
|
||||
polling:
|
||||
update_interval_seconds: 30
|
||||
heartbeat_interval_seconds: 30
|
||||
|
||||
storage:
|
||||
state_file: /home/liejiu/ota-agent/runtime/state.json
|
||||
manifest_dir: /home/liejiu/ota-agent/runtime/manifests
|
||||
log_dir: /home/liejiu/ota-agent/runtime/logs
|
||||
|
||||
mysql_backup:
|
||||
enabled: true
|
||||
backup_dir: /home/liejiu/ota-agent/runtime/mysql-backups
|
||||
dump_command: docker exec mysql mysqldump -uroot -pnlrobot nl_frobot > {backup_file}
|
||||
timeout_seconds: 120
|
||||
BIN
dist/main.exe
vendored
Normal file
BIN
dist/main.exe
vendored
Normal file
Binary file not shown.
21
main.py
Normal file
21
main.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import uvicorn
|
||||
|
||||
from ota_agent.app import create_app
|
||||
from ota_agent.config import load_config
|
||||
from ota_agent.logging_utils import setup_logging
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config_path = os.environ.get("OTA_AGENT_CONFIG", "config.yaml")
|
||||
config = load_config(config_path)
|
||||
setup_logging(config.storage.log_dir)
|
||||
app = create_app(config)
|
||||
uvicorn.run(app, host=config.server.host, port=config.server.port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
38
main.spec
Normal file
38
main.spec
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='main',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
1
ota_agent/__init__.py
Normal file
1
ota_agent/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""OTA Agent package."""
|
||||
306
ota_agent/app.py
Normal file
306
ota_agent/app.py
Normal file
@@ -0,0 +1,306 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
|
||||
from .cloud_client import CloudClient
|
||||
from .compose_manager import ComposeManager
|
||||
from .config import AgentConfig
|
||||
from .manifest_store import ManifestStore
|
||||
from .models import (
|
||||
AgentStatus,
|
||||
ConfirmUpgradeRequest,
|
||||
HeartbeatPayload,
|
||||
LocalStatusResponse,
|
||||
OperationResult,
|
||||
PostponeUpgradeRequest,
|
||||
ReportPayload,
|
||||
UpdateCheckRequest,
|
||||
utc_now,
|
||||
)
|
||||
from .mysql_backup import MysqlBackupManager
|
||||
from .registry_login import RegistryLoginManager
|
||||
from .state_store import StateStore
|
||||
|
||||
|
||||
class AgentService:
|
||||
def __init__(self, config: AgentConfig) -> None:
|
||||
self.config = config
|
||||
self.compose = ComposeManager(config.compose)
|
||||
self.cloud = CloudClient(config.cloud)
|
||||
self.state_store = StateStore(config.storage.state_file)
|
||||
self.manifest_store = ManifestStore(config.storage.manifest_dir)
|
||||
self.mysql_backup = MysqlBackupManager(
|
||||
enabled=config.mysql_backup.enabled,
|
||||
backup_dir=config.mysql_backup.backup_dir,
|
||||
dump_command=config.mysql_backup.dump_command,
|
||||
timeout_seconds=config.mysql_backup.timeout_seconds,
|
||||
compose_manager=self.compose,
|
||||
)
|
||||
self.registry_login = RegistryLoginManager(
|
||||
enabled=config.registry.enabled,
|
||||
server=config.registry.server,
|
||||
username=config.registry.username,
|
||||
password=config.registry.password,
|
||||
timeout_seconds=config.registry.timeout_seconds,
|
||||
compose_manager=self.compose,
|
||||
)
|
||||
self.state = self.state_store.load(
|
||||
vehicle_id=config.vehicle.vehicle_id,
|
||||
vin=config.vehicle.vin,
|
||||
current_release=config.vehicle.current_release,
|
||||
)
|
||||
self.lock = asyncio.Lock()
|
||||
self.upgrade_task: asyncio.Task[None] | None = None
|
||||
self.background_tasks: list[asyncio.Task[None]] = []
|
||||
self.last_backup_file: str | None = None
|
||||
self.last_target_release: str | None = None
|
||||
self.last_images: dict[str, str] = {}
|
||||
|
||||
async def startup(self) -> None:
|
||||
self.background_tasks = [
|
||||
asyncio.create_task(self._heartbeat_loop(), name="heartbeat-loop"),
|
||||
asyncio.create_task(self._update_loop(), name="update-loop"),
|
||||
]
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
for task in self.background_tasks:
|
||||
task.cancel()
|
||||
for task in self.background_tasks:
|
||||
with suppress(asyncio.CancelledError):
|
||||
await task
|
||||
|
||||
def get_local_status(self) -> LocalStatusResponse:
|
||||
return LocalStatusResponse(
|
||||
vehicle_id=self.state.vehicle_id,
|
||||
vin=self.state.vin,
|
||||
current_release=self.state.current_release,
|
||||
status=self.state.status.value,
|
||||
available_update=self.state.available_update,
|
||||
last_result=self.state.last_result,
|
||||
updated_at=self.state.updated_at,
|
||||
)
|
||||
|
||||
async def postpone_upgrade(self, request: PostponeUpgradeRequest) -> OperationResult:
|
||||
async with self.lock:
|
||||
if not self.state.available_update:
|
||||
return OperationResult(success=False, detail="当前没有可延期的升级任务")
|
||||
self.state.status = AgentStatus.WAIT_USER_CONFIRM
|
||||
self.state.last_result = f"用户稍后提醒: {request.reason}"
|
||||
self._touch_and_save()
|
||||
await self._safe_report(AgentStatus.WAIT_USER_CONFIRM, self.state.last_result)
|
||||
return OperationResult(success=True, detail="已记录稍后提醒")
|
||||
|
||||
async def confirm_upgrade(self, request: ConfirmUpgradeRequest) -> OperationResult:
|
||||
async with self.lock:
|
||||
if not self.state.available_update:
|
||||
raise HTTPException(status_code=400, detail="当前没有可升级版本")
|
||||
if self.upgrade_task and not self.upgrade_task.done():
|
||||
return OperationResult(success=False, detail="升级任务正在执行中")
|
||||
self.state.last_result = f"用户已确认升级: {request.confirmed_by}"
|
||||
self._touch_and_save()
|
||||
self.upgrade_task = asyncio.create_task(self._execute_upgrade(), name="upgrade-task")
|
||||
return OperationResult(success=True, detail="升级任务已启动")
|
||||
|
||||
async def check_update_once(self) -> None:
|
||||
async with self.lock:
|
||||
payload = UpdateCheckRequest(
|
||||
vehicle_id=self.state.vehicle_id,
|
||||
vin=self.state.vin,
|
||||
current_release=self.state.current_release,
|
||||
)
|
||||
try:
|
||||
response = await self.cloud.check_update(payload)
|
||||
except Exception as exc:
|
||||
async with self.lock:
|
||||
self.state.last_check_at = utc_now()
|
||||
self.state.last_result = f"检查更新失败: {exc}"
|
||||
self._touch_and_save()
|
||||
return
|
||||
|
||||
async with self.lock:
|
||||
self.state.last_check_at = utc_now()
|
||||
if response.has_update and response.manifest:
|
||||
self.manifest_store.save(response.manifest)
|
||||
self.state.available_update = response.manifest
|
||||
self.state.status = AgentStatus.WAIT_USER_CONFIRM
|
||||
self.state.last_result = f"发现新版本 {response.manifest.release_version},等待人工确认"
|
||||
else:
|
||||
if self.state.status in {AgentStatus.IDLE, AgentStatus.SUCCESS, AgentStatus.WAIT_USER_CONFIRM}:
|
||||
self.state.status = AgentStatus.IDLE
|
||||
self.state.available_update = None
|
||||
self.state.last_result = response.message or "当前无可用更新"
|
||||
self._touch_and_save()
|
||||
|
||||
async def heartbeat_once(self) -> None:
|
||||
async with self.lock:
|
||||
if self.state.status == AgentStatus.SUCCESS:
|
||||
self.state.status = AgentStatus.IDLE
|
||||
self.state.last_result = self.state.last_result or "升级成功,恢复空闲状态"
|
||||
self._touch_and_save()
|
||||
payload = HeartbeatPayload(
|
||||
vehicle_id=self.state.vehicle_id,
|
||||
vin=self.state.vin,
|
||||
current_release=self.state.current_release,
|
||||
agent_status=self.state.status.value,
|
||||
target_release=self.last_target_release,
|
||||
last_result=self.state.last_result,
|
||||
images=self.last_images,
|
||||
backup_file=self.last_backup_file,
|
||||
updated_at=self.state.updated_at,
|
||||
)
|
||||
try:
|
||||
await self.cloud.heartbeat(payload)
|
||||
async with self.lock:
|
||||
self.state.last_heartbeat_at = utc_now()
|
||||
self._touch_and_save()
|
||||
except Exception as exc:
|
||||
async with self.lock:
|
||||
self.state.last_result = f"心跳上报失败: {exc}"
|
||||
self._touch_and_save()
|
||||
|
||||
async def _heartbeat_loop(self) -> None:
|
||||
while True:
|
||||
await self.heartbeat_once()
|
||||
await asyncio.sleep(self.config.polling.heartbeat_interval_seconds)
|
||||
|
||||
async def _update_loop(self) -> None:
|
||||
while True:
|
||||
await self.check_update_once()
|
||||
await asyncio.sleep(self.config.polling.update_interval_seconds)
|
||||
|
||||
async def _execute_upgrade(self) -> None:
|
||||
async with self.lock:
|
||||
manifest = self.state.available_update
|
||||
if not manifest:
|
||||
return
|
||||
target_release = manifest.release_version
|
||||
env_mapping = manifest.components.to_env_mapping()
|
||||
self.last_target_release = target_release
|
||||
self.last_images = dict(env_mapping)
|
||||
self.last_backup_file = None
|
||||
self.state.status = AgentStatus.BACKING_UP_DATABASE
|
||||
self.state.last_result = f"开始升级到 {target_release},先执行数据库备份"
|
||||
self._touch_and_save()
|
||||
await self._safe_report(AgentStatus.BACKING_UP_DATABASE, f"开始升级到 {target_release},先执行数据库备份", target_release)
|
||||
|
||||
try:
|
||||
backup_result = await self.mysql_backup.backup_before_upgrade(target_release)
|
||||
if not backup_result.success:
|
||||
raise RuntimeError(f"数据库备份失败: {backup_result.stderr or backup_result.stdout}")
|
||||
|
||||
backup_file_path = (backup_result.stdout or "").strip()
|
||||
self.last_backup_file = backup_file_path or None
|
||||
backup_file_name = Path(backup_file_path).name if backup_file_path else ""
|
||||
backup_message = f"数据库备份完成,开始拉取镜像: {target_release}"
|
||||
if backup_file_name:
|
||||
backup_message = f"数据库备份完成({backup_file_name}),开始拉取镜像: {target_release}"
|
||||
|
||||
async with self.lock:
|
||||
self.state.status = AgentStatus.PULLING_IMAGE
|
||||
self.state.last_result = backup_message
|
||||
self._touch_and_save()
|
||||
await self._safe_report(AgentStatus.PULLING_IMAGE, backup_message, target_release)
|
||||
|
||||
login_result = await self.registry_login.login_if_needed()
|
||||
if not login_result.success:
|
||||
raise RuntimeError(f"私有仓库登录失败: {login_result.stderr or login_result.stdout}")
|
||||
|
||||
await self.compose.apply_manifest(env_mapping)
|
||||
pull_result = await self.compose.pull()
|
||||
if not pull_result.success:
|
||||
raise RuntimeError(f"拉取镜像失败: {pull_result.stderr or pull_result.stdout}")
|
||||
|
||||
async with self.lock:
|
||||
self.state.status = AgentStatus.RESTARTING_SERVICE
|
||||
self.state.last_result = "镜像拉取完成,开始重启服务"
|
||||
self._touch_and_save()
|
||||
await self._safe_report(AgentStatus.RESTARTING_SERVICE, "镜像拉取完成,开始重启服务", target_release)
|
||||
|
||||
up_result = await self.compose.up()
|
||||
if not up_result.success:
|
||||
raise RuntimeError(f"服务启动失败: {up_result.stderr or up_result.stdout}")
|
||||
|
||||
async with self.lock:
|
||||
self.state.status = AgentStatus.HEALTH_CHECKING
|
||||
self.state.last_result = "服务已启动,开始健康检查"
|
||||
self._touch_and_save()
|
||||
await self._safe_report(AgentStatus.HEALTH_CHECKING, "服务已启动,开始健康检查", target_release)
|
||||
|
||||
health = await self.compose.health_check()
|
||||
if not health.success:
|
||||
raise RuntimeError(health.detail)
|
||||
|
||||
async with self.lock:
|
||||
self.state.status = AgentStatus.SUCCESS
|
||||
self.state.current_release = target_release
|
||||
self.state.available_update = None
|
||||
self.state.last_result = f"升级成功: {target_release}"
|
||||
self._touch_and_save()
|
||||
await self._safe_report(AgentStatus.SUCCESS, f"升级成功: {target_release}", target_release)
|
||||
self.last_target_release = None
|
||||
except Exception as exc:
|
||||
await self.compose.rollback()
|
||||
async with self.lock:
|
||||
self.state.status = AgentStatus.ROLLED_BACK
|
||||
self.state.last_result = f"升级失败并已回滚: {exc}"
|
||||
self._touch_and_save()
|
||||
await self._safe_report(AgentStatus.ROLLED_BACK, f"升级失败并已回滚: {exc}", target_release)
|
||||
|
||||
async def _safe_report(self, status: AgentStatus, detail: str, release_version: str | None = None) -> None:
|
||||
payload = ReportPayload(
|
||||
vehicle_id=self.state.vehicle_id,
|
||||
vin=self.state.vin,
|
||||
current_release=self.state.current_release,
|
||||
target_release=release_version or self.last_target_release,
|
||||
agent_status=status.value,
|
||||
success=status == AgentStatus.SUCCESS,
|
||||
message=detail,
|
||||
images=self.last_images,
|
||||
backup_file=self.last_backup_file,
|
||||
)
|
||||
with suppress(Exception):
|
||||
await self.cloud.report(payload)
|
||||
|
||||
def _touch_and_save(self) -> None:
|
||||
self.state.updated_at = utc_now()
|
||||
self.state_store.save(self.state)
|
||||
|
||||
|
||||
def create_app(config: AgentConfig) -> FastAPI:
|
||||
service = AgentService(config)
|
||||
app = FastAPI(title="Vehicle OTA Agent", version="0.1.0")
|
||||
|
||||
@app.on_event("startup")
|
||||
async def on_startup() -> None:
|
||||
await service.startup()
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def on_shutdown() -> None:
|
||||
await service.shutdown()
|
||||
|
||||
@app.get("/health")
|
||||
async def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/ota/status", response_model=LocalStatusResponse)
|
||||
async def local_status() -> LocalStatusResponse:
|
||||
return service.get_local_status()
|
||||
|
||||
@app.post("/ota/check-update")
|
||||
async def check_update() -> OperationResult:
|
||||
await service.check_update_once()
|
||||
return OperationResult(success=True, detail="已执行一次检查更新")
|
||||
|
||||
@app.post("/ota/confirm", response_model=OperationResult)
|
||||
async def confirm_upgrade(request: ConfirmUpgradeRequest) -> OperationResult:
|
||||
return await service.confirm_upgrade(request)
|
||||
|
||||
@app.post("/ota/postpone", response_model=OperationResult)
|
||||
async def postpone_upgrade(request: PostponeUpgradeRequest) -> OperationResult:
|
||||
return await service.postpone_upgrade(request)
|
||||
|
||||
return app
|
||||
48
ota_agent/cloud_client.py
Normal file
48
ota_agent/cloud_client.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import httpx
|
||||
|
||||
from .config import CloudConfig
|
||||
from .models import HeartbeatPayload, ReportPayload, UpdateCheckRequest, UpdateCheckResponse
|
||||
|
||||
|
||||
class CloudClient:
|
||||
def __init__(self, config: CloudConfig) -> None:
|
||||
self.config = config
|
||||
self.timeout = httpx.Timeout(config.timeout_seconds)
|
||||
|
||||
def _headers(self) -> dict[str, str]:
|
||||
return {
|
||||
self.config.token_header: self.config.token,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
async def heartbeat(self, payload: HeartbeatPayload) -> None:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
self.config.base_url.rstrip("/") + self.config.heartbeat_path,
|
||||
headers=self._headers(),
|
||||
json=payload.model_dump(by_alias=True),
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
async def check_update(self, payload: UpdateCheckRequest) -> UpdateCheckResponse:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
self.config.base_url.rstrip("/") + self.config.update_check_path,
|
||||
headers=self._headers(),
|
||||
json=payload.model_dump(by_alias=True),
|
||||
)
|
||||
response.raise_for_status()
|
||||
return UpdateCheckResponse(**response.json())
|
||||
|
||||
async def report(self, payload: ReportPayload) -> None:
|
||||
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
||||
response = await client.post(
|
||||
self.config.base_url.rstrip("/") + self.config.report_path,
|
||||
headers=self._headers(),
|
||||
json=payload.model_dump(by_alias=True),
|
||||
)
|
||||
response.raise_for_status()
|
||||
123
ota_agent/compose_manager.py
Normal file
123
ota_agent/compose_manager.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
from .config import ComposeConfig
|
||||
from .models import CommandResult, HealthcheckResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ComposeManager:
|
||||
def __init__(self, config: ComposeConfig) -> None:
|
||||
self.config = config
|
||||
self.working_dir = Path(config.working_dir)
|
||||
self.working_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.compose_file_path = self.working_dir / config.file
|
||||
self.env_path = self.working_dir / config.env_file
|
||||
self.backup_env_path = self.working_dir / config.backup_env_file
|
||||
|
||||
async def apply_manifest(self, env_mapping: dict[str, str]) -> None:
|
||||
current_text = self.env_path.read_text(encoding="utf-8") if self.env_path.exists() else ""
|
||||
if self.env_path.exists():
|
||||
self.backup_env_path.write_text(current_text, encoding="utf-8")
|
||||
logger.info("已备份镜像环境文件: %s", self.backup_env_path)
|
||||
|
||||
env_lines = self._merge_env(current_text, env_mapping)
|
||||
self.env_path.write_text("\n".join(env_lines) + "\n", encoding="utf-8")
|
||||
logger.info("已写入新镜像配置到 %s", self.env_path)
|
||||
|
||||
async def rollback(self) -> None:
|
||||
if self.backup_env_path.exists():
|
||||
self.env_path.write_text(self.backup_env_path.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
logger.warning("升级失败,已恢复镜像环境文件: %s", self.env_path)
|
||||
await self.pull()
|
||||
await self.up()
|
||||
|
||||
async def pull(self) -> CommandResult:
|
||||
return await self.run_command(self._build_command(self.config.pull_command))
|
||||
|
||||
async def up(self) -> CommandResult:
|
||||
return await self.run_command(self._build_command(self.config.up_command))
|
||||
|
||||
async def health_check(self) -> HealthcheckResult:
|
||||
if not self.config.healthcheck_url:
|
||||
return HealthcheckResult(success=True, detail="healthcheck skipped")
|
||||
|
||||
deadline = asyncio.get_running_loop().time() + self.config.health_check_seconds
|
||||
timeout = httpx.Timeout(min(self.config.healthcheck_interval_seconds, self.config.health_check_seconds))
|
||||
last_detail = "healthcheck not started"
|
||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||
while True:
|
||||
try:
|
||||
response = await client.get(self.config.healthcheck_url)
|
||||
if 200 <= response.status_code < 300:
|
||||
logger.info("健康检查通过: %s", response.status_code)
|
||||
return HealthcheckResult(success=True, detail=f"healthcheck ok: {response.status_code}")
|
||||
last_detail = f"healthcheck failed: {response.status_code} {response.text}"
|
||||
except Exception as exc:
|
||||
last_detail = f"healthcheck error: {exc}"
|
||||
|
||||
if asyncio.get_running_loop().time() >= deadline:
|
||||
logger.error("健康检查失败: %s", last_detail)
|
||||
return HealthcheckResult(success=False, detail=last_detail)
|
||||
await asyncio.sleep(self.config.healthcheck_interval_seconds)
|
||||
|
||||
async def run_command(self, command: list[str]) -> CommandResult:
|
||||
logger.info("执行命令: %s", " ".join(command))
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*command,
|
||||
cwd=str(self.working_dir),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await process.communicate()
|
||||
result = CommandResult(
|
||||
success=process.returncode == 0,
|
||||
stdout=stdout.decode("utf-8", errors="ignore"),
|
||||
stderr=stderr.decode("utf-8", errors="ignore"),
|
||||
returncode=process.returncode or 0,
|
||||
)
|
||||
if result.success:
|
||||
logger.info("命令执行成功(returncode=%s)", result.returncode)
|
||||
else:
|
||||
logger.error("命令执行失败(returncode=%s): %s", result.returncode, result.stderr or result.stdout)
|
||||
return result
|
||||
|
||||
def _build_command(self, command: str) -> list[str]:
|
||||
parts = command.split()
|
||||
if len(parts) >= 2 and parts[0] == "docker" and parts[1] == "compose":
|
||||
return [
|
||||
"docker",
|
||||
"compose",
|
||||
"-f",
|
||||
str(self.compose_file_path),
|
||||
"--env-file",
|
||||
str(self.env_path),
|
||||
*parts[2:],
|
||||
]
|
||||
return [*command.split()]
|
||||
|
||||
def _merge_env(self, current_text: str, env_mapping: dict[str, str]) -> list[str]:
|
||||
data: dict[str, str] = {}
|
||||
order: list[str] = []
|
||||
for raw_line in current_text.splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
key = key.strip()
|
||||
if key not in order:
|
||||
order.append(key)
|
||||
data[key] = value.strip()
|
||||
|
||||
for key, value in env_mapping.items():
|
||||
if key not in order:
|
||||
order.append(key)
|
||||
data[key] = value
|
||||
|
||||
return [f"{key}={data[key]}" for key in order]
|
||||
88
ota_agent/config.py
Normal file
88
ota_agent/config.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class ServerConfig(BaseModel):
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 19090
|
||||
|
||||
|
||||
class VehicleConfig(BaseModel):
|
||||
vehicle_id: str = "vehicle-test-001"
|
||||
vin: str = "vehicle-test-001"
|
||||
current_release: str = "vehicle-release-0.0.1"
|
||||
|
||||
|
||||
class CloudConfig(BaseModel):
|
||||
base_url: str = "http://127.0.0.1:8080"
|
||||
heartbeat_path: str = "/api/agent/heartbeat"
|
||||
update_check_path: str = "/api/agent/update-check"
|
||||
report_path: str = "/api/agent/report"
|
||||
timeout_seconds: int = 10
|
||||
token: str = "change-me"
|
||||
token_header: str = "X-OTA-TOKEN"
|
||||
|
||||
|
||||
class RegistryConfig(BaseModel):
|
||||
enabled: bool = False
|
||||
server: str = ""
|
||||
username: str = ""
|
||||
password: str = ""
|
||||
timeout_seconds: int = 30
|
||||
|
||||
|
||||
class ComposeConfig(BaseModel):
|
||||
working_dir: str
|
||||
file: str = "docker-compose.yml"
|
||||
env_file: str = ".env"
|
||||
backup_env_file: str = ".env.bak"
|
||||
pull_command: str = "docker compose pull"
|
||||
up_command: str = "docker compose up -d"
|
||||
health_check_seconds: int = 15
|
||||
healthcheck_url: str | None = None
|
||||
healthcheck_interval_seconds: int = 3
|
||||
|
||||
|
||||
class PollingConfig(BaseModel):
|
||||
update_interval_seconds: int = 30
|
||||
heartbeat_interval_seconds: int = 30
|
||||
|
||||
|
||||
class StorageConfig(BaseModel):
|
||||
state_file: str
|
||||
manifest_dir: str
|
||||
log_dir: str = "./runtime/logs"
|
||||
|
||||
|
||||
class MysqlBackupConfig(BaseModel):
|
||||
enabled: bool = False
|
||||
backup_dir: str = "./runtime/mysql-backups"
|
||||
dump_command: str = "docker exec mysql mysqldump -uroot -p123456 app_db > {backup_file}"
|
||||
timeout_seconds: int = 120
|
||||
|
||||
|
||||
class AgentConfig(BaseSettings):
|
||||
model_config = SettingsConfigDict(extra="ignore")
|
||||
|
||||
server: ServerConfig = Field(default_factory=ServerConfig)
|
||||
vehicle: VehicleConfig = Field(default_factory=VehicleConfig)
|
||||
cloud: CloudConfig = Field(default_factory=CloudConfig)
|
||||
registry: RegistryConfig = Field(default_factory=RegistryConfig)
|
||||
compose: ComposeConfig = Field(default_factory=lambda: ComposeConfig(working_dir="./runtime"))
|
||||
polling: PollingConfig = Field(default_factory=PollingConfig)
|
||||
storage: StorageConfig = Field(default_factory=lambda: StorageConfig(state_file="./runtime/state.json", manifest_dir="./runtime/manifests", log_dir="./runtime/logs"))
|
||||
mysql_backup: MysqlBackupConfig = Field(default_factory=MysqlBackupConfig)
|
||||
|
||||
|
||||
def load_config(config_path: str | Path) -> AgentConfig:
|
||||
path = Path(config_path)
|
||||
raw: dict[str, Any] = {}
|
||||
if path.exists():
|
||||
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
return AgentConfig(**raw)
|
||||
33
ota_agent/logging_utils.py
Normal file
33
ota_agent/logging_utils.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def setup_logging(log_dir: str) -> None:
|
||||
target_dir = Path(log_dir)
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
log_file = target_dir / "ota-agent.log"
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.INFO)
|
||||
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s [%(levelname)s] %(name)s - %(message)s"
|
||||
)
|
||||
|
||||
file_handler = RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=2 * 1024 * 1024,
|
||||
backupCount=3,
|
||||
encoding="utf-8",
|
||||
)
|
||||
file_handler.setFormatter(formatter)
|
||||
|
||||
stream_handler = logging.StreamHandler()
|
||||
stream_handler.setFormatter(formatter)
|
||||
|
||||
root_logger.handlers.clear()
|
||||
root_logger.addHandler(file_handler)
|
||||
root_logger.addHandler(stream_handler)
|
||||
17
ota_agent/manifest_store.py
Normal file
17
ota_agent/manifest_store.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from .models import UpdateManifest
|
||||
|
||||
|
||||
class ManifestStore:
|
||||
def __init__(self, manifest_dir: str) -> None:
|
||||
self.path = Path(manifest_dir)
|
||||
self.path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def save(self, manifest: UpdateManifest) -> Path:
|
||||
target = self.path / f"{manifest.release_version}.json"
|
||||
target.write_text(json.dumps(manifest.model_dump(), ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
return target
|
||||
130
ota_agent/models.py
Normal file
130
ota_agent/models.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
|
||||
|
||||
def to_camel(value: str) -> str:
|
||||
parts = value.split("_")
|
||||
return parts[0] + "".join(part.capitalize() for part in parts[1:])
|
||||
|
||||
|
||||
class ApiModel(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
|
||||
|
||||
|
||||
class AgentStatus(str, Enum):
|
||||
IDLE = "IDLE"
|
||||
HAS_UPDATE = "HAS_UPDATE"
|
||||
WAIT_USER_CONFIRM = "WAIT_USER_CONFIRM"
|
||||
BACKING_UP_DATABASE = "BACKING_UP_DATABASE"
|
||||
PULLING_IMAGE = "PULLING_IMAGE"
|
||||
RESTARTING_SERVICE = "RESTARTING_SERVICE"
|
||||
HEALTH_CHECKING = "HEALTH_CHECKING"
|
||||
SUCCESS = "SUCCESS"
|
||||
FAILED = "FAILED"
|
||||
ROLLED_BACK = "ROLLED_BACK"
|
||||
|
||||
|
||||
class ComponentImages(ApiModel):
|
||||
images: dict[str, str] = Field(default_factory=dict)
|
||||
|
||||
def to_env_mapping(self) -> dict[str, str]:
|
||||
return {key: value for key, value in self.images.items() if value}
|
||||
|
||||
|
||||
class UpdateManifest(ApiModel):
|
||||
release_version: str
|
||||
release_notes: str = ""
|
||||
components: ComponentImages = Field(default_factory=ComponentImages)
|
||||
upgrade_mode: str = "manual_confirm"
|
||||
|
||||
|
||||
class AgentState(BaseModel):
|
||||
vehicle_id: str
|
||||
vin: str
|
||||
current_release: str
|
||||
status: AgentStatus = AgentStatus.IDLE
|
||||
available_update: UpdateManifest | None = None
|
||||
last_check_at: str | None = None
|
||||
last_heartbeat_at: str | None = None
|
||||
last_result: str | None = None
|
||||
updated_at: str = Field(default_factory=lambda: utc_now())
|
||||
|
||||
|
||||
class HeartbeatPayload(ApiModel):
|
||||
vehicle_id: str
|
||||
vin: str
|
||||
current_release: str
|
||||
agent_status: str
|
||||
target_release: str | None = None
|
||||
last_result: str | None = None
|
||||
images: dict[str, str] = Field(default_factory=dict)
|
||||
backup_file: str | None = None
|
||||
updated_at: str
|
||||
|
||||
|
||||
class UpdateCheckRequest(ApiModel):
|
||||
vehicle_id: str
|
||||
vin: str
|
||||
current_release: str
|
||||
|
||||
|
||||
class UpdateCheckResponse(ApiModel):
|
||||
has_update: bool = False
|
||||
manifest: UpdateManifest | None = None
|
||||
message: str = ""
|
||||
|
||||
|
||||
class ReportPayload(ApiModel):
|
||||
vehicle_id: str
|
||||
vin: str
|
||||
current_release: str
|
||||
target_release: str | None = None
|
||||
agent_status: str
|
||||
success: bool
|
||||
message: str = ""
|
||||
images: dict[str, str] = Field(default_factory=dict)
|
||||
backup_file: str | None = None
|
||||
updated_at: str = Field(default_factory=lambda: utc_now())
|
||||
|
||||
|
||||
class ConfirmUpgradeRequest(BaseModel):
|
||||
confirmed_by: str = "android-app"
|
||||
|
||||
|
||||
class PostponeUpgradeRequest(BaseModel):
|
||||
reason: str = "user_postpone"
|
||||
|
||||
|
||||
class LocalStatusResponse(BaseModel):
|
||||
vehicle_id: str
|
||||
vin: str
|
||||
current_release: str
|
||||
status: str
|
||||
available_update: UpdateManifest | None = None
|
||||
last_result: str | None = None
|
||||
updated_at: str
|
||||
|
||||
|
||||
class CommandResult(BaseModel):
|
||||
success: bool
|
||||
stdout: str = ""
|
||||
stderr: str = ""
|
||||
returncode: int = 0
|
||||
|
||||
|
||||
class HealthcheckResult(BaseModel):
|
||||
success: bool
|
||||
detail: str
|
||||
|
||||
|
||||
class OperationResult(BaseModel):
|
||||
success: bool
|
||||
detail: str
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
59
ota_agent/mysql_backup.py
Normal file
59
ota_agent/mysql_backup.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from .compose_manager import ComposeManager
|
||||
from .models import CommandResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MysqlBackupManager:
|
||||
def __init__(self, *, enabled: bool, backup_dir: str, dump_command: str, timeout_seconds: int, compose_manager: ComposeManager) -> None:
|
||||
self.enabled = enabled
|
||||
self.backup_dir = Path(backup_dir)
|
||||
self.dump_command = dump_command
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.compose_manager = compose_manager
|
||||
if self.enabled:
|
||||
self.backup_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async def backup_before_upgrade(self, target_release: str) -> CommandResult:
|
||||
if not self.enabled:
|
||||
return CommandResult(success=True, stdout="mysql backup skipped", returncode=0)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
backup_file = self.backup_dir / f"mysql-backup-{target_release}-{timestamp}.sql"
|
||||
command = self.dump_command.replace("{backup_file}", str(backup_file))
|
||||
logger.info("开始执行MySQL备份,目标文件: %s", backup_file)
|
||||
result = await self._run_shell_command(command)
|
||||
if result.success:
|
||||
logger.info("MySQL备份完成: %s", backup_file)
|
||||
result.stdout = str(backup_file)
|
||||
else:
|
||||
logger.error("MySQL备份失败: %s", result.stderr or result.stdout)
|
||||
return result
|
||||
|
||||
async def _run_shell_command(self, command: str) -> CommandResult:
|
||||
process = await asyncio.create_subprocess_shell(
|
||||
command,
|
||||
cwd=str(self.compose_manager.working_dir),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=self.timeout_seconds)
|
||||
except asyncio.TimeoutError:
|
||||
process.kill()
|
||||
await process.wait()
|
||||
return CommandResult(success=False, stderr=f"mysql backup timeout after {self.timeout_seconds}s", returncode=-1)
|
||||
|
||||
return CommandResult(
|
||||
success=process.returncode == 0,
|
||||
stdout=stdout.decode("utf-8", errors="ignore"),
|
||||
stderr=stderr.decode("utf-8", errors="ignore"),
|
||||
returncode=process.returncode or 0,
|
||||
)
|
||||
70
ota_agent/registry_login.py
Normal file
70
ota_agent/registry_login.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from .compose_manager import ComposeManager
|
||||
from .models import CommandResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RegistryLoginManager:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
enabled: bool,
|
||||
server: str,
|
||||
username: str,
|
||||
password: str,
|
||||
timeout_seconds: int,
|
||||
compose_manager: ComposeManager,
|
||||
) -> None:
|
||||
self.enabled = enabled
|
||||
self.server = server
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.compose_manager = compose_manager
|
||||
|
||||
async def login_if_needed(self) -> CommandResult:
|
||||
if not self.enabled:
|
||||
return CommandResult(success=True, stdout="registry login skipped", returncode=0)
|
||||
|
||||
command = [
|
||||
"docker",
|
||||
"login",
|
||||
self.server,
|
||||
"-u",
|
||||
self.username,
|
||||
"-p",
|
||||
self.password,
|
||||
]
|
||||
logger.info("开始执行私有仓库登录: %s", self.server)
|
||||
result = await self._run_command(command)
|
||||
if result.success:
|
||||
logger.info("私有仓库登录成功: %s", self.server)
|
||||
else:
|
||||
logger.error("私有仓库登录失败: %s", result.stderr or result.stdout)
|
||||
return result
|
||||
|
||||
async def _run_command(self, command: list[str]) -> CommandResult:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*command,
|
||||
cwd=str(self.compose_manager.working_dir),
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=self.timeout_seconds)
|
||||
except asyncio.TimeoutError:
|
||||
process.kill()
|
||||
await process.wait()
|
||||
return CommandResult(success=False, stderr=f"registry login timeout after {self.timeout_seconds}s", returncode=-1)
|
||||
|
||||
return CommandResult(
|
||||
success=process.returncode == 0,
|
||||
stdout=stdout.decode("utf-8", errors="ignore"),
|
||||
stderr=stderr.decode("utf-8", errors="ignore"),
|
||||
returncode=process.returncode or 0,
|
||||
)
|
||||
39
ota_agent/state_store.py
Normal file
39
ota_agent/state_store.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from .models import AgentState
|
||||
|
||||
|
||||
class StateStore:
|
||||
def __init__(self, state_file: str) -> None:
|
||||
self.path = Path(state_file)
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def load(self, vehicle_id: str, vin: str, current_release: str) -> AgentState:
|
||||
if not self.path.exists() or self._is_empty_file():
|
||||
state = AgentState(vehicle_id=vehicle_id, vin=vin, current_release=current_release)
|
||||
self.save(state)
|
||||
return state
|
||||
|
||||
try:
|
||||
raw = json.loads(self.path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
state = AgentState(vehicle_id=vehicle_id, vin=vin, current_release=current_release)
|
||||
self.save(state)
|
||||
return state
|
||||
|
||||
raw["vehicle_id"] = vehicle_id
|
||||
raw["vin"] = vin
|
||||
raw["current_release"] = current_release
|
||||
return AgentState(**raw)
|
||||
|
||||
def save(self, state: AgentState) -> None:
|
||||
self.path.write_text(
|
||||
state.model_dump_json(indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
def _is_empty_file(self) -> bool:
|
||||
return self.path.stat().st_size == 0
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
fastapi==0.116.1
|
||||
uvicorn[standard]==0.35.0
|
||||
httpx==0.28.1
|
||||
pydantic==2.11.7
|
||||
pydantic-settings==2.10.1
|
||||
PyYAML==6.0.2
|
||||
8
runtime/README.txt
Normal file
8
runtime/README.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# 运行目录
|
||||
|
||||
该目录用于放置车端 `docker compose` 编排文件、`.env` 版本文件、状态文件和 manifest 缓存。
|
||||
|
||||
建议:
|
||||
- 将真实业务的 `docker-compose.yml` 放到这里
|
||||
- 将镜像 tag 写入 `.env`
|
||||
- 让 OTA Agent 只修改 `.env` 并执行 `docker compose pull/up`
|
||||
75
runtime/docker-compose.yml
Normal file
75
runtime/docker-compose.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: ${MYSQL_IMAGE}
|
||||
container_name: mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "123456"
|
||||
MYSQL_DATABASE: nl_frobt
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./mysql/:/docker-entrypoint-initdb.d/
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
redis:
|
||||
image: ${REDIS_IMAGE}
|
||||
container_name: redis
|
||||
restart: always
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- app-network
|
||||
command: redis-server --requirepass 123456
|
||||
|
||||
backend:
|
||||
image: ${BACKEND_IMAGE}
|
||||
container_name: backend
|
||||
restart: always
|
||||
environment:
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://mysql:3306/nl_frobt?serverTimezone=GMT%2B8&characterEncoding=utf-8&userSSL=false
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: "123456"
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
SPRING_DATA_REDIS_PORT: 6379
|
||||
SPRING_DATA_REDIS_PASSWORD: "123456"
|
||||
SA-TOKEN_ALONE-REDIS_HOST: redis
|
||||
SA-TOKEN_ALONE-REDIS_PORT: 6379
|
||||
SA-TOKEN_ALONE-REDIS_PASSWORD: "123456"
|
||||
ports:
|
||||
- "8011:8011"
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
volumes:
|
||||
- /opt/ota-agent/backend-logs:/app/logs
|
||||
- /opt/ota-agent/backend-data/file:/app/data/file
|
||||
- /opt/ota-agent/backend-data/qrcode:/app/data/qrcode
|
||||
- /opt/ota-agent/backend-data/avatar:/app/data/avatar
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
frontend:
|
||||
image: ${FRONTEND_IMAGE}
|
||||
container_name: frontend
|
||||
restart: always
|
||||
ports:
|
||||
- "8013:8013"
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
332
runtime/logs/ota-agent.log
Normal file
332
runtime/logs/ota-agent.log
Normal file
@@ -0,0 +1,332 @@
|
||||
2026-04-17 17:39:50,251 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:39:50,256 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:39:50,270 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:39:50,271 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=WAIT_USER_CONFIRM
|
||||
2026-04-17 17:40:20,570 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:40:20,571 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:40:20,587 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:40:20,589 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:40:50,940 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:40:50,942 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:40:50,957 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:40:50,959 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:41:21,388 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:41:21,389 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:41:21,394 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:41:21,395 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:41:51,735 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:41:51,736 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:41:51,744 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:41:51,745 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:42:22,048 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:42:22,049 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:42:22,069 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:42:22,070 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:42:52,361 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:42:52,362 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:42:52,377 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:42:52,378 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:43:22,672 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:43:22,673 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:43:22,727 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:43:22,728 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:43:52,981 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:43:52,982 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:43:53,014 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:43:53,017 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:44:23,320 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:44:23,321 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:44:23,351 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:44:23,353 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:44:53,632 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:44:53,633 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:44:53,669 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:44:53,671 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:45:23,968 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:45:23,969 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:45:24,112 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:45:24,115 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:45:54,324 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:45:54,325 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:45:54,361 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:45:54,362 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:46:24,662 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:46:24,663 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:46:24,697 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:46:24,699 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:46:54,979 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:46:54,980 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:46:54,990 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:46:54,992 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:47:25,309 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:47:25,310 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:47:25,344 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:47:25,345 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:47:55,616 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:47:55,617 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:47:55,657 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:47:55,659 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:48:25,954 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:48:25,955 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:48:25,990 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:48:25,991 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:48:56,291 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:48:56,292 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:48:56,327 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:48:56,328 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:49:27,215 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:49:27,218 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:49:27,253 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:49:27,257 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:49:58,219 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:49:58,221 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:49:58,264 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:49:58,267 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:50:29,218 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:50:29,220 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:50:29,254 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:50:29,258 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:51:00,200 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:51:00,202 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:51:00,214 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:51:00,216 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:51:30,530 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:51:30,532 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:51:30,567 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:51:30,569 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:52:00,855 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:52:00,857 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:52:00,892 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:52:00,893 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:52:31,205 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:52:31,207 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:52:31,241 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:52:31,242 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:53:01,574 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:53:01,575 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:53:01,580 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:53:01,582 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:53:31,907 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:53:31,908 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:53:31,942 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:53:31,943 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:54:02,216 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:54:02,217 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:54:02,254 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:54:02,257 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:54:32,577 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:54:32,578 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:54:32,611 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:54:32,613 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:55:02,903 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:55:02,904 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:55:02,939 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:55:02,943 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:55:33,231 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:55:33,235 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:55:33,443 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:55:33,446 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:56:03,414 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:56:03,415 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:56:03,676 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:56:03,677 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:56:33,581 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:56:33,582 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:56:33,915 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:56:33,918 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:57:03,780 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:57:03,781 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:57:04,130 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:57:04,132 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:57:33,965 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:57:33,966 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:57:34,337 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:57:34,340 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:58:04,151 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:58:04,152 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:58:04,553 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:58:04,556 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:58:34,367 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:58:34,369 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:58:34,786 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:58:34,788 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:59:04,541 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:59:04,542 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:59:04,993 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:59:04,996 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 17:59:34,708 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 17:59:34,709 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 17:59:35,226 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 17:59:35,227 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:00:04,922 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:00:04,923 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:00:05,426 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:00:05,428 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:00:35,091 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:00:35,092 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:00:35,631 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:00:35,633 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:01:05,266 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:01:05,268 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:01:05,843 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:01:05,844 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:01:35,462 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:01:35,464 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:01:36,023 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:01:36,024 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:02:05,659 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:02:05,660 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:02:06,257 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:02:06,258 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:02:36,083 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:02:36,086 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:02:36,704 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:02:36,706 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:03:06,474 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:03:06,477 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:03:07,316 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:03:07,318 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:03:36,970 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:03:36,972 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:03:37,848 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:03:37,851 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:04:07,434 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:04:07,437 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False
|
||||
2026-04-17 18:04:08,355 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:04:08,359 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:06:10,423 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:06:10,424 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:06:10,462 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:06:10,464 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:06:41,441 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:06:41,444 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:06:41,454 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:06:41,457 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:07:12,398 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:07:12,401 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:07:12,435 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:07:12,438 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:07:42,804 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:07:42,806 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:07:42,814 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:07:42,815 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:08:13,246 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:08:13,247 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:08:13,283 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:08:13,286 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:08:43,665 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:08:43,666 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:08:43,703 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:08:43,706 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:09:14,117 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:09:14,118 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:09:14,153 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:09:14,155 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:09:44,542 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:09:44,543 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:09:44,579 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:09:44,582 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:10:14,950 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:10:14,952 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:10:14,990 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:10:14,992 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:10:45,396 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:10:45,397 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:10:45,433 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:10:45,436 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:11:15,805 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:11:15,807 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:11:15,841 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:11:15,843 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:11:46,221 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:11:46,222 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:11:46,259 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:11:46,262 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:12:16,629 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:12:16,630 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:12:16,669 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:12:16,672 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:12:47,045 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:12:47,047 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:12:47,083 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:12:47,086 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:13:17,487 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:13:17,489 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:13:17,528 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:13:17,530 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:13:47,917 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:13:47,919 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:13:47,966 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:13:47,969 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:14:18,342 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:14:18,344 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:14:18,378 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:14:18,381 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:14:48,786 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:14:48,788 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:14:48,823 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:14:48,825 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:15:19,209 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:15:19,210 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:15:19,245 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:15:19,247 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:15:49,791 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:15:49,793 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:15:49,826 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:15:49,828 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:16:20,746 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:16:20,749 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:16:20,754 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:16:20,756 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:16:51,269 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:16:51,271 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:16:51,303 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:16:51,305 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:17:21,739 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:17:21,742 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:17:21,773 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:17:21,776 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:17:52,148 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:17:52,149 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:17:52,158 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:17:52,159 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:18:22,583 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:18:22,584 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:18:22,593 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:18:22,594 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:18:53,084 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:18:53,086 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:18:53,125 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:18:53,127 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:19:23,866 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:19:23,869 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:19:23,902 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:19:23,905 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:19:54,383 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:19:54,385 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:19:54,420 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:19:54,422 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:20:24,798 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:20:24,799 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:20:24,834 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:20:24,836 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:20:55,273 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:20:55,274 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:20:55,309 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:20:55,311 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:21:25,687 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:21:25,689 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:21:25,725 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:21:25,728 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:21:56,168 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:21:56,170 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:21:56,180 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:21:56,181 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-17 18:22:26,582 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 200 "
|
||||
2026-04-17 18:22:26,584 [INFO] ota_agent.cloud_client - check update vehicleId=vehicle-test-001 hasUpdate=False message=No release assigned
|
||||
2026-04-17 18:22:26,622 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 200 "
|
||||
2026-04-17 18:22:26,625 [INFO] ota_agent.cloud_client - heartbeat ok vehicleId=vehicle-test-001 status=IDLE
|
||||
2026-04-21 17:01:52,646 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 401 "
|
||||
2026-04-21 17:01:52,647 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 401 "
|
||||
2026-04-21 21:10:57,935 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/update-check "HTTP/1.1 401 "
|
||||
2026-04-21 21:10:57,936 [INFO] httpx - HTTP Request: POST http://127.0.0.1:8080/api/agent/heartbeat "HTTP/1.1 401 "
|
||||
12
runtime/manifests/vehicle-release-0.0.2.json
Normal file
12
runtime/manifests/vehicle-release-0.0.2.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"release_version": "vehicle-release-0.0.2",
|
||||
"release_notes": "1. Spring Boot 后端升级到 v0.0.2\n2. 两个 Vue 前端升级到 v0.0.2\n3. 两个 ROS2 程序升级到 v0.0.2\n",
|
||||
"components": {
|
||||
"backend": "repo/backend:v0.0.2",
|
||||
"frontend1": "repo/frontend1:v0.0.2",
|
||||
"frontend2": "repo/frontend2:v0.0.2",
|
||||
"ros2node1": "repo/ros2node1:v0.0.2",
|
||||
"ros2node2": "repo/ros2node2:v0.0.2"
|
||||
},
|
||||
"upgrade_mode": "manual_confirm"
|
||||
}
|
||||
4
runtime/ota-images.env
Normal file
4
runtime/ota-images.env
Normal file
@@ -0,0 +1,4 @@
|
||||
MYSQL_IMAGE=125.122.25.219:5000/ota/mysql:8.0
|
||||
REDIS_IMAGE=125.122.25.219:5000/ota/redis:7-alpine
|
||||
BACKEND_IMAGE=125.122.25.219:5000/ota/backend:v1.0.1
|
||||
FRONTEND_IMAGE=125.122.25.219:5000/ota/frontend:v1.0.2
|
||||
11
runtime/state.json
Normal file
11
runtime/state.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"vehicle_id": "vehicle-test-001",
|
||||
"vin": "vehicle-test-001",
|
||||
"current_release": "vehicle-release-0.0.1",
|
||||
"status": "IDLE",
|
||||
"available_update": null,
|
||||
"last_check_at": "2026-04-21T13:10:57.939089+00:00",
|
||||
"last_heartbeat_at": "2026-04-17T10:22:26.626688+00:00",
|
||||
"last_result": "心跳上报失败: Client error '401 ' for url 'http://127.0.0.1:8080/api/agent/heartbeat'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401",
|
||||
"updated_at": "2026-04-21T13:10:57.940167+00:00"
|
||||
}
|
||||
Reference in New Issue
Block a user