import httpx from typing import List, Optional, Dict from app.config import settings import json class ProxmoxService: """Proxmox VE API 통신 서비스""" def __init__(self): self.base_url = settings.PROXMOX_HOST self.api_token = settings.PROXMOX_API_TOKEN self.verify_ssl = settings.PROXMOX_VERIFY_SSL async def _make_request(self, method: str, endpoint: str, **kwargs) -> Dict: """Proxmox API 요청""" url = f"{self.base_url}/api2/json{endpoint}" headers = {"Authorization": self.api_token} async with httpx.AsyncClient(verify=self.verify_ssl, timeout=30.0) as client: response = await client.request(method, url, headers=headers, **kwargs) response.raise_for_status() return response.json() async def get_nodes(self) -> List[Dict]: """노드 목록 조회""" result = await self._make_request("GET", "/nodes") return result.get("data", []) async def get_vms(self, node: str) -> List[Dict]: """특정 노드의 VM 목록 조회""" result = await self._make_request("GET", f"/nodes/{node}/qemu") return result.get("data", []) async def get_all_vms(self) -> List[Dict]: """모든 노드의 VM 목록 조회""" nodes = await self.get_nodes() print(f"DEBUG: 노드 목록: {nodes}") all_vms = [] for node in nodes: node_name = node.get("node") if node_name: vms = await self.get_vms(node_name) print(f"DEBUG: {node_name} 노드의 VM 목록: {vms}") for vm in vms: vm["node"] = node_name all_vms.append(vm) print(f"DEBUG: 전체 VM 개수: {len(all_vms)}") print(f"DEBUG: 전체 VM 목록: {all_vms}") return all_vms async def get_vm_status(self, node: str, vm_id: int) -> Dict: """VM 상태 조회""" result = await self._make_request("GET", f"/nodes/{node}/qemu/{vm_id}/status/current") return result.get("data", {}) async def get_vm_ip(self, node: str, vm_id: int) -> Optional[str]: """QEMU Guest Agent를 통해 VM IP 주소 조회""" try: result = await self._make_request( "GET", f"/nodes/{node}/qemu/{vm_id}/agent/network-get-interfaces" ) interfaces = result.get("data", {}).get("result", []) for iface in interfaces: # loopback 제외 if "loopback" in iface.get("name", "").lower(): continue ip_addresses = iface.get("ip-addresses", []) for ip in ip_addresses: if ip.get("ip-address-type") == "ipv4": address = ip.get("ip-address") # 사설 IP만 반환 if self._is_private_ip(address): return address return None except: return None async def start_vm(self, node: str, vm_id: int) -> bool: """VM 시작""" try: await self._make_request("POST", f"/nodes/{node}/qemu/{vm_id}/status/start") return True except: return False async def stop_vm(self, node: str, vm_id: int) -> bool: """VM 종료""" try: await self._make_request("POST", f"/nodes/{node}/qemu/{vm_id}/status/stop") return True except: return False async def reboot_vm(self, node: str, vm_id: int) -> bool: """VM 재시작""" try: await self._make_request("POST", f"/nodes/{node}/qemu/{vm_id}/status/reboot") return True except: return False def _is_private_ip(self, ip: str) -> bool: """사설 IP 주소 확인""" if not ip: return False parts = ip.split(".") if len(parts) != 4: return False try: first = int(parts[0]) second = int(parts[1]) # 10.0.0.0/8 if first == 10: return True # 172.16.0.0/12 if first == 172 and 16 <= second <= 31: return True # 192.168.0.0/16 if first == 192 and second == 168: return True return False except: return False # 싱글톤 인스턴스 proxmox_service = ProxmoxService()