无法使用 domainCreateXML 或 domainDefineXML 函数在 java 中创建新域

Can't use domainCreateXML or domainDefineXML function to create a new domain in java

当我编写一个 java 项目时,我发现无法使用 libvirt 正确创建域。它抛出一个异常 "qemu-system-x86_64: Failed to start VNC server on `127.0.0.1:4101': Failed to bind socket: Address already in use",但我没有使用“4101”端口。

系统信息:

# system centos7
$ uname -a
Linux bogon 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
$ rpm -ql libvirt-java
/usr/share/doc/libvirt-java-0.4.9
/usr/share/doc/libvirt-java-0.4.9/AUTHORS
/usr/share/doc/libvirt-java-0.4.9/INSTALL
/usr/share/doc/libvirt-java-0.4.9/LICENCE
/usr/share/doc/libvirt-java-0.4.9/NEWS
/usr/share/doc/libvirt-java-0.4.9/README
/usr/share/java/libvirt-0.4.9.jar
/usr/share/java/libvirt.jar
$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)

异常消息:

libvirt: QEMU Driver 错误 : 内部错误:process exited while connecting to monitor: 
(process:5178): GLib-WARNING **: 08:32:41.803: gmem.c:489: custom memory allocation vtable not supported
2019-09-12T00:32:41.839117Z qemu-system-x86_64: Failed to start VNC server on `127.0.0.1:4101': Failed to bind socket: Address already in use
Exception in thread "main" org.libvirt.LibvirtException: 内部错误:process exited while connecting to monitor: 
(process:5178): GLib-WARNING **: 08:32:41.803: gmem.c:489: custom memory allocation vtable not supported
2019-09-12T00:32:41.839117Z qemu-system-x86_64: Failed to start VNC server on `127.0.0.1:4101': Failed to bind socket: Address already in use
    at org.libvirt.ErrorHandler.processError(Unknown Source)
    at org.libvirt.Connect.processError(Unknown Source)
    at org.libvirt.Connect.domainCreateXML(Unknown Source)
    at open.main(open.java:20)

测试代码:

public class VirConnector {
    static Connect connect;

