refact: use only server

This commit is contained in:
2025-08-27 13:28:50 +02:00
parent cf8a37f183
commit 2934224787
39 changed files with 6843 additions and 1223 deletions

247
backend/app/api/hosts.py Normal file
View File

@@ -0,0 +1,247 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from app.database import get_db, ProxmoxHost
from app.models.schemas import ProxmoxHost as ProxmoxHostSchema, ProxmoxHostCreate, ProxmoxVM
from app.services.proxmox_host_service import ProxmoxHostService
from app.services.logging_service import LoggingService
router = APIRouter()
@router.get("/", response_model=List[ProxmoxHostSchema])
async def get_hosts(db: Session = Depends(get_db)):
"""Get all Proxmox hosts"""
hosts = db.query(ProxmoxHost).all()
return hosts
@router.post("/", response_model=ProxmoxHostSchema)
async def create_host(host: ProxmoxHostCreate, db: Session = Depends(get_db)):
"""Create a new Proxmox host"""
try:
# Test Proxmox connection before creating
temp_host = ProxmoxHost(**host.dict())
host_service = ProxmoxHostService(temp_host)
if not await host_service.test_proxmox_connection():
raise HTTPException(status_code=400, detail="Cannot connect to Proxmox host")
# Create the host in database
db_host = ProxmoxHost(**host.dict())
db.add(db_host)
db.commit()
db.refresh(db_host)
# Log host creation
LoggingService.log_host_action(
db=db,
action="create",
host_id=db_host.id,
host_name=db_host.name,
success=True,
message=f"Proxmox host '{db_host.name}' created successfully"
)
return db_host
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error creating host: {str(e)}")
@router.get("/{host_id}", response_model=ProxmoxHostSchema)
async def get_host(host_id: int, db: Session = Depends(get_db)):
"""Get a specific Proxmox host"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
return host
@router.put("/{host_id}", response_model=ProxmoxHostSchema)
async def update_host(host_id: int, host_update: ProxmoxHostCreate, db: Session = Depends(get_db)):
"""Update a Proxmox host"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
# Test new connection if Proxmox settings changed
temp_host = ProxmoxHost(**host_update.dict(), id=host_id)
host_service = ProxmoxHostService(temp_host)
if not await host_service.test_proxmox_connection():
raise HTTPException(status_code=400, detail="Cannot connect to Proxmox with new settings")
old_name = host.name
for key, value in host_update.dict().items():
setattr(host, key, value)
db.commit()
db.refresh(host)
# Log host update
LoggingService.log_host_action(
db=db,
action="update",
host_id=host.id,
host_name=host.name,
success=True,
message=f"Proxmox host '{old_name}' updated successfully"
)
return host
@router.delete("/{host_id}")
async def delete_host(host_id: int, db: Session = Depends(get_db)):
"""Delete a Proxmox host"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
# Log host deletion before deleting
LoggingService.log_host_action(
db=db,
action="delete",
host_id=host.id,
host_name=host.name,
success=True,
message=f"Proxmox host '{host.name}' deleted successfully"
)
db.delete(host)
db.commit()
return {"message": "Host deleted successfully"}
@router.post("/{host_id}/wake")
async def wake_host(host_id: int, db: Session = Depends(get_db)):
"""Wake up a Proxmox host using Wake-on-LAN"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
host_service = ProxmoxHostService(host)
success = await host_service.wake_host(db)
if not success:
raise HTTPException(status_code=500, detail="Failed to wake host")
return {"message": f"WOL packet sent to {host.name}", "success": True}
@router.post("/{host_id}/shutdown")
async def shutdown_host(host_id: int, db: Session = Depends(get_db)):
"""Shutdown a Proxmox host (and all its VMs)"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
host_service = ProxmoxHostService(host)
success = await host_service.shutdown_host(db)
if not success:
raise HTTPException(status_code=500, detail="Failed to shutdown host")
return {"message": f"Shutdown initiated for {host.name}", "success": True}
@router.get("/{host_id}/vms", response_model=List[ProxmoxVM])
async def get_host_vms(host_id: int, db: Session = Depends(get_db)):
"""Get all VMs/containers from a Proxmox host"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
try:
host_service = ProxmoxHostService(host)
vms = await host_service.get_vms()
return vms
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error getting VMs: {str(e)}")
@router.post("/{host_id}/vms/{vmid}/start")
async def start_vm(host_id: int, vmid: str, node: str, vm_type: str = "qemu", db: Session = Depends(get_db)):
"""Start a VM/container on a Proxmox host"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
host_service = ProxmoxHostService(host)
success = await host_service.start_vm(node, vmid, vm_type)
# Get VM name for logging
try:
vms = await host_service.get_vms()
vm_name = next((vm.name for vm in vms if vm.vmid == vmid), f"VM-{vmid}")
except:
vm_name = f"VM-{vmid}"
# Log VM start action
LoggingService.log_proxmox_vm_action(
db=db,
action="start",
vmid=vmid,
vm_name=vm_name,
node=node,
success=success,
message=f"VM {vm_name} ({'started' if success else 'failed to start'}) on node {node}"
)
if not success:
raise HTTPException(status_code=500, detail="Failed to start VM")
return {"message": f"VM {vmid} start command sent", "success": True}
@router.post("/{host_id}/vms/{vmid}/stop")
async def stop_vm(host_id: int, vmid: str, node: str, vm_type: str = "qemu", db: Session = Depends(get_db)):
"""Stop a VM/container on a Proxmox host"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
host_service = ProxmoxHostService(host)
success = await host_service.stop_vm(node, vmid, vm_type)
# Get VM name for logging
try:
vms = await host_service.get_vms()
vm_name = next((vm.name for vm in vms if vm.vmid == vmid), f"VM-{vmid}")
except:
vm_name = f"VM-{vmid}"
# Log VM stop action
LoggingService.log_proxmox_vm_action(
db=db,
action="stop",
vmid=vmid,
vm_name=vm_name,
node=node,
success=success,
message=f"VM {vm_name} ({'stopped' if success else 'failed to stop'}) on node {node}"
)
if not success:
raise HTTPException(status_code=500, detail="Failed to stop VM")
return {"message": f"VM {vmid} stop command sent", "success": True}
@router.post("/check-status")
async def check_all_hosts_status(db: Session = Depends(get_db)):
"""Check online status of all Proxmox hosts"""
await ProxmoxHostService.check_all_hosts_status(db)
return {"message": "Host status checked for all hosts"}
@router.get("/{host_id}/status")
async def check_host_status(host_id: int, db: Session = Depends(get_db)):
"""Check online status of a specific Proxmox host"""
host = db.query(ProxmoxHost).filter(ProxmoxHost.id == host_id).first()
if not host:
raise HTTPException(status_code=404, detail="Host not found")
is_online = await ProxmoxHostService.ping_host(host.ip_address)
from datetime import datetime
host.is_online = is_online
host.last_ping = datetime.utcnow()
db.commit()
return {
"host_id": host_id,
"host_name": host.name,
"is_online": is_online,
"last_ping": host.last_ping
}

