feat: KVM虚拟化管理平台初始版本
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
"""存储池管理路由"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
from lxml import etree
|
||||
import os
|
||||
|
||||
from app.libvirt_conn import libvirt_conn
|
||||
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():
|
||||
"""列出所有存储池"""
|
||||
conn = libvirt_conn.conn
|
||||
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):
|
||||
"""获取存储池详情"""
|
||||
conn = libvirt_conn.conn
|
||||
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):
|
||||
"""创建存储池"""
|
||||
with libvirt_conn.get_rw() as rw_conn:
|
||||
xml = f"""<pool type='{pool.type}'>
|
||||
<name>{pool.name}</name>
|
||||
<target>
|
||||
<path>{pool.path}</path>
|
||||
</target>
|
||||
</pool>"""
|
||||
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):
|
||||
"""删除存储池"""
|
||||
with libvirt_conn.get_rw() 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):
|
||||
"""在存储池中创建卷"""
|
||||
with libvirt_conn.get_rw() as rw_conn:
|
||||
try:
|
||||
pool = rw_conn.storagePoolLookupByName(name)
|
||||
except libvirt.libvirtError:
|
||||
raise HTTPException(status_code=404, detail=f"存储池 '{name}' 不存在")
|
||||
|
||||
vol_xml = f"""<volume>
|
||||
<name>{vol.name}</name>
|
||||
<capacity unit='GiB'>{vol.capacity_gb}</capacity>
|
||||
<allocation unit='GiB'>1</allocation>
|
||||
<target>
|
||||
<format type='{vol.format}'/>
|
||||
</target>
|
||||
</volume>"""
|
||||
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):
|
||||
"""删除卷"""
|
||||
with libvirt_conn.get_rw() 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():
|
||||
"""列出可用的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)}
|
||||
Reference in New Issue
Block a user