8ccccf8f52
主要功能: - 多主机管理: 支持TCP/SSH方式纳管远程KVM主机 - 用户认证: JWT token认证, 默认admin/admin123 - noVNC控制台: 前端集成noVNC, WebSocket代理VNC连接 - 深色主题: 全局Element Plus深色主题覆盖 - 虚拟机操作: 克隆、迁移、XML编辑、快照管理 - 资源监控: CPU/内存/磁盘IO/网络流量实时监控 Bug修复: - libvirt getInfo()内存单位修正(MiB非KiB) - 远程主机VNC 0.0.0.0监听地址连接策略修复 - Dashboard定时器内存泄漏修复 - bcrypt版本兼容性修复
115 lines
4.1 KiB
Python
115 lines
4.1 KiB
Python
"""快照管理路由"""
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from pydantic import BaseModel, Field
|
|
from typing import Optional
|
|
from lxml import etree
|
|
|
|
from app.libvirt_conn import conn_pool
|
|
import libvirt
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class SnapshotCreate(BaseModel):
|
|
name: str = Field(..., description="快照名称")
|
|
description: Optional[str] = Field(None, description="快照描述")
|
|
|
|
|
|
@router.get("/list/{vm_name}")
|
|
async def list_snapshots(vm_name: str, host_id: str = Query("local")):
|
|
"""列出虚拟机的所有快照"""
|
|
conn = conn_pool.get_conn(host_id)
|
|
try:
|
|
dom = conn.lookupByName(vm_name)
|
|
except libvirt.libvirtError:
|
|
raise HTTPException(status_code=404, detail=f"虚拟机 '{vm_name}' 不存在")
|
|
|
|
snapshots = []
|
|
try:
|
|
for snap in dom.listAllSnapshots(0):
|
|
xml = etree.fromstring(snap.getXMLDesc().encode())
|
|
desc = xml.find("description")
|
|
state = xml.find("state")
|
|
creation = xml.find("creationTime")
|
|
|
|
snapshots.append({
|
|
"name": snap.getName(),
|
|
"state": state.text if state is not None else "",
|
|
"description": desc.text if desc is not None else "",
|
|
"creation_time": int(creation.text) if creation is not None else 0,
|
|
"is_current": snap.isCurrent() == 1,
|
|
})
|
|
except libvirt.libvirtError:
|
|
pass
|
|
|
|
return {"vm": vm_name, "snapshots": snapshots, "total": len(snapshots)}
|
|
|
|
|
|
@router.post("/create/{vm_name}")
|
|
async def create_snapshot(vm_name: str, snap: SnapshotCreate, host_id: str = Query("local")):
|
|
"""创建快照"""
|
|
with conn_pool.get_rw(host_id) as rw_conn:
|
|
try:
|
|
dom = rw_conn.lookupByName(vm_name)
|
|
except libvirt.libvirtError:
|
|
raise HTTPException(status_code=404, detail=f"虚拟机 '{vm_name}' 不存在")
|
|
|
|
desc_xml = f"<description>{snap.description}</description>" if snap.description else ""
|
|
xml = f"""<domainsnapshot>
|
|
<name>{snap.name}</name>
|
|
{desc_xml}
|
|
</domainsnapshot>"""
|
|
|
|
try:
|
|
dom.snapshotCreateXML(xml, 0)
|
|
return {"message": f"快照 '{snap.name}' 创建成功"}
|
|
except libvirt.libvirtError as e:
|
|
raise HTTPException(status_code=500, detail=f"创建快照失败: {str(e)}")
|
|
|
|
|
|
@router.post("/revert/{vm_name}/{snap_name}")
|
|
async def revert_snapshot(vm_name: str, snap_name: str, host_id: str = Query("local")):
|
|
"""恢复快照"""
|
|
with conn_pool.get_rw(host_id) as rw_conn:
|
|
try:
|
|
dom = rw_conn.lookupByName(vm_name)
|
|
snap = dom.snapshotLookupByName(snap_name)
|
|
except libvirt.libvirtError:
|
|
raise HTTPException(status_code=404, detail="虚拟机或快照不存在")
|
|
|
|
try:
|
|
dom.revertToSnapshot(snap)
|
|
return {"message": f"已恢复到快照 '{snap_name}'"}
|
|
except libvirt.libvirtError as e:
|
|
raise HTTPException(status_code=500, detail=f"恢复快照失败: {str(e)}")
|
|
|
|
|
|
@router.delete("/delete/{vm_name}/{snap_name}")
|
|
async def delete_snapshot(vm_name: str, snap_name: str, host_id: str = Query("local")):
|
|
"""删除快照"""
|
|
with conn_pool.get_rw(host_id) as rw_conn:
|
|
try:
|
|
dom = rw_conn.lookupByName(vm_name)
|
|
snap = dom.snapshotLookupByName(snap_name)
|
|
except libvirt.libvirtError:
|
|
raise HTTPException(status_code=404, detail="虚拟机或快照不存在")
|
|
|
|
try:
|
|
snap.delete(0)
|
|
return {"message": f"快照 '{snap_name}' 已删除"}
|
|
except libvirt.libvirtError as e:
|
|
raise HTTPException(status_code=500, detail=f"删除快照失败: {str(e)}")
|
|
|
|
|
|
@router.get("/detail/{vm_name}/{snap_name}")
|
|
async def get_snapshot_detail(vm_name: str, snap_name: str, host_id: str = Query("local")):
|
|
"""获取快照详情"""
|
|
conn = conn_pool.get_conn(host_id)
|
|
try:
|
|
dom = conn.lookupByName(vm_name)
|
|
snap = dom.snapshotLookupByName(snap_name)
|
|
except libvirt.libvirtError:
|
|
raise HTTPException(status_code=404, detail="虚拟机或快照不存在")
|
|
|
|
return {"name": snap_name, "vm": vm_name, "xml": snap.getXMLDesc()}
|