"""存储池管理路由""" from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel, Field from typing import Optional from lxml import etree import os from app.libvirt_conn import conn_pool import libvirt router = APIRouter() class PoolCreate(BaseModel): name: str = Field(..., description="存储池名称") path: str = Field(..., description="存储池路径") type: str = Field("dir", description="存储池类型: dir/fs/logical") class VolCreate(BaseModel): name: str = Field(..., description="卷名称") capacity_gb: int = Field(20, description="容量(GB)") format: str = Field("qcow2", description="格式: qcow2/raw") @router.get("/pools") async def list_pools(host_id: str = Query("local")): """列出所有存储池""" conn = conn_pool.get_conn(host_id) pools = conn.listAllStoragePools(0) result = [] for pool in pools: info = pool.info() xml = etree.fromstring(pool.XMLDesc(0).encode()) target = xml.find(".//target/path") result.append({ "name": pool.name(), "state": ["inactive", "building", "running", "degraded", "inaccessible"][info[0]] if info[0] < 5 else "unknown", "capacity_gb": round(info[1] / (1024**3), 2), "allocation_gb": round(info[2] / (1024**3), 2), "available_gb": round(info[3] / (1024**3), 2), "path": target.text if target is not None else "", "autostart": pool.autostart() == 1, "persistent": pool.isPersistent() == 1, }) return {"pools": result, "total": len(result)} @router.get("/pool/{name}") async def get_pool(name: str, host_id: str = Query("local")): """获取存储池详情""" conn = conn_pool.get_conn(host_id) try: pool = conn.storagePoolLookupByName(name) except libvirt.libvirtError: raise HTTPException(status_code=404, detail=f"存储池 '{name}' 不存在") info = pool.info() xml = etree.fromstring(pool.XMLDesc(0).encode()) volumes = [] try: for vol_name in pool.listVolumes(): vol = pool.storageVolLookupByName(vol_name) vol_info = vol.info() volumes.append({ "name": vol_name, "path": vol.path(), "type": vol_info[0], "capacity_gb": round(vol_info[1] / (1024**3), 2), "allocation_gb": round(vol_info[2] / (1024**3), 2), }) except Exception: pass target = xml.find(".//target/path") return { "name": pool.name(), "state": ["inactive", "building", "running", "degraded", "inaccessible"][info[0]] if info[0] < 5 else "unknown", "capacity_gb": round(info[1] / (1024**3), 2), "allocation_gb": round(info[2] / (1024**3), 2), "available_gb": round(info[3] / (1024**3), 2), "path": target.text if target is not None else "", "autostart": pool.autostart() == 1, "volumes": volumes, "volume_count": len(volumes), } @router.post("/pool/create") async def create_pool(pool: PoolCreate, host_id: str = Query("local")): """创建存储池""" with conn_pool.get_rw(host_id) as rw_conn: xml = f""" {pool.name} {pool.path} """ try: os.makedirs(pool.path, exist_ok=True) p = rw_conn.storagePoolDefineXML(xml, 0) p.setAutostart(1) p.create(0) return {"message": f"存储池 '{pool.name}' 创建成功"} except libvirt.libvirtError as e: raise HTTPException(status_code=500, detail=f"创建存储池失败: {str(e)}") @router.delete("/pool/{name}") async def delete_pool(name: str, host_id: str = Query("local")): """删除存储池""" with conn_pool.get_rw(host_id) as rw_conn: try: pool = rw_conn.storagePoolLookupByName(name) except libvirt.libvirtError: raise HTTPException(status_code=404, detail=f"存储池 '{name}' 不存在") if pool.info()[0] == libvirt.VIR_STORAGE_POOL_RUNNING: pool.destroy() pool.undefine() return {"message": f"存储池 '{name}' 已删除"} @router.post("/pool/{name}/volume") async def create_volume(name: str, vol: VolCreate, host_id: str = Query("local")): """在存储池中创建卷""" with conn_pool.get_rw(host_id) as rw_conn: try: pool = rw_conn.storagePoolLookupByName(name) except libvirt.libvirtError: raise HTTPException(status_code=404, detail=f"存储池 '{name}' 不存在") vol_xml = f""" {vol.name} {vol.capacity_gb} 1 """ try: pool.createXML(vol_xml, 0) return {"message": f"卷 '{vol.name}' 创建成功"} except libvirt.libvirtError as e: raise HTTPException(status_code=500, detail=f"创建卷失败: {str(e)}") @router.delete("/pool/{pool_name}/volume/{vol_name}") async def delete_volume(pool_name: str, vol_name: str, host_id: str = Query("local")): """删除卷""" with conn_pool.get_rw(host_id) as rw_conn: try: pool = rw_conn.storagePoolLookupByName(pool_name) vol = pool.storageVolLookupByName(vol_name) vol.delete(0) return {"message": f"卷 '{vol_name}' 已删除"} except libvirt.libvirtError as e: raise HTTPException(status_code=500, detail=f"删除卷失败: {str(e)}") @router.get("/isos") async def list_isos(host_id: str = Query("local")): """列出可用的ISO镜像""" iso_dirs = ["/var/lib/libvirt/iso", "/isos", "/mnt/isos"] isos = [] for d in iso_dirs: if os.path.isdir(d): for f in os.listdir(d): if f.lower().endswith(".iso"): fp = os.path.join(d, f) size = os.path.getsize(fp) isos.append({ "name": f, "path": fp, "size_gb": round(size / (1024**3), 2), }) return {"isos": isos, "total": len(isos)}