View File

@@ -1,166 +0,0 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from app.database import get_db, ProxmoxCluster
from app.models.schemas import ProxmoxCluster as ProxmoxClusterSchema, ProxmoxClusterCreate, ProxmoxVM
from app.services.proxmox_service import ProxmoxService
from app.services.logging_service import LoggingService
router = APIRouter()
@router.get("/clusters", response_model=List[ProxmoxClusterSchema])
async def get_clusters(db: Session = Depends(get_db)):
clusters = db.query(ProxmoxCluster).all()
return clusters
@router.post("/clusters", response_model=ProxmoxClusterSchema)
async def create_cluster(cluster: ProxmoxClusterCreate, db: Session = Depends(get_db)):
try:
proxmox_service = ProxmoxService(
host=cluster.host,
user=cluster.username,
password=cluster.password,
port=cluster.port,
verify_ssl=cluster.verify_ssl
)
if not await proxmox_service.test_connection():
raise HTTPException(status_code=400, detail="Cannot connect to Proxmox cluster")
db_cluster = ProxmoxCluster(**cluster.dict())
db.add(db_cluster)
db.commit()
db.refresh(db_cluster)
# Log cluster creation
LoggingService.log_proxmox_cluster_action(
db=db,
action="create",
cluster_id=db_cluster.id,
cluster_name=db_cluster.name,
success=True,
message=f"Proxmox cluster '{db_cluster.name}' created successfully"
)
return db_cluster
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error creating cluster: {str(e)}")
@router.get("/clusters/{cluster_id}", response_model=ProxmoxClusterSchema)
async def get_cluster(cluster_id: int, db: Session = Depends(get_db)):
cluster = db.query(ProxmoxCluster).filter(ProxmoxCluster.id == cluster_id).first()
if not cluster:
raise HTTPException(status_code=404, detail="Cluster not found")
return cluster
@router.delete("/clusters/{cluster_id}")
async def delete_cluster(cluster_id: int, db: Session = Depends(get_db)):
cluster = db.query(ProxmoxCluster).filter(ProxmoxCluster.id == cluster_id).first()
if not cluster:
raise HTTPException(status_code=404, detail="Cluster not found")
# Log cluster deletion before deleting
LoggingService.log_proxmox_cluster_action(
db=db,
action="delete",
cluster_id=cluster.id,
cluster_name=cluster.name,
success=True,
message=f"Proxmox cluster '{cluster.name}' deleted successfully"
)
db.delete(cluster)
db.commit()
return {"message": "Cluster deleted successfully"}
@router.get("/clusters/{cluster_id}/vms", response_model=List[ProxmoxVM])
async def get_cluster_vms(cluster_id: int, db: Session = Depends(get_db)):
cluster = db.query(ProxmoxCluster).filter(ProxmoxCluster.id == cluster_id).first()
if not cluster:
raise HTTPException(status_code=404, detail="Cluster not found")
try:
proxmox_service = ProxmoxService(
host=cluster.host,
user=cluster.username,
password=cluster.password,
port=cluster.port,
verify_ssl=cluster.verify_ssl
)
vms = await proxmox_service.get_vms()
return vms
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error getting VMs: {str(e)}")
@router.post("/clusters/{cluster_id}/vms/{vmid}/start")
async def start_vm(cluster_id: int, vmid: str, node: str, vm_type: str = "qemu", db: Session = Depends(get_db)):
cluster = db.query(ProxmoxCluster).filter(ProxmoxCluster.id == cluster_id).first()
if not cluster:
raise HTTPException(status_code=404, detail="Cluster not found")
proxmox_service = ProxmoxService(
host=cluster.host,
user=cluster.username,
password=cluster.password,
port=cluster.port,
verify_ssl=cluster.verify_ssl
)
success = await proxmox_service.start_vm(node, vmid, vm_type)
# Get VM name from the cluster's VMs
vms = await proxmox_service.get_vms()
vm_name = next((vm.name for vm in vms if vm.vmid == vmid), f"VM-{vmid}")
# Log VM start action
LoggingService.log_proxmox_vm_action(
db=db,
action="start",
vmid=vmid,
vm_name=vm_name,
node=node,
success=success,
message=f"VM {vm_name} ({'started' if success else 'failed to start'}) on node {node}"
)
if not success:
raise HTTPException(status_code=500, detail="Failed to start VM")
return {"message": f"VM {vmid} start command sent", "success": True}
@router.post("/clusters/{cluster_id}/vms/{vmid}/stop")
async def stop_vm(cluster_id: int, vmid: str, node: str, vm_type: str = "qemu", db: Session = Depends(get_db)):
cluster = db.query(ProxmoxCluster).filter(ProxmoxCluster.id == cluster_id).first()
if not cluster:
raise HTTPException(status_code=404, detail="Cluster not found")
proxmox_service = ProxmoxService(
host=cluster.host,
user=cluster.username,
password=cluster.password,
port=cluster.port,
verify_ssl=cluster.verify_ssl
)
success = await proxmox_service.stop_vm(node, vmid, vm_type)
# Get VM name from the cluster's VMs
vms = await proxmox_service.get_vms()
vm_name = next((vm.name for vm in vms if vm.vmid == vmid), f"VM-{vmid}")
# Log VM stop action
LoggingService.log_proxmox_vm_action(
db=db,
action="stop",
vmid=vmid,
vm_name=vm_name,
node=node,
success=success,
message=f"VM {vm_name} ({'stopped' if success else 'failed to stop'}) on node {node}"
)
if not success:
raise HTTPException(status_code=500, detail="Failed to stop VM")
return {"message": f"VM {vmid} stop command sent", "success": True}

