|
@@ -1,11 +1,11 @@
|
|
|
"""虚拟机管理路由"""
|
|
"""虚拟机管理路由"""
|
|
|
-from fastapi import APIRouter, HTTPException
|
|
|
|
|
|
|
+from fastapi import APIRouter, HTTPException, Query
|
|
|
from pydantic import BaseModel, Field
|
|
from pydantic import BaseModel, Field
|
|
|
from typing import Optional, List
|
|
from typing import Optional, List
|
|
|
from lxml import etree
|
|
from lxml import etree
|
|
|
import os
|
|
import os
|
|
|
|
|
|
|
|
-from app.libvirt_conn import libvirt_conn
|
|
|
|
|
|
|
+from app.libvirt_conn import conn_pool
|
|
|
from app.utils import parse_vm_info, generate_vm_xml
|
|
from app.utils import parse_vm_info, generate_vm_xml
|
|
|
import libvirt
|
|
import libvirt
|
|
|
|
|
|
|
@@ -36,9 +36,9 @@ class VMClone(BaseModel):
|
|
|
# ===== API =====
|
|
# ===== API =====
|
|
|
|
|
|
|
|
@router.get("/list")
|
|
@router.get("/list")
|
|
|
-async def list_vms():
|
|
|
|
|
|
|
+async def list_vms(host_id: str = Query("local")):
|
|
|
"""获取所有虚拟机列表"""
|
|
"""获取所有虚拟机列表"""
|
|
|
- conn = libvirt_conn.conn
|
|
|
|
|
|
|
+ conn = conn_pool.get_conn(host_id)
|
|
|
domains = conn.listAllDomains(0)
|
|
domains = conn.listAllDomains(0)
|
|
|
vms = []
|
|
vms = []
|
|
|
for dom in domains:
|
|
for dom in domains:
|
|
@@ -56,9 +56,9 @@ async def list_vms():
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/detail/{name}")
|
|
@router.get("/detail/{name}")
|
|
|
-async def get_vm_detail(name: str):
|
|
|
|
|
|
|
+async def get_vm_detail(name: str, host_id: str = Query("local")):
|
|
|
"""获取虚拟机详情"""
|
|
"""获取虚拟机详情"""
|
|
|
- conn = libvirt_conn.conn
|
|
|
|
|
|
|
+ conn = conn_pool.get_conn(host_id)
|
|
|
try:
|
|
try:
|
|
|
dom = conn.lookupByName(name)
|
|
dom = conn.lookupByName(name)
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|
|
@@ -69,20 +69,17 @@ async def get_vm_detail(name: str):
|
|
|
# 运行中的虚拟机获取更多动态信息
|
|
# 运行中的虚拟机获取更多动态信息
|
|
|
if info["state"] == "running":
|
|
if info["state"] == "running":
|
|
|
try:
|
|
try:
|
|
|
- # CPU 时间
|
|
|
|
|
_, _, cpu_time, _ = dom.info()
|
|
_, _, cpu_time, _ = dom.info()
|
|
|
info["cpu_time_ns"] = cpu_time
|
|
info["cpu_time_ns"] = cpu_time
|
|
|
except Exception:
|
|
except Exception:
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- # 内存使用
|
|
|
|
|
try:
|
|
try:
|
|
|
mem_stats = dom.memoryStats()
|
|
mem_stats = dom.memoryStats()
|
|
|
info["memory_stats"] = mem_stats
|
|
info["memory_stats"] = mem_stats
|
|
|
except Exception:
|
|
except Exception:
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- # 块设备统计
|
|
|
|
|
try:
|
|
try:
|
|
|
block_stats = []
|
|
block_stats = []
|
|
|
for disk in info.get("disks", []):
|
|
for disk in info.get("disks", []):
|
|
@@ -99,7 +96,6 @@ async def get_vm_detail(name: str):
|
|
|
except Exception:
|
|
except Exception:
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- # 网络统计
|
|
|
|
|
try:
|
|
try:
|
|
|
net_stats = []
|
|
net_stats = []
|
|
|
for i, iface in enumerate(info.get("interfaces", [])):
|
|
for i, iface in enumerate(info.get("interfaces", [])):
|
|
@@ -120,18 +116,18 @@ async def get_vm_detail(name: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/create")
|
|
@router.post("/create")
|
|
|
-async def create_vm(vm: VMCreate):
|
|
|
|
|
|
|
+async def create_vm(vm: VMCreate, host_id: str = Query("local")):
|
|
|
"""创建虚拟机"""
|
|
"""创建虚拟机"""
|
|
|
- conn = libvirt_conn.conn
|
|
|
|
|
|
|
+ conn = conn_pool.get_conn(host_id)
|
|
|
|
|
|
|
|
# 检查名称是否已存在
|
|
# 检查名称是否已存在
|
|
|
try:
|
|
try:
|
|
|
conn.lookupByName(vm.name)
|
|
conn.lookupByName(vm.name)
|
|
|
raise HTTPException(status_code=400, detail=f"虚拟机 '{vm.name}' 已存在")
|
|
raise HTTPException(status_code=400, detail=f"虚拟机 '{vm.name}' 已存在")
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|
|
|
- pass # 不存在,继续创建
|
|
|
|
|
|
|
+ pass
|
|
|
|
|
|
|
|
- with libvirt_conn.get_rw() as rw_conn:
|
|
|
|
|
|
|
+ with conn_pool.get_rw(host_id) as rw_conn:
|
|
|
try:
|
|
try:
|
|
|
# 确定磁盘路径
|
|
# 确定磁盘路径
|
|
|
pool = rw_conn.storagePoolLookupByName(vm.pool_name)
|
|
pool = rw_conn.storagePoolLookupByName(vm.pool_name)
|
|
@@ -143,7 +139,6 @@ async def create_vm(vm: VMCreate):
|
|
|
disk_path = os.path.join(pool_path, f"{vm.name}.qcow2")
|
|
disk_path = os.path.join(pool_path, f"{vm.name}.qcow2")
|
|
|
|
|
|
|
|
# 创建 qcow2 磁盘
|
|
# 创建 qcow2 磁盘
|
|
|
- # 创建卷的 XML
|
|
|
|
|
vol_xml = f"""<volume>
|
|
vol_xml = f"""<volume>
|
|
|
<name>{vm.name}.qcow2</name>
|
|
<name>{vm.name}.qcow2</name>
|
|
|
<capacity unit='GiB'>{vm.disk_gb}</capacity>
|
|
<capacity unit='GiB'>{vm.disk_gb}</capacity>
|
|
@@ -179,15 +174,15 @@ async def create_vm(vm: VMCreate):
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/action/{name}")
|
|
@router.post("/action/{name}")
|
|
|
-async def vm_action(name: str, action: VMAction):
|
|
|
|
|
|
|
+async def vm_action(name: str, action: VMAction, host_id: str = Query("local")):
|
|
|
"""虚拟机操作"""
|
|
"""虚拟机操作"""
|
|
|
- conn = libvirt_conn.conn
|
|
|
|
|
|
|
+ conn = conn_pool.get_conn(host_id)
|
|
|
try:
|
|
try:
|
|
|
dom = conn.lookupByName(name)
|
|
dom = conn.lookupByName(name)
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|
|
|
raise HTTPException(status_code=404, detail=f"虚拟机 '{name}' 不存在")
|
|
raise HTTPException(status_code=404, detail=f"虚拟机 '{name}' 不存在")
|
|
|
|
|
|
|
|
- with libvirt_conn.get_rw() as rw_conn:
|
|
|
|
|
|
|
+ with conn_pool.get_rw(host_id) as rw_conn:
|
|
|
try:
|
|
try:
|
|
|
rw_dom = rw_conn.lookupByName(name)
|
|
rw_dom = rw_conn.lookupByName(name)
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|
|
@@ -221,9 +216,9 @@ async def vm_action(name: str, action: VMAction):
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/delete/{name}")
|
|
@router.delete("/delete/{name}")
|
|
|
-async def delete_vm(name: str, force: bool = False):
|
|
|
|
|
|
|
+async def delete_vm(name: str, force: bool = False, host_id: str = Query("local")):
|
|
|
"""删除虚拟机"""
|
|
"""删除虚拟机"""
|
|
|
- with libvirt_conn.get_rw() as rw_conn:
|
|
|
|
|
|
|
+ with conn_pool.get_rw(host_id) as rw_conn:
|
|
|
try:
|
|
try:
|
|
|
dom = rw_conn.lookupByName(name)
|
|
dom = rw_conn.lookupByName(name)
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|
|
@@ -262,9 +257,9 @@ async def delete_vm(name: str, force: bool = False):
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/clone/{name}")
|
|
@router.post("/clone/{name}")
|
|
|
-async def clone_vm(name: str, clone: VMClone):
|
|
|
|
|
|
|
+async def clone_vm(name: str, clone: VMClone, host_id: str = Query("local")):
|
|
|
"""克隆虚拟机"""
|
|
"""克隆虚拟机"""
|
|
|
- with libvirt_conn.get_rw() as rw_conn:
|
|
|
|
|
|
|
+ with conn_pool.get_rw(host_id) as rw_conn:
|
|
|
try:
|
|
try:
|
|
|
dom = rw_conn.lookupByName(name)
|
|
dom = rw_conn.lookupByName(name)
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|
|
@@ -278,19 +273,15 @@ async def clone_vm(name: str, clone: VMClone):
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
- # 获取源虚拟机 XML
|
|
|
|
|
xml_desc = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
|
|
xml_desc = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
|
|
|
tree = etree.fromstring(xml_desc.encode())
|
|
tree = etree.fromstring(xml_desc.encode())
|
|
|
|
|
|
|
|
- # 修改名称
|
|
|
|
|
tree.find("name").text = clone.new_name
|
|
tree.find("name").text = clone.new_name
|
|
|
|
|
|
|
|
- # 修改 UUID(删除让libvirt自动生成)
|
|
|
|
|
uuid_elem = tree.find("uuid")
|
|
uuid_elem = tree.find("uuid")
|
|
|
if uuid_elem is not None:
|
|
if uuid_elem is not None:
|
|
|
tree.remove(uuid_elem)
|
|
tree.remove(uuid_elem)
|
|
|
|
|
|
|
|
- # 修改磁盘路径
|
|
|
|
|
import uuid as uuid_mod
|
|
import uuid as uuid_mod
|
|
|
new_uuid = str(uuid_mod.uuid4())[:8]
|
|
new_uuid = str(uuid_mod.uuid4())[:8]
|
|
|
for disk in tree.findall(".//disk[@device='disk']"):
|
|
for disk in tree.findall(".//disk[@device='disk']"):
|
|
@@ -300,7 +291,6 @@ async def clone_vm(name: str, clone: VMClone):
|
|
|
new_path = old_path.replace(f"{name}.qcow2", f"{clone.new_name}.qcow2")
|
|
new_path = old_path.replace(f"{name}.qcow2", f"{clone.new_name}.qcow2")
|
|
|
source.set("file", new_path)
|
|
source.set("file", new_path)
|
|
|
|
|
|
|
|
- # 修改 MAC 地址
|
|
|
|
|
for mac in tree.findall(".//interface/mac"):
|
|
for mac in tree.findall(".//interface/mac"):
|
|
|
import random
|
|
import random
|
|
|
mac_addr = "52:54:00:%02x:%02x:%02x" % (
|
|
mac_addr = "52:54:00:%02x:%02x:%02x" % (
|
|
@@ -310,7 +300,6 @@ async def clone_vm(name: str, clone: VMClone):
|
|
|
)
|
|
)
|
|
|
mac.set("address", mac_addr)
|
|
mac.set("address", mac_addr)
|
|
|
|
|
|
|
|
- # 复制磁盘
|
|
|
|
|
old_disk_path = ""
|
|
old_disk_path = ""
|
|
|
new_disk_path = ""
|
|
new_disk_path = ""
|
|
|
for disk in tree.findall(".//disk[@device='disk']/source"):
|
|
for disk in tree.findall(".//disk[@device='disk']/source"):
|
|
@@ -325,7 +314,6 @@ async def clone_vm(name: str, clone: VMClone):
|
|
|
capture_output=True,
|
|
capture_output=True,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- # 定义新虚拟机
|
|
|
|
|
new_xml = etree.tostring(tree, encoding="unicode")
|
|
new_xml = etree.tostring(tree, encoding="unicode")
|
|
|
rw_conn.defineXML(new_xml)
|
|
rw_conn.defineXML(new_xml)
|
|
|
|
|
|
|
@@ -336,9 +324,9 @@ async def clone_vm(name: str, clone: VMClone):
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/xml/{name}")
|
|
@router.get("/xml/{name}")
|
|
|
-async def get_vm_xml(name: str):
|
|
|
|
|
|
|
+async def get_vm_xml(name: str, host_id: str = Query("local")):
|
|
|
"""获取虚拟机 XML 配置"""
|
|
"""获取虚拟机 XML 配置"""
|
|
|
- conn = libvirt_conn.conn
|
|
|
|
|
|
|
+ conn = conn_pool.get_conn(host_id)
|
|
|
try:
|
|
try:
|
|
|
dom = conn.lookupByName(name)
|
|
dom = conn.lookupByName(name)
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|
|
@@ -348,9 +336,9 @@ async def get_vm_xml(name: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/xml/{name}")
|
|
@router.put("/xml/{name}")
|
|
|
-async def update_vm_xml(name: str, xml: dict):
|
|
|
|
|
|
|
+async def update_vm_xml(name: str, xml: dict, host_id: str = Query("local")):
|
|
|
"""更新虚拟机 XML 配置"""
|
|
"""更新虚拟机 XML 配置"""
|
|
|
- with libvirt_conn.get_rw() as rw_conn:
|
|
|
|
|
|
|
+ with conn_pool.get_rw(host_id) as rw_conn:
|
|
|
try:
|
|
try:
|
|
|
dom = rw_conn.lookupByName(name)
|
|
dom = rw_conn.lookupByName(name)
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|
|
@@ -368,9 +356,9 @@ async def update_vm_xml(name: str, xml: dict):
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/migrate/{name}")
|
|
@router.post("/migrate/{name}")
|
|
|
-async def migrate_vm(name: str, dest_uri: str, live: bool = True):
|
|
|
|
|
|
|
+async def migrate_vm(name: str, dest_uri: str, live: bool = True, host_id: str = Query("local")):
|
|
|
"""迁移虚拟机"""
|
|
"""迁移虚拟机"""
|
|
|
- with libvirt_conn.get_rw() as rw_conn:
|
|
|
|
|
|
|
+ with conn_pool.get_rw(host_id) as rw_conn:
|
|
|
try:
|
|
try:
|
|
|
dom = rw_conn.lookupByName(name)
|
|
dom = rw_conn.lookupByName(name)
|
|
|
except libvirt.libvirtError:
|
|
except libvirt.libvirtError:
|