"""快照管理路由"""
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()}