View File

@@ -1,90 +0,0 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
from app.database import get_db, Server
from app.models.schemas import Server as ServerSchema, ServerCreate
from app.services.wol_service import WolService
from app.services.logging_service import LoggingService
router = APIRouter()
@router.get("/", response_model=List[ServerSchema])
async def get_servers(db: Session = Depends(get_db)):
servers = db.query(Server).all()
return servers
@router.post("/", response_model=ServerSchema)
async def create_server(server: ServerCreate, db: Session = Depends(get_db)):
db_server = Server(**server.dict())
db.add(db_server)
db.commit()
db.refresh(db_server)
# Log server creation
LoggingService.log_server_action(
db=db,
action="create",
server_id=db_server.id,
server_name=db_server.name,
success=True,
message=f"Server '{db_server.name}' created successfully"
)
return db_server
@router.get("/{server_id}", response_model=ServerSchema)
async def get_server(server_id: int, db: Session = Depends(get_db)):
server = db.query(Server).filter(Server.id == server_id).first()
if not server:
raise HTTPException(status_code=404, detail="Server not found")
return server
@router.put("/{server_id}", response_model=ServerSchema)
async def update_server(server_id: int, server_update: ServerCreate, db: Session = Depends(get_db)):
server = db.query(Server).filter(Server.id == server_id).first()
if not server:
raise HTTPException(status_code=404, detail="Server not found")
old_name = server.name
for key, value in server_update.dict().items():
setattr(server, key, value)
db.commit()
db.refresh(server)
# Log server update
LoggingService.log_server_action(
db=db,
action="update",
server_id=server.id,
server_name=server.name,
success=True,
message=f"Server '{old_name}' updated successfully"
)
return server
@router.delete("/{server_id}")
async def delete_server(server_id: int, db: Session = Depends(get_db)):
server = db.query(Server).filter(Server.id == server_id).first()
if not server:
raise HTTPException(status_code=404, detail="Server not found")
# Log server deletion before deleting
LoggingService.log_server_action(
db=db,
action="delete",
server_id=server.id,
server_name=server.name,
success=True,
message=f"Server '{server.name}' deleted successfully"
)
db.delete(server)
db.commit()
return {"message": "Server deleted successfully"}
@router.post("/check-status")
async def check_all_servers_status(db: Session = Depends(get_db)):
await WolService.check_all_servers_status(db)
return {"message": "Server status checked for all servers"}

