概述
作者自己使用的是qemu+kvm的底层实现。
这篇文章仅仅介绍API的使用和一些注意事项,作者自己学习了近乎两个多月,因为目前java方向的API早在13年就停止维护和更新,现在使用最多的方向是Python在网上也很少找到关于java方向的介绍,作者也只是希望在其他朋友学习的路上点亮一盏灯。
org.libvirt 这是libvirt-java的官方文档
libvirt Java API用法连载之libvirt Java API使用详解(四)_kyyee的博客-优快云博客 对于基本概念不清楚的朋友可以先看这位博主的前四期概念讲解 作者也是从他这里开始学习并获得感悟想写一篇学习心得分享给大家的。
废话不多说直接开始
前期的准备工作
libvirt-java官方是已经停止维护很久了,目前是一些程序员在自发维护并开源了 代码可以在GitHub上HTTPS拉取 地址为:GitHub - libvirt/libvirt-java: Read-only mirror. Please submit merge requests / issues to https://gitlab.com/libvirt/libvirt-java
下载下来通过maven打成包引入就行了。
作者的开发是在Linux系统上的 本质上libvirt-java是在调用libvirt的OS库也就是C语言写的方法向宿主机发送指令完成操作,所以如果你也是在Linux上开发一定要先安装OS库 下面是命令:
sudo apt install libvirt-dev
Connect
我们需要知道的是,java程序所有对宿主机上libvirtd发出命令都是依赖于一个连接的这里作者使用的是tcp连接 ,这种连接方式虽然不安全但是不需要验证很方便:
Connect connect = new Connect("qemu+tcp://172.16.0.61/system");
中间的IP部分随自己的宿主机IP而定。
注意:
你需要先设置libvirtd的TCP监听已经打开
virsh链接虚拟机_virsh使用qemu+tcp访问远程libvirtd_zhibo shan的博客-优快云博客 参照这个博主的分享做修改。
为了确保连接正常你可以先在宿主机上尝试自己连接自己 如果进入了virsh 说明成功
virsh --connect qemu+tcp://172.16.0.61/system
但还有可能如果宿主机可以连上自己而外界无法连上,则说明TCP端口16509被禁用了你可以使用以下命令: 因为libvirtd的端口就是16509.
iptables -I INPUT -p tcp --dport 16509 -j ACCEPT
做完了这些准备工作Connect对象就能正常创建完毕。
(当然你也可以使用SSH连接的方法但作者没有使用过)
图片来源 :libvirt 的 qemu+ssh URI 方式连接演示_哔哩哔哩_bilibili
接下来我将介绍Connect中的方法
宿主机中所有的设备都是通过其对应的XML文件进行创建的 只有能被解析的符合规则的XML才能创建出你想要的设备。
StoragePool 存储池 存储池就是用来存放存储卷的文件夹它的大小取决于这个文件夹所处的节点可使用的磁盘空间大小
参数flags 是选择的标志位 你可以看做是代表一个指令 作为一个灵活的成熟的管理系统 每个命令的功能都有多种选择
返回值 | 方法 | 介绍 | |
String[] | listDefinedStoragePools() | 返回宿主机静止状态的存储池名称集合 | |
String[] | listStoragePools() | 正在运行的存储池名称集合 | |
StoragePool | storagePoolCreateXML(java.lang.String xmlDesc, int flags) | 创建临时存储池 后面的flags标志位可以通过
| |
StoragePool | storagePoolDefineXML(java.lang.String xml, int flags) | 创建一个持久的存储池 | |
StoragePool |
| 通过存储池的名称找到这个存储池 在同一个宿主机中存储池的名字必须唯一 | |
StoragePool | storagePoolLookupByUUID(java.util.UUID uuid) | 通过UUID找到存储池在同一个宿主机中存储池的UUID必须唯一 | |
创建存储池:
// 这是一段伪代码仅供参考
// 存储卷xml type=dir 说明这是一个文件夹类型的存储池
// 名称和uuid是宿主机唯一
// 大小 capacity 作者认为是可以不用填 它与你文件夹所在的挂载节点大小一致
// path 这里的路径就是你这个作为存储池的文件夹所在的路径
// 在创建存储池之前请确保你的宿主机的path路径下有这个文件夹否则会报错
String storagePoolXmlDesc = "<pool type='dir'>\n" +
" <name>" + name + "</name>\n" +
" <uuid>" + uuid+ "</uuid>\n" +
" <capacity unit='GiB'>"+ capacity +"</capacity>\n" +
" <allocation unit='GiB'>0</allocation>\n" +
" <source>\n" +
" </source>\n" +
" <target>\n" +
" <path>" + path + "</path>\n" +
" <permissions>\n" +
" <mode>0711</mode>\n" +
" <owner>0</owner>\n" +
" <group>0</group>\n" +
" <label>system_u:object_r:virt_image_t:s0</label>\n" +
" </permissions>\n" +
" </target>\n" +
"</pool>";
Connect connect = new Connect("qemu+tcp://"+ip+":16509/system");
StoragePool storagePool = connect.storagePoolDefineXML(storagePoolXmlDesc, 0);
对存储池的操作以及相关信息的获取方法
还是直接上代码:
StoragePool storagePool = connect.storagePoolLookupByName("你的存储的名称");
// 启动
storagePool.create(0);
// 停止
storagePool.destroy()
// 随宿主机启动而启动 0 禁止 1 开启
storagePool.setAutostart()
// 注销 如果你创建的是一个持久化的存储池就需要
storagePool.destroy();
storagePool.undefine();
// 但是如果你创建是一个临时的就只需要 或者重启宿主机就可以了(我猜你一定不会选这个)
storagePool.destroy();
// 关于存储池的信息
StoragePoolInfo info = storagePool.getInfo();
// uuid
String uuidString = storagePool.getUUIDString();
// state 存储池状态 有四种具体看枚举类
String state = info.state.toString();
// capacity 总容量 单位总是byte
Double capacity = info.capacity/ 1024.00 / 1024.00 / 1024.00;
// available 可用容量
Double available = info.available/ 1024.00 / 1024.00 / 1024.00;
// allocation 已用容量
Double allocation = info.allocation/ 1024.00 / 1024.00 / 1024.00;
// XML 当前存储卷的XML描述
String xml = storagePool.getXMLDesc(0);
StorageVol 存储卷 是虚拟机真正使用的磁盘空间 有了存储卷 一台虚拟机需要的三要素存储、计算、网络三要素就有一个了
storageVol都需要通过storagePool对象获得 代表当前存储池下的存储卷 相同存储池下存储卷的名称必须唯一 (作者的存储卷都是qcow2文件 因为qcow2支持活动快照 且性能优秀)
返回值 | 方法 | 介绍 | |
String[] | listVolumes() | 当前存储池下方所有存储卷的名称集合 | |
StorageVol |
| 创建存储卷 | |
StorageVol | storageVolCreateXMLFrom(java.lang.String xmlDesc, StorageVol cloneVolume, int flags) | 克隆存储卷 | |
StorageVol |
| 通过名称获取存储卷对象 |
上代码:
// type 代表文件格式的存储卷 虚拟机的数据都保存在它对应的存储卷的文件里 通过相同的存储卷可以启动数据相同的虚拟机(但是两台机器共用一个存储卷很难管理不建议这么做)
// type 也可以是block 涉及到活动硬盘的装卸 后面会介绍
String storageVolXml =
"<volume type='file'>\n" +
" <name>"+name+"</name> <!--名称必须唯一-->\n" +
" <source>\n" +
" </source>\n" +
" <capacity unit='GiB'>"+ capacity +"</capacity> <!--StorageVol 的容量-->\n" +
" <allocation>0</allocation> <!--StorageVol 的已用容量-->\n" +
" <target>\n" +
" <path>" + path + "</path> <!--StorageVol 在宿主机的路径-->\n" +
" <format type='qcow2'/> <!--文件类型,通常都是 qcow2,也可以是 raw-->\n" +
" <permissions> <!--权限-->\n" +
" <mode>0600</mode>\n" +
" <owner>0</owner>\n" +
" <group>0</group>\n" +
" </permissions>\n" +
" </target>\n" +
"</volume>";
// 在这个存储池对象下创建存储卷
StorageVol storageVol = storagePool.storageVolCreateXML(storageVolXml, 0);
// 存储卷信息
StorageVolInfo info = storageVol.getInfo();
// 两种结果 VIR_STORAGE_VOL_FILE,或者VIR_STORAGE_VOL_BLOCK; 代表它的type属性
info.type.toString()
// 总容量 单位byte (数据都十分可靠)
info.capacity
// 已用容量 单位byte
info.allocation
// 擦除存储卷上的所有数据
storageVol.wipe();
// 如果想删除存储卷只需要在擦除以后加一句
storageVol.delete(0);
重点功能克隆存储卷:
克隆存储卷可以复制一个数据相同的存储卷在实际操作中是非常有用的特别在克隆虚拟机时
// 源存储卷所在的存储卷
StoragePool sourceStoragePool = connect.storagePoolLookupByName(scrPoolName);
// 源存储卷对象
StorageVol genericVol = sourceStoragePool.storageVolLookupByName(scrVoName);
// 目标存储池 也就是说允许跨存储池克隆
StoragePool storagePool = connect.storagePoolLookupByName(dstPoolName);
// 接下来我们需要创建一个目标存储卷的XML
// 依旧要保持存储卷名称在当前存储池下唯一 并且既然是克隆建议存储卷大小应该也相同
// 路径并不重要 因为已知存储池的情况下 路径就是当前存储池路径加上当前存储卷名称
String dstVolXml = String storageVolXml =
"<volume type='file'>\n" +
" <name>"+name+"</name> <!--名称必须唯一-->\n" +
" <source>\n" +
" </source>\n" +
" <capacity unit='GiB'>"+ capacity +"</capacity> <!--StorageVol 的容量-->\n" +
" <allocation>0</allocation> <!--StorageVol 的已用容量-->\n" +
" <target>\n" +
" <path>" + path + "</path> <!--StorageVol 在宿主机的路径-->\n" +
" <format type='qcow2'/> <!--文件类型,通常都是 qcow2,也可以是 raw-->\n" +
" <permissions> <!--权限-->\n" +
" <mode>0600</mode>\n" +
" <owner>0</owner>\n" +
" <group>0</group>\n" +
" </permissions>\n" +
" </target>\n" +
"</volume>"
// 在目标存储池对象下调用方法 传入克隆目标的XML描述,源存储卷,和标志位就能创建出和源存储卷相同的克隆体
storagePool.storageVolCreateXMLFrom(xmlDesc, genericVol, 0)
同时存储卷还提供了对它进行扩容的方法
关于这个方法有要注意的点 如果我们的虚拟机使用的是当前存储卷可以不停机对这个存储卷进行扩容 存储卷的大小也确实修改了但是你需要自己进入虚拟机上手动挂载多出来的空间
// 原长度
Long srcCap = storageVol.getInfo().capacity;
long targetCapacity = addSize * 1024 * 1024 * 1024;
long resize = srcCap + targetCapacity; // 转换为字节
// 传入的是扩容后的总大小
storageVol.resize(resize, StorageVol.ResizeFlags.ALLOCATE);
下一章更新虚拟机相关内容