    public static Connect getConnection() {
        try {
            connect = new Connect("qemu:///system");
        } catch ( LibvirtException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return connect;
    }
}

public class VirtualManageService {
    Connect connect = null;
    //创建,加载镜像也是这个函数只是cfg不同而已
    public boolean domainCreate(String cfg) throws LibvirtException {
        //创建一个domain
        boolean status = false;
        try {
            connect = VirConnector.getConnection();
            connect.domainCreateXML(cfg,0);
            status = true;
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
    public boolean domainActive(String name) throws LibvirtException {
        //创建一个domain
        boolean status = false;
        try {
            connect = VirConnector.getConnection();
            Domain domain = connect.domainLookupByName(name);
            int num = domain.isActive();
            if (num == 0){
                domain.create();
                status = true;
            }else {
                status = true;
            }
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
    //删除
    public boolean domainDelete(String name) throws LibvirtException {
        try {
            connect = VirConnector.getConnection();
            Domain domain = connect.domainLookupByName(name);
            domain.destroy();
        } finally {

        }
        return true;
    }
    //创建存储池
    public boolean storagePoolCreate(String cfg) {
        boolean status = false;
        StoragePool storagePool = null;
        try {
            //创建存储池
            connect = VirConnector.getConnection();
            storagePool = connect.storagePoolDefineXML(cfg,0);
            //激活
            storagePool.create(0);
            //设置自动启动
            storagePool.setAutostart(0);
            status = true;
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
    //销毁存储池
    public boolean storagePoolDelete(String name) throws LibvirtException {
        boolean status = false;
        StoragePool storagePool = null;
        try {
            //查找存储池
            connect = VirConnector.getConnection();
            storagePool = connect.storagePoolLookupByName(name);
            //销毁对象
            storagePool.destroy();
            //清除定义
            storagePool.undefine();
            status = true;
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
    //创建分卷
    public boolean volumeCreate(String poolName,String cfg){
        boolean status = false;
        StoragePool storagePool = null;
        StorageVol storageVol = null;
        try {
            //查找存储池
            connect = VirConnector.getConnection();
            storagePool = connect.storagePoolLookupByName(poolName);
            //创建分卷
            storagePool.storageVolCreateXML(cfg,0);
            status = true;
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
    //删除分卷
    public boolean volumeDelete(String poolName,String volName){
        boolean status = false;
        StoragePool storagePool = null;
        StorageVol storageVol = null;
        try {
            //查找存储池
            connect = VirConnector.getConnection();
            storagePool = connect.storagePoolLookupByName(poolName);
            //查找分卷
            storageVol = storagePool.storageVolLookupByName(volName);
            //清空分卷数据
            storageVol.wipe();
            //删除分卷
            storageVol.delete(0);
            status = true;
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
    //分卷迁移
    public boolean volumeMove(String poolNameSrc,String volNameSrc,String poolNameDesc,String cfg) throws LibvirtException {
        boolean status = false;
        StoragePool storagePoolSrc = null;
        StoragePool storagePoolDesc = null;
        StorageVol storageVolSrc = null;
        try {
            //查找存储池
            connect = VirConnector.getConnection();
            storagePoolSrc = connect.storagePoolLookupByName(poolNameSrc);
            //查找待迁移的分卷
            storageVolSrc = storagePoolSrc.storageVolLookupByName(volNameSrc);
            //查找目的存储池
            if (poolNameDesc != poolNameSrc){
                storagePoolDesc = connect.storagePoolLookupByName(poolNameDesc);
                //将待迁移的分卷按照cfg的策略进行迁移
                storagePoolDesc.storageVolCreateXMLFrom(cfg,storageVolSrc,0);
            }else{
                storagePoolSrc.storageVolCreateXMLFrom(cfg,storageVolSrc,0);
            }
            status = true;
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
    //创建网络配置
    public boolean networkCreate(String cfg){
        boolean status = false;
        Network network = null;
        try {
            connect = VirConnector.getConnection();
            network = connect.networkDefineXML(cfg);
            network.create();
            network.getAutostart();
            status = true;
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
    //删除一个网络配置
    public boolean networkDelete(String name){
        boolean status = false;
        Network network = null;
        try {
            connect = VirConnector.getConnection();
            network = connect.networkLookupByName(name);
            network.destroy();
            network.undefine();
            status = true;
        } catch (LibvirtException e) {
            e.printStackTrace();
        }
        return status;
    }
}

@Controller
public class VirController {
    //老师
    @RequestMapping("/openExpVir.do")
    @ResponseBody
//    public String openVirtualForTeacher(HttpServletRequest request) throws IOException, LibvirtException, DocumentException {
    public String openVirtualForTeacher(String coursename, String teacherID) throws IOException, LibvirtException {
        //获取当前用户及其课程的信息
        int[] port = new int[2];
//        HttpSession session = request.getSession();
        String link = "null";
        //账号就是teacherDomain的主键
//        String teacherID = (String) session.getAttribute("teacherID");
//        String courseName = (String) session.getAttribute("coursename");
        String courseName = coursename;
        InquireDao inquireDao = new InquireDao();
        port = inquireDao.inquireTeacherDomain(teacherID, courseName);
        if (port[0] != 0 & port[1] != 0) {
            boolean active = new VirtualManageService().domainActive(teacherID+courseName);
            //1.传递参数
            String shell = "bash " + "/shell/run.sh " + port[0] + " " + port[1];
            //2.启动脚本
            Process process = Runtime.getRuntime().exec(shell);
            //创建连接,写入数据库意味着websocket创建成功,镜像创建成功。故直接访问link
            link = "http://localhost:" + port[1] + "/vnc_lite.html";
        } else {
            //等待创建镜像和创建websocket
            //创建镜像
            VirtualManageService virtualManageService = new VirtualManageService();
            //对镜像进行克隆
            String volNameDesc = teacherID + courseName;
            //配置文件
            String pathVolMove = "/config/vol/defaultMove.xml";
            XmlAlterService xmlVolMove = new XmlAlterService(pathVolMove);
            xmlVolMove.xmlAlterName(volNameDesc);
            String volDescCfg = xmlVolMove.xmlAlterVolPath("/home/chenyiguang/images/" + volNameDesc);
            boolean isMove = virtualManageService.volumeMove("mypool", "sparse.img", "mypool", volDescCfg);
            if (isMove) {
                //成功
            } else {
                //失败
            }
            //获取配置文件 源配置文件
            String path = "/config/dom/defaultDomain.xml";
            XmlAlterService xmlDol = new XmlAlterService(path);
            xmlDol.xmlAlterName(teacherID + courseName);
            //自动选择端口
            port = virtualManageService.selectPortForTeacher();
            xmlDol.xmlAlterDomPort(port[0]);
            String domainCfg = xmlDol.xmlAlterDomPath("/home/chenyiguang/images/" + volNameDesc);
            boolean status = virtualManageService.domainCreate(domainCfg);
            if (status == true) {
                //创建成功
            } else {
                //创建失败
            }
            //写入数据库
            AddDao addDao = new AddDao();
            addDao.addTeacherDomain(teacherID, courseName, port[0], port[1]);
            //创建websockify
            //1.传递参数
            String shell = "bash " + "/shell/run.sh " + port[0] + " " + port[1];
            //2.启动脚本
            Process process = Runtime.getRuntime().exec(shell);
            link = "http://localhost:" + port[1] + "/vnc_lite.html";
        }
        return link;
    }

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<domain type="qemu">
    <name>20050505云计算与大数据</name>
    <memory unit="G">1</memory>
    <vcpu>1</vcpu>
    <os>
        <type arch="x86_64" machine="pc">hvm</type>
        <boot dev="hd"/>
    </os>
    <devices>
        <emulator>/usr/bin/qemu-system-x86_64</emulator>
        <disk type="file" device="disk">
            <source file="/home/chenyiguang/images/20050505云计算与大数据"/>
            <target dev="hda"/>
        </disk>
        <interface type="network">
            <source network="default"/>
        </interface>
        <input type="mouse" bus="ps2"/>
        <graphics type="vnc" port="10001" listen="127.0.0.1"/>
    </devices>
</domain>
$ virt-xml-validate error.xml 
error.xml validates

It throws an exception that is "qemu-system-x86_64: Failed to start VNC server on `127.0.0.1:4101': Failed to bind socket: Address already in use", but I don't use the "4101" port.

这是一个稍微误导的 QEMU 错误消息。

您的 XML 配置正在请求端口 10001:

<graphics type="vnc" port="10001" listen="127.0.0.1"/>

-vnc 的 QEMU 命令行参数不需要端口号,而是需要 VNC 显示号。要从端口号中获取 VNC 显示号,我们必须减去 5900。因此您对端口 10001 的请求会变成 QEMU 命令行参数 -vnc 127.0.0.1:4101.

QEMU 无法绑定到显示 4101 / 端口 10001,因为其他东西已经在监听它。

这里最简单的解决方案是根本不指定端口号,而是在 XML 配置中设置 autoport="yes",libvirt 将自动选择一个空闲端口来使用。