115 satır
4.0 KiB
Python
115 satır
4.0 KiB
Python
"""快照管理路由"""
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
from typing import Optional
|
|
from lxml import etree
|
|
|
|
from app.libvirt_conn import libvirt_conn
|
|
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):
|
|
"""列出虚拟机的所有快照"""
|
|
conn = libvirt_conn.conn
|
|
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):
|
|
"""创建快照"""
|
|
with libvirt_conn.get_rw() 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):
|
|
"""恢复快照"""
|
|
with libvirt_conn.get_rw() 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):
|
|
"""删除快照"""
|
|
with libvirt_conn.get_rw() 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):
|
|
"""获取快照详情"""
|
|
conn = libvirt_conn.conn
|
|
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()}
|