View File

@@ -34,6 +34,28 @@ class ProxmoxCluster(Base):
verify_ssl = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
class ProxmoxHost(Base):
__tablename__ = "proxmox_hosts"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True, nullable=False)
description = Column(String)
# Network configuration for WOL
ip_address = Column(String, nullable=False)
mac_address = Column(String, nullable=False)
# Proxmox configuration
proxmox_host = Column(String, nullable=False) # Can be same as ip_address
proxmox_username = Column(String, nullable=False)
proxmox_password = Column(String, nullable=False)
proxmox_port = Column(Integer, default=8006)
verify_ssl = Column(Boolean, default=True)
# Host status
is_online = Column(Boolean, default=False)
last_ping = Column(DateTime)
# Optional shutdown endpoint for host shutdown
shutdown_endpoint = Column(String, nullable=True) # e.g., /api/hosts/shutdown
created_at = Column(DateTime, default=datetime.utcnow)
class ActionLog(Base):
__tablename__ = "action_logs"

View File

@@ -2,7 +2,7 @@ from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from app.database import init_db
from app.api import servers, proxmox, wol
from app.api import wol, hosts
import traceback
app = FastAPI(title="Zebra Power", description="Wake-on-LAN and Proxmox Management API")
@@ -27,9 +27,12 @@ async def global_exception_handler(request: Request, exc: Exception):
}
)
app.include_router(servers.router, prefix="/api/servers", tags=["servers"])
app.include_router(proxmox.router, prefix="/api/proxmox", tags=["proxmox"])
app.include_router(wol.router, prefix="/api/wol", tags=["wol"])
# Anciens endpoints conservés temporairement pour transition
# app.include_router(servers.router, prefix="/api/servers", tags=["servers"]) - SUPPRIMÉ
# app.include_router(proxmox.router, prefix="/api/proxmox", tags=["proxmox"]) - SUPPRIMÉ
app.include_router(wol.router, prefix="/api/wol", tags=["wol"]) # Conservé pour les logs
app.include_router(hosts.router, prefix="/api/hosts", tags=["hosts"]) # Nouvelle API unifiée
@app.on_event("startup")
async def startup_event():

