Files
kvm-manager/backend/app/routers/snapshot.py
T
admin 8ccccf8f52 feat: 多主机纳管、用户认证、noVNC控制台、深色主题
主要功能:
- 多主机管理: 支持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版本兼容性修复
2026-05-07 12:41:10 +08:00

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()}