Files
xingrin/backend/apps/engine/services/worker_service.py
2025-12-19 19:48:01 +08:00

191 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
WorkerNode 业务逻辑服务层Service
负责 Worker 节点相关的业务逻辑处理
"""
import logging
from typing import Any
from apps.engine.repositories import DjangoWorkerRepository
logger = logging.getLogger(__name__)
class WorkerService:
"""Worker 节点业务逻辑服务"""
def __init__(self) -> None:
"""初始化服务,注入 Repository 依赖"""
self.repo = DjangoWorkerRepository()
# ==================== 查询 ====================
def get_worker(self, worker_id: int):
"""根据 ID 获取 Worker 节点"""
return self.repo.get_by_id(worker_id)
def get_all_workers(self):
"""获取所有 Worker 节点查询集"""
return self.repo.get_all()
# ==================== 状态更新 ====================
def update_status(self, worker_id: int, status: str) -> bool:
"""更新 Worker 节点状态
Args:
worker_id: Worker ID
status: 状态 (pending/deploying/online/offline)
"""
return self.repo.update_status(worker_id, status)
def delete_worker(self, worker_id: int) -> bool:
"""删除 Worker 节点"""
return self.repo.delete_by_id(worker_id)
# ==================== 自注册 ====================
def register_worker(self, name: str, is_local: bool = True):
"""
注册 Worker 节点(本地 Worker 自注册用)
幂等操作:已存在则返回现有节点。
Args:
name: Worker 名称
is_local: 是否为本地节点
Returns:
(WorkerNode, created) 元组
"""
return self.repo.get_or_create_by_name(
name=name,
is_local=is_local
)
def remote_uninstall(
self,
worker_id: int,
ip_address: str,
ssh_port: int,
username: str,
password: str | None
) -> tuple[bool, str]:
"""
在远程主机上执行卸载脚本
Args:
worker_id: Worker ID仅用于日志
ip_address: SSH 主机地址
ssh_port: SSH 端口
username: SSH 用户名
password: SSH 密码
Returns:
(success, message) 元组
"""
if not password:
return False, "未配置 SSH 密码,跳过远程卸载"
try:
import paramiko
from apps.engine.services.deploy_service import get_uninstall_script
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
logger.info(f"[卸载] 正在连接 {ip_address}...")
ssh.connect(
ip_address,
port=ssh_port,
username=username,
password=password,
timeout=30
)
# 上传卸载脚本
uninstall_script = get_uninstall_script()
remote_script_path = '/tmp/xingrin_uninstall.sh'
sftp = ssh.open_sftp()
with sftp.file(remote_script_path, 'w') as f:
f.write(uninstall_script)
sftp.chmod(remote_script_path, 0o755)
sftp.close()
# 执行卸载脚本
logger.info(f"[卸载] 正在执行卸载脚本...")
stdin, stdout, stderr = ssh.exec_command(f"bash {remote_script_path}")
exit_status = stdout.channel.recv_exit_status()
ssh.close()
if exit_status == 0:
logger.info(f"[卸载] Worker {worker_id} 远程卸载成功")
return True, "远程卸载成功"
else:
error = stderr.read().decode().strip()
logger.warning(f"[卸载] Worker {worker_id} 远程卸载失败: {error}")
return False, f"远程卸载失败: {error}"
except Exception as e:
logger.warning(f"[卸载] Worker {worker_id} 远程卸载异常: {e}")
return False, f"远程卸载异常: {str(e)}"
def execute_remote_command(
self,
ip_address: str,
ssh_port: int,
username: str,
password: str | None,
command: str
) -> tuple[bool, str]:
"""
在远程主机上执行命令
Args:
ip_address: SSH 主机地址
ssh_port: SSH 端口
username: SSH 用户名
password: SSH 密码
command: 要执行的命令
Returns:
(success, message) 元组
"""
if not password:
return False, "未配置 SSH 密码"
try:
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(
ip_address,
port=ssh_port,
username=username,
password=password,
timeout=30
)
stdin, stdout, stderr = ssh.exec_command(command, timeout=120)
exit_status = stdout.channel.recv_exit_status()
ssh.close()
if exit_status == 0:
return True, stdout.read().decode().strip()
else:
error = stderr.read().decode().strip()
return False, error
except Exception as e:
return False, str(e)
__all__ = ["WorkerService"]