View File

@@ -71,5 +71,29 @@ class ActionLog(ActionLogCreate):
id: int
timestamp: datetime
class Config:
from_attributes = True
class ProxmoxHostBase(BaseModel):
name: str
description: Optional[str] = None
ip_address: str
mac_address: str
proxmox_host: str
proxmox_username: str
proxmox_password: str
proxmox_port: int = 8006
verify_ssl: bool = True
shutdown_endpoint: Optional[str] = None
class ProxmoxHostCreate(ProxmoxHostBase):
pass
class ProxmoxHost(ProxmoxHostBase):
id: int
is_online: bool
last_ping: Optional[datetime] = None
created_at: datetime
class Config:
from_attributes = True

View File

@@ -79,4 +79,17 @@ class LoggingService:
message=message,
target_id=server_id,
target_name=server_name
)
@staticmethod
def log_host_action(db: Session, action: str, host_id: int, host_name: str, success: bool, message: str):
"""Log Proxmox host actions (create/update/delete/wake/shutdown)"""
LoggingService.log_action(
db=db,
action_type="host",
action=action,
success=success,
message=message,
target_id=host_id,
target_name=host_name
)

View File

@@ -0,0 +1,253 @@
import asyncio
import subprocess
import requests
from typing import List, Optional
from sqlalchemy.orm import Session
from datetime import datetime
from proxmoxer import ProxmoxAPI
from app.database import ProxmoxHost
from app.models.schemas import ProxmoxVM
from app.services.logging_service import LoggingService
import logging
logger = logging.getLogger(__name__)
class ProxmoxHostService:
def __init__(self, host_config: ProxmoxHost):
self.host_config = host_config
self._proxmox = None
def _get_proxmox_connection(self):
if not self._proxmox:
try:
self._proxmox = ProxmoxAPI(
self.host_config.proxmox_host,
user=self.host_config.proxmox_username,
password=self.host_config.proxmox_password,
port=self.host_config.proxmox_port,
verify_ssl=self.host_config.verify_ssl,
timeout=10
)
except Exception as e:
logger.error(f"Failed to connect to Proxmox {self.host_config.proxmox_host}: {str(e)}")
raise ConnectionError(f"Cannot connect to Proxmox: {str(e)}")
return self._proxmox
@staticmethod
async def send_wol_packet(mac_address: str) -> bool:
try:
result = subprocess.run(
["wakeonlan", mac_address],
capture_output=True,
text=True,
timeout=10
)
return result.returncode == 0
except subprocess.TimeoutExpired:
logger.error(f"WOL timeout for MAC: {mac_address}")
return False
except Exception as e:
logger.error(f"WOL error for MAC {mac_address}: {str(e)}")
return False
@staticmethod
async def ping_host(ip_address: str) -> bool:
try:
result = subprocess.run(
["ping", "-c", "1", "-W", "3", ip_address],
capture_output=True,
text=True,
timeout=5
)
return result.returncode == 0
except subprocess.TimeoutExpired:
return False
except Exception as e:
logger.error(f"Ping error for IP {ip_address}: {str(e)}")
return False
async def wake_host(self, db: Session) -> bool:
success = await self.send_wol_packet(self.host_config.mac_address)
# Log WOL action
LoggingService.log_host_action(
db=db,
action="wake",
host_id=self.host_config.id,
host_name=self.host_config.name,
success=success,
message=f"WOL packet {'sent successfully' if success else 'failed'} to {self.host_config.name} ({self.host_config.mac_address})"
)
return success
async def shutdown_host(self, db: Session) -> bool:
try:
# Try using shutdown endpoint if configured
if self.host_config.shutdown_endpoint:
try:
response = requests.post(
f"http://{self.host_config.ip_address}{self.host_config.shutdown_endpoint}",
timeout=10
)
success = response.status_code == 200
except Exception as e:
logger.error(f"Shutdown endpoint failed: {str(e)}")
success = False
else:
# Try Proxmox API shutdown (shutdown all VMs then the node)
proxmox = self._get_proxmox_connection()
nodes = proxmox.nodes.get()
success = True
for node_data in nodes:
node_name = node_data['node']
try:
# Stop all VMs/containers first
vms = await self.get_vms_for_node(node_name)
for vm in vms:
if vm.status == 'running':
await self.stop_vm(node_name, vm.vmid, vm.type)
# Then shutdown the node
proxmox.nodes(node_name).status.post(command='shutdown')
except Exception as e:
logger.error(f"Failed to shutdown node {node_name}: {str(e)}")
success = False
# Log shutdown action
LoggingService.log_host_action(
db=db,
action="shutdown",
host_id=self.host_config.id,
host_name=self.host_config.name,
success=success,
message=f"Host {self.host_config.name} {'shutdown initiated' if success else 'shutdown failed'}"
)
return success
except Exception as e:
logger.error(f"Host shutdown failed: {str(e)}")
LoggingService.log_host_action(
db=db,
action="shutdown",
host_id=self.host_config.id,
host_name=self.host_config.name,
success=False,
message=f"Host shutdown failed: {str(e)}"
)
return False
async def test_proxmox_connection(self) -> bool:
try:
proxmox = self._get_proxmox_connection()
proxmox.version.get()
return True
except Exception as e:
logger.error(f"Proxmox connection test failed: {str(e)}")
return False
async def get_nodes(self) -> List[dict]:
try:
proxmox = self._get_proxmox_connection()
return proxmox.nodes.get()
except Exception as e:
logger.error(f"Failed to get nodes: {str(e)}")
return []
async def get_vms(self) -> List[ProxmoxVM]:
try:
proxmox = self._get_proxmox_connection()
vms = []
nodes_list = await self.get_nodes()
nodes = [n['node'] for n in nodes_list]
for node_name in nodes:
vms.extend(await self.get_vms_for_node(node_name))
return vms
except Exception as e:
logger.error(f"Failed to get VMs: {str(e)}")
return []
async def get_vms_for_node(self, node_name: str) -> List[ProxmoxVM]:
try:
proxmox = self._get_proxmox_connection()
vms = []
# Get QEMU VMs
try:
qemu_vms = proxmox.nodes(node_name).qemu.get()
for vm in qemu_vms:
if vm.get('template', 0) != 1:
vms.append(ProxmoxVM(
vmid=str(vm['vmid']),
name=vm.get('name', f"VM-{vm['vmid']}"),
status=vm.get('status', 'unknown'),
node=node_name,
type='qemu'
))
except Exception as e:
logger.error(f"Failed to get QEMU VMs from node {node_name}: {str(e)}")
# Get LXC Containers
try:
lxc_containers = proxmox.nodes(node_name).lxc.get()
for container in lxc_containers:
vms.append(ProxmoxVM(
vmid=str(container['vmid']),
name=container.get('name', f"CT-{container['vmid']}"),
status=container.get('status', 'unknown'),
node=node_name,
type='lxc'
))
except Exception as e:
logger.error(f"Failed to get LXC containers from node {node_name}: {str(e)}")
return vms
except Exception as e:
logger.error(f"Failed to get VMs for node {node_name}: {str(e)}")
return []
async def start_vm(self, node: str, vmid: str, vm_type: str = 'qemu') -> bool:
try:
proxmox = self._get_proxmox_connection()
if vm_type == 'lxc':
proxmox.nodes(node).lxc(vmid).status.start.post()
else:
proxmox.nodes(node).qemu(vmid).status.start.post()
return True
except Exception as e:
logger.error(f"Failed to start VM {vmid}: {str(e)}")
return False
async def stop_vm(self, node: str, vmid: str, vm_type: str = 'qemu') -> bool:
try:
proxmox = self._get_proxmox_connection()
if vm_type == 'lxc':
proxmox.nodes(node).lxc(vmid).status.shutdown.post()
else:
proxmox.nodes(node).qemu(vmid).status.shutdown.post()
return True
except Exception as e:
logger.error(f"Failed to stop VM {vmid}: {str(e)}")
return False
@staticmethod
async def check_all_hosts_status(db: Session) -> None:
hosts = db.query(ProxmoxHost).all()
tasks = []
for host in hosts:
tasks.append(ProxmoxHostService.ping_host(host.ip_address))
results = await asyncio.gather(*tasks)
for host, is_online in zip(hosts, results):
host.is_online = is_online
host.last_ping = datetime.utcnow()
db.commit()

