"""快照管理路由""" 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"{snap.description}" if snap.description else "" xml = f""" {snap.name} {desc_xml} """ 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()}