first commit
This commit is contained in:
140
app/services/proxmox_service.py
Normal file
140
app/services/proxmox_service.py
Normal file
@@ -0,0 +1,140 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user