View File

@@ -1,114 +0,0 @@
from proxmoxer import ProxmoxAPI
from typing import List, Optional
from app.models.schemas import ProxmoxVM
import logging
logger = logging.getLogger(__name__)
class ProxmoxService:
def __init__(self, host: str, user: str, password: str, port: int = 8006, verify_ssl: bool = True):
self.host = host
self.user = user
self.password = password
self.port = port
self.verify_ssl = verify_ssl
self._proxmox = None
def _get_connection(self):
if not self._proxmox:
try:
self._proxmox = ProxmoxAPI(
self.host,
user=self.user,
password=self.password,
port=self.port,
verify_ssl=self.verify_ssl,
timeout=10
)
except Exception as e:
logger.error(f"Failed to connect to Proxmox {self.host}: {str(e)}")
raise ConnectionError(f"Cannot connect to Proxmox: {str(e)}")
return self._proxmox
async def test_connection(self) -> bool:
try:
proxmox = self._get_connection()
proxmox.version.get()
return True
except Exception as e:
logger.error(f"Proxmox connection test failed: {str(e)}")
return False
async def get_nodes(self) -> List[dict]:
try:
proxmox = self._get_connection()
return proxmox.nodes.get()
except Exception as e:
logger.error(f"Failed to get nodes: {str(e)}")
return []
async def get_vms(self, node: Optional[str] = None) -> List[ProxmoxVM]:
try:
proxmox = self._get_connection()
vms = []
if node:
nodes = [node]
else:
nodes_list = await self.get_nodes()
nodes = [n['node'] for n in nodes_list]
for node_name in nodes:
try:
qemu_vms = proxmox.nodes(node_name).qemu.get()
for vm in qemu_vms:
if vm.get('template', 0) != 1:
vms.append(ProxmoxVM(
vmid=str(vm['vmid']),
name=vm.get('name', f"VM-{vm['vmid']}"),
status=vm.get('status', 'unknown'),
node=node_name,
type='qemu'
))
lxc_containers = proxmox.nodes(node_name).lxc.get()
for container in lxc_containers:
vms.append(ProxmoxVM(
vmid=str(container['vmid']),
name=container.get('name', f"CT-{container['vmid']}"),
status=container.get('status', 'unknown'),
node=node_name,
type='lxc'
))
except Exception as e:
logger.error(f"Failed to get VMs from node {node_name}: {str(e)}")
continue
return vms
except Exception as e:
logger.error(f"Failed to get VMs: {str(e)}")
return []
async def start_vm(self, node: str, vmid: str, vm_type: str = 'qemu') -> bool:
try:
proxmox = self._get_connection()
if vm_type == 'lxc':
proxmox.nodes(node).lxc(vmid).status.start.post()
else:
proxmox.nodes(node).qemu(vmid).status.start.post()
return True
except Exception as e:
logger.error(f"Failed to start VM {vmid}: {str(e)}")
return False
async def stop_vm(self, node: str, vmid: str, vm_type: str = 'qemu') -> bool:
try:
proxmox = self._get_connection()
if vm_type == 'lxc':
proxmox.nodes(node).lxc(vmid).status.shutdown.post()
else:
proxmox.nodes(node).qemu(vmid).status.shutdown.post()
return True
except Exception as e:
logger.error(f"Failed to stop VM {vmid}: {str(e)}")
return False