Files
pvemanager/backend/app/proxmox/mixins/lxc.py
Dilshod Rasulov bfd92102cf
Some checks failed
Backend Tests / pytest (Python 3.12) (push) Failing after 2m27s
Refactor backend: Split models and proxmox_client into modules, add alembic
2026-06-13 03:53:27 +05:00

195 lines
9.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.
from typing import List, Dict, Optional, Union, Any
import time
import urllib3
import shlex
from loguru import logger
class LxcMixin:
def start_container(self, node: str, vmid: int) -> Optional[str]:
"""Запустить LXC контейнер. Возвращает UPID задачи или None при ошибке."""
if not self.proxmox:
return None
try:
upid = self.proxmox.nodes(node).lxc(vmid).status.start.post()
return upid if isinstance(upid, str) else None
except Exception as e:
logger.error(f"Ошибка запуска LXC {vmid} на {node}: {e}")
return None
def stop_container(self, node: str, vmid: int, force: bool = False) -> Optional[str]:
"""Остановить LXC контейнер. Возвращает UPID задачи или None при ошибке."""
if not self.proxmox:
return None
try:
upid = self.proxmox.nodes(node).lxc(vmid).status.stop.post()
return upid if isinstance(upid, str) else None
except Exception as e:
logger.error(f"Ошибка остановки LXC {vmid} на {node}: {e}")
return None
def restart_container(self, node: str, vmid: int) -> Optional[str]:
"""Перезапустить LXC контейнер. Возвращает UPID задачи или None при ошибке."""
if not self.proxmox:
return None
try:
upid = self.proxmox.nodes(node).lxc(vmid).status.reboot.post()
return upid if isinstance(upid, str) else None
except Exception as e:
logger.error(f"Ошибка перезапуска LXC {vmid} на {node}: {e}")
return None
def shutdown_container(self, node: str, vmid: int, timeout: int = 60) -> Optional[str]:
"""Корректно выключить LXC. Возвращает UPID задачи."""
if not self.proxmox:
return None
try:
upid = self.proxmox.nodes(node).lxc(vmid).status.shutdown.post(timeout=timeout)
return upid if isinstance(upid, str) else None
except Exception as e:
logger.error(f"Ошибка корректного выключения LXC {vmid} на {node}: {e}")
return None
def clone_container(self, node: str, vmid: int, new_vmid: int, hostname: str,
full: bool = True, target_node: Optional[str] = None,
target_storage: Optional[str] = None,
description: Optional[str] = None) -> Optional[str]:
"""Клонировать LXC контейнер."""
if not self.proxmox:
return None
try:
params = {'newid': new_vmid, 'hostname': hostname, 'full': 1 if full else 0}
if target_storage:
params['storage'] = target_storage
if target_node and target_node != node:
params['target'] = target_node
if description:
params['description'] = description
upid = self.proxmox.nodes(node).lxc(vmid).clone.post(**params)
return upid if isinstance(upid, str) else None
except Exception as e:
logger.error(f"Ошибка клонирования LXC {vmid} -> {new_vmid}: {e}")
return None
def change_container_password(self, node: str, vmid: int, username: str, password: str) -> Dict:
"""
Сменить пароль пользователя в LXC через `pct exec` (chpasswd).
Контейнер должен быть запущен.
"""
if not self.proxmox:
return {'success': False, 'error': 'Proxmox connection not initialized'}
try:
result = self.proxmox.nodes(node).lxc(vmid).status.current.get()
if result.get('status') != 'running':
return {'success': False, 'error': 'Container is not running'}
# Используем printf + chpasswd; shlex.quote защищает от shell-injection
# Proxmox exec API ожидает отдельные поля: command + args[N]
entry = shlex.quote(f'{username}:{password}')
cmd_str = f"printf '%s\\n' {entry} | chpasswd"
data = {'command': 'bash', 'args[0]': '-c', 'args[1]': cmd_str}
res = self.proxmox.nodes(node).lxc(vmid).exec.post(**data)
return {'success': True, 'result': res}
except Exception as e:
logger.error(f"Не удалось сменить пароль в LXC {vmid}: {e}")
return {'success': False, 'error': str(e)}
def set_container_notes(self, node: str, vmid: int, description: str) -> bool:
"""Установить description (заметки) у LXC."""
if not self.proxmox:
return False
try:
self.proxmox.nodes(node).lxc(vmid).config.put(description=description or '')
return True
except Exception as e:
logger.error(f"Ошибка обновления заметок LXC {vmid}: {e}")
return False
def force_stop_container(self, node: str, vmid: int) -> bool:
"""Принудительно остановить LXC контейнер (через SSH - аналог kill -9)"""
if not self.proxmox or not self.host:
return False
try:
# Пробуем остановить через SSH команду
# Сначала получаем информацию о контейнере
container_info = self.proxmox.nodes(node).lxc(vmid).status.current.get()
if container_info.get('status') != 'running':
logger.info(f"Контейнер {vmid} на {node} уже остановлен")
return True
# Используем обычный stop - Proxmox должен остановить его
try:
self.proxmox.nodes(node).lxc(vmid).status.stop.post()
logger.info(f"Принудительная остановка контейнера {vmid} на {node} выполнена")
return True
except Exception as api_error:
logger.warning(f"Прямой stop не сработал, ошибка: {api_error}")
return False
except Exception as e:
logger.error(f"Ошибка принудительной остановки контейнера {vmid}: {e}")
return False
def get_container_status(self, node: str, vmid: int) -> Optional[Dict]:
"""Получить статус конкретного LXC контейнера"""
if not self.proxmox:
return None
try:
status = self.proxmox.nodes(node).lxc(vmid).status.current.get()
return status
except Exception as e:
logger.error(f"Ошибка получения статуса LXC {vmid} на {node}: {e}")
return None
def get_container_stats(self, node: str, vmid: int) -> Optional[Dict]:
"""Получить статистику LXC контейнера (CPU, память, диск)"""
return self.get_container_status(node, vmid)
def get_container_rrddata(self, node: str, vmid: int, timeframe: str = "hour") -> Dict:
"""
Получить исторические данные контейнера для графиков
Args:
node: Имя ноды
vmid: ID контейнера
timeframe: Период времени (hour, day, week, month, year)
Returns:
Dict с временными рядами данных
"""
if not self.proxmox:
return {}
try:
rrddata = self.proxmox.nodes(node).lxc(vmid).rrddata.get(timeframe=timeframe)
return {'data': rrddata, 'timeframe': timeframe}
except Exception as e:
logger.error(f"Ошибка получения RRD данных LXC {vmid} на {node}: {e}")
return {}
def get_container_vnc(self, node: str, vmid: int) -> Dict:
"""
Получить данные для VNC подключения к LXC контейнеру
Args:
node: Имя ноды
vmid: ID контейнера
Returns:
Dict с данными VNC (port, ticket, cert, upid)
"""
if not self.proxmox:
return {}
try:
# Создать терминал прокси сессию для LXC
vnc_data = self.proxmox.nodes(node).lxc(vmid).vncproxy.post(websocket=1)
return vnc_data
except Exception as e:
logger.error(f"Ошибка получения VNC для LXC {vmid} на {node}: {e}")
return {}