cat /etc/issue
uname -r
grep -E '(vmx|svm)' /proc/cpuinfo
lsmod|grep kvm
ls -l /dev/kvm
cd /usr/local
# wget下载
wget https://download.qemu.org/qemu-6.2.0.tar.xz
# 解压
tar -Jxvf qemu-6.2.0.tar.xz
cd qemu-6.2.0
./configure
配置失败的尝试安装以下几个包
apt-get install zlib1g zlib1g-dev
apt install libglib2.0-dev
apt-get install libpixman-1-dev
apt install ninja-build
# 多线程编译
make -j 50
# 安装
make install | tee make-install.log
安装成功后,查看qemu提供的工具(按两次Tab键给出以qemu-开头的命令)
which qemu-img
# 适用于18
apt-get install libvirt-bin
apt-get install libvirt-dev
# 适用于20
apt-get install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
apt-get install libvirt-dev
# 查看libvirtd的版本号
libvirtd --version
# virt-manager的安装
apt-get install virt-manager
# 查看版本号
virt-manager --version
# 下载链接
https://www.jetbrains.com/idea/download/other.html
tar -zxvf ideaIU-2022.1.1.tar.gz
# 赋权限
chmod 755 -R idea-IU-221.5591.52/
# 运行
cd /home/wyl/idea/idea-IU-221.5591.52/bin
./idea.sh
sudo apt-get install git
git config --global user.name "pepsi-wyl"
git config --global user.email "[email protected]"
登陆界面和web端管理
宿主机详细信息
Libvirt连接信息
虚拟机信息、创建、启动、挂起、还原、保存、恢复、关闭、强制关闭、重启、删除
快照信息、创建、删除、使用
镜像信息、添加、删除、下载
存储池信息、创建、删除
网络断开和连接
1.LibvirtUtils 获取连接和连接信息
2.SigarUtils 获取宿主机信息
3.LibvirtService 管理虚拟机的一系列操作
4.LibvirtController 处理前端页面请求调用LibvirtService完成对虚拟机的业务
5.UserController 处理前端页面请求完成登陆逻辑
6.HTML页面 展示数据和方便操作
云平台管理系统是对虚拟机进行管理和操作,
该系统采用Libvirt Java API 进行分析和设计
,使用编程语言java作为开发语言,使用IDEA作为开发平台,
通过SpringBoot+Thymeleaf框架实现功能,能够很好的实现系统的开发及测试。
点击添加虚拟机按钮即可跳转到添加界面 输入创建虚拟机的名称和选择虚拟机的镜像即可创建个默认大小的虚拟机 虚拟机创建成功
点击虚拟机的启动按钮即可启动 虚拟机变为运行状态 虚拟机管理器查看也是运行状态 虚拟机启动成功
点击虚拟机的挂起按钮即可挂起 虚拟机变为挂起状态 虚拟机管理器查看也是挂起状态
点击虚拟机的还原按钮即可还原 虚拟机变为运行状态 虚拟机管理器查看也是运行状态
点击虚拟机的保存按钮即可保存 选择合适的路径进行保存 保存成功虚拟机状态为关闭并且生成磁盘文件
点击虚拟机的恢复按钮即可恢复 选择磁盘文件 恢复成功虚拟机状态变为运行态
点击虚拟机的关闭按钮或者强制关闭按钮即可关闭虚拟机 关闭成功变为关闭状态 虚拟机管理器查看也是关闭状态
点击虚拟机的重启按钮即可重启虚拟机 虚拟机正在重启 虚拟机启动成功并且状态仍然为运行状态
点击删除按钮即可删除虚拟机 虚拟机删除后将不存在 虚拟机管理器查看也是是不存在
点击快照管理即可进入管理界面 快照界面可以查看快照拍摄的时间状态等信息 也可以对快照进行一系列操作
点击镜像管理即可进行镜像管理,该页面展示了镜像信息和定义了一系列操作
点击镜像添加跳转到添加界面 输入添加镜像的名称和选择镜像文件点击提交按钮即可 添加成功
点击添加存储池按钮跳转到添加界面 输入存储池名称和存储池的路径点击提交按钮即可 存储添加成功
点击存储池管理按钮跳转到存储池管理界面 点击删除按钮即可删除存储池 存储删除成功
package com.example.libvirt.Utils;
import com.example.libvirt.pojo.LibvirtConnect;
import lombok.SneakyThrows;
import lombok.extern.java.Log;
import org.libvirt.Connect;
@Log
public class LibvirtUtils {
private static Connect connect;
// Connection
@SneakyThrows
public static Connect getConnection() {
if (null == connect) {
connect = new Connect("qemu:///system", false);
log.info("Libvirt local connection successful" + "\n"
+ " 连接URI: " + connect.getURI() + "\n"
+ " 宿主机主机名: " + connect.getHostName() + "\n"
+ " 宿主机剩余内存: " + connect.getFreeMemory() + "\n"
+ " 宿主机最大cpu数量: " + connect.getMaxVcpus(null) + "\n"
+ " libvirt库版本号: " + connect.getLibVirVersion() + "\n"
+ " hypervisor名称: " + connect.getType()
);
}
return connect;
}
// ConnectionInfo
@SneakyThrows
public static LibvirtConnect getConnectionIo() {
Connect connect = getConnection();
return LibvirtConnect.builder()
.url(connect.getURI())
.hostName(connect.getHostName())
.libVirVersion(connect.getLibVirVersion())
.hypervisorVersion(connect.getType())
.build();
}
}
package com.example.libvirt.Utils;
import com.example.libvirt.pojo.Host;
import lombok.SneakyThrows;
import org.hyperic.sigar.*;
import java.util.HashMap;
public class SigarUtils {
private static Sigar sigar;
private static Sigar getSigarInstance() {
if (null == sigar) sigar = new Sigar();
return sigar;
}
// getHostInfo
@SneakyThrows
public static Host getHostInfo() {
OperatingSystem OS = OperatingSystem.getInstance();
Mem mem = getSigarInstance().getMem();
Swap swap = sigar.getSwap();
CpuInfo[] cpuInfoList = getSigarInstance().getCpuInfoList();
HashMap<String, Object> fileInfoMap = new HashMap<>();
for (FileSystem fs : getSigarInstance().getFileSystemList()) {
//盘符类型:ext4 硬盘类型:本地硬盘 2
if (fs.getType() == 2 && fs.getSysTypeName().equals("ext4")) {
FileSystemUsage usage = getSigarInstance().getFileSystemUsage(fs.getDirName());
fileInfoMap.put("devName", fs.getDevName()); // 盘符名称
fileInfoMap.put("dirName", fs.getDirName()); // 盘符路径
fileInfoMap.put("fileTotal", usage.getTotal()); // 总大小 KB
fileInfoMap.put("fileUsed", usage.getUsed()); // 已经使用量 KB
fileInfoMap.put("fileUsePercent", (usage.getUsePercent() * 100D)); // 资源的利用率 %
}
}
HashMap<String, String> netInfoMap = new HashMap<>();
for (String name : getSigarInstance().getNetInterfaceList()) {
if (name.equals("ens33")) {
NetInterfaceConfig cfg = sigar.getNetInterfaceConfig(name);
if (NetFlags.LOOPBACK_ADDRESS.equals(cfg.getAddress()) || (cfg.getFlags() & NetFlags.IFF_LOOPBACK) != 0 || NetFlags.NULL_HWADDR.equals(cfg.getHwaddr()))
continue;
netInfoMap.put("description", cfg.getDescription()); // 网卡描述
netInfoMap.put("netType", cfg.getType()); // 网卡类型
netInfoMap.put("IPAddress", cfg.getAddress()); // IP地址
netInfoMap.put("MACAddress", cfg.getHwaddr()); // MAC地址
netInfoMap.put("netmask", cfg.getNetmask()); // 子网掩码
}
}
return Host.builder()
// os
.vendor(OS.getVendor()) // Ubuntu
.vendorVersion(OS.getVendorVersion()) // 20.04
.vendorCodeName(OS.getVendorCodeName()) // focal
.version(OS.getVersion()) // 5.13.0-44-generic
// memory 单位:k
.memory(mem.getTotal() / 1024L)
.memoryUsed(mem.getUsed() / 1024L)
.memoryFree(mem.getFree() / 1024L)
.swap(swap.getTotal() / 1024L)
.swapUsed(swap.getUsed() / 1024L)
.swapFree(swap.getFree() / 1024L)
// cpu
.cpuNum(cpuInfoList.length)
.cpuModel(cpuInfoList[1].getModel())
.cpuMhz(cpuInfoList[1].getMhz())
// file
.devName((String) fileInfoMap.get("devName")) // 盘符名称
.dirName((String) fileInfoMap.get("dirName")) // 盘符路径
.fileTotal((Long) fileInfoMap.get("fileTotal")) // 总大小 KB
.fileUsed((Long) fileInfoMap.get("fileUsed")) // 已经使用量 KB
.fileUsePercent((Double) fileInfoMap.get("fileUsePercent")) // 资源的利用率 %
// net
.netDescription(netInfoMap.get("description")) // 网卡描述
.netType(netInfoMap.get("netType")) // 网卡类型
.netIP(netInfoMap.get("IPAddress")) // IP地址
.netMAC(netInfoMap.get("MACAddress")) // MAC地址
.netMask(netInfoMap.get("netmask")) // 子网掩码
.build();
}
}
package com.example.libvirt.pojo;
import lombok.*;
@Builder
@Getter
@ToString
public class Host {
// os
private String vendor; // Ubuntu
private String vendorVersion; // 20.04
private String vendorCodeName; // focal
private String version; // 5.13.0-44-generic
// memory 单位:k
private long memory;
private long memoryUsed;
private long memoryFree;
private long swap;
private long swapUsed;
private long swapFree;
// cpu
private int cpuNum;
private String cpuModel;
private int cpuMhz;
// file
private String devName;
private String dirName;
private long fileTotal;
private long fileUsed;
private double fileUsePercent;
// net
private String netDescription;
private String netType;
private String netIP;
private String netMAC;
private String netMask;
}
package com.example.libvirt.pojo;
import lombok.*;
@Builder
@Getter
@ToString
public class Virtual {
private int id;
private String name;
private String state;
}
package com.example.libvirt.pojo;
import lombok.*;
@Builder
@Getter
@ToString
public class ImgFile {
private String name;
private String size;
}
package com.example.libvirt.pojo;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
@Builder
@Getter
@ToString
public class Snapshot {
private String name;
private String creationTime;
private String state;
}
package com.example.libvirt.pojo;
import lombok.*;
@Builder
@Getter
@ToString
public class Storagepool {
private String name; // 名称
private String type; // 类型
private int capacity; // GB 容量
private int available; // GB 可用容量
private int allocation; // GB 已用容量
private String usage; // 使用率(%)
private String state; // 状态
private String xml; // 描述xml
}
package com.example.libvirt.pojo;
import lombok.*;
@Builder
@Getter
@ToString
public class LibvirtConnect {
private String url;
private String hostName;
private long libVirVersion;
private String hypervisorVersion;
}
package com.example.libvirt.service;
import com.example.libvirt.Utils.*;
import com.example.libvirt.pojo.*;
import org.apache.commons.io.FileUtils;
import lombok.SneakyThrows;
import lombok.extern.java.Log;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.libvirt.*;
import javax.servlet.http.HttpServletResponse;
import javax.swing.*;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
@Log
@Service(value = "libvirtService")
public class LibvirtService {
/**
* getHostInformation
*/
public Host getHostInfo() {
return SigarUtils.getHostInfo();
}
/**
* getLibvirtConnectInformation
*/
public LibvirtConnect getLibvirtConnectInformation() {
return LibvirtUtils.getConnectionIo();
}
/**
* getDomainById
*/
@SneakyThrows
public Domain getDomainById(int id) {
return LibvirtUtils.getConnection().domainLookupByID(id);
}
/**
* getDomainByName
*/
@SneakyThrows
public Domain getDomainByName(String name) {
return LibvirtUtils.getConnection().domainLookupByName(name);
}
/**
* getVirtualById
*/
@SneakyThrows
public Virtual getVirtualById(int id) {
Domain domain = getDomainById(id);
return Virtual.builder()
.id(domain.getID())
.name(domain.getName())
.state(domain.getInfo().state.toString())
.build();
}
/**
* getVirtualByName
*/
@SneakyThrows
public Virtual getVirtualByName(String name) {
Domain domain = getDomainByName(name);
return Virtual.builder()
.id(domain.getID())
.name(domain.getName())
.state(domain.getInfo().state.toString())
.build();
}
/**
* 虚拟机列表
*/
@SneakyThrows
public List<Virtual> getVirtualList() {
ArrayList<Virtual> virtualList = new ArrayList<>();
// live
int[] ids = LibvirtUtils.getConnection().listDomains();
for (int id : ids) virtualList.add(getVirtualById(id));
// down
String[] names = LibvirtUtils.getConnection().listDefinedDomains();
for (String name : names) virtualList.add(getVirtualByName(name));
return virtualList;
}
/**
* 暂停/挂起 虚拟机
*/
@SneakyThrows
private void suspendedDomain(Domain domain) {
if (domain.isActive() == 1) {
domain.suspend();
log.info(domain.getName() + "虚拟机已挂起!");
} else log.info("虚拟机未打开");
}
public void suspendedDomainById(int id) {
suspendedDomain(getDomainById(id));
}
public void suspendedDomainName(String name) {
suspendedDomain(getDomainByName(name));
}
/**
* 还原 暂停/挂起 虚拟机
*/
@SneakyThrows
private void resumeDomain(Domain domain) {
if (domain.isActive() == 1) {
domain.resume();
log.info(domain.getName() + "虚拟机已唤醒!");
} else log.info("虚拟机未打开");
}
public void resumeDomainById(int id) {
resumeDomain(getDomainById(id));
}
public void resumeDomainByName(String name) {
resumeDomain(getDomainByName(name));
}
/**
* 保存 虚拟机 --->img文件
*/
@SneakyThrows
private void saveDomain(Domain domain) {
JFileChooser jf = new JFileChooser();
jf.setFileSelectionMode(JFileChooser.SAVE_DIALOG | JFileChooser.DIRECTORIES_ONLY);
jf.showDialog(null, null);
String f = jf.getSelectedFile().getAbsolutePath() + "/save.img";
if (domain.isActive() == 1) {
domain.save(f);
log.info(domain.getName() + "虚拟机状态已保存!" + "save: " + f);
} else log.info("虚拟机未打开");
}
public void saveDomainById(int id) {
saveDomain(getDomainById(id));
}
public void saveDomainByName(String name) {
saveDomain(getDomainByName(name));
}
/**
* 恢复 虚拟机 --->img文件
*/
@SneakyThrows
private void restoreDomain(Domain domain) {
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(null);
if (chooser.getSelectedFile() != null) {
String path = chooser.getSelectedFile().getPath();
if (domain.isActive() == 0) {
domain.getConnect().restore(path);
log.info(domain.getName() + "虚拟机状态已恢复!!" + "path: " + path);
} else log.info("虚拟机未关闭");
}
}
public void restoreDomainById(int id) {
restoreDomain(getDomainById(id));
}
public void restoreDomainByName(String name) {
restoreDomain(getDomainByName(name));
}
/**
* 启动 虚拟机
*/
@SneakyThrows
private void initiateDomain(Domain domain) {
if (domain.isActive() == 0) {
domain.create();
log.info(domain.getName() + "虚拟机已启动!");
} else log.info("虚拟机已经打开过!");
}
public void initiateDomainByName(String name) {
initiateDomain(getDomainByName(name));
}
/**
* 关闭 虚拟机
*/
@SneakyThrows
private void shutdownDomain(Domain domain) {
if (domain.isActive() == 1) {
domain.shutdown();
log.info(domain.getName() + "虚拟机已正常关机!");
} else log.info("虚拟机未打开");
}
public void shutdownDomainById(int id) {
shutdownDomain(getDomainById(id));
}
public void shutdownDomainByName(String name) {
shutdownDomain(getDomainByName(name));
}
/**
* 强制关闭 虚拟机
*/
@SneakyThrows
private void shutdownMustDomain(Domain domain) {
if (domain.isActive() == 1) {
domain.destroy();
log.info(domain.getName() + "虚拟机已强制关机!");
} else log.info("虚拟机未打开");
}
public void shutdownMustDomainById(int id) {
shutdownMustDomain(getDomainById(id));
}
public void shutdownMustDomainByName(String name) {
shutdownMustDomain(getDomainByName(name));
}
/**
* 重启 虚拟机
*/
@SneakyThrows
private void rebootDomain(Domain domain) {
if (domain.isActive() == 1) {
domain.reboot(0);
log.info(domain.getName() + "虚拟机状态已重启!");
} else log.info("虚拟机未打开");
}
public void rebootDomainById(int id) {
rebootDomain(getDomainById(id));
}
public void rebootDomainByName(String name) {
rebootDomain(getDomainByName(name));
}
/**
* 添加 虚拟机 xml------>name 1024MB
*/
@SneakyThrows
public void addDomainByName(String name) {
String xml = "<domain type='kvm'>\n" +
" <name>" + name + "</name>\n" +
" <uuid>" + UUID.randomUUID() + "</uuid>\n" +
" <metadata>\n" +
" <libosinfo:libosinfo xmlns:libosinfo=\"http://libosinfo.org/xmlns/libvirt/domain/1.0\">\n" +
" <libosinfo:os id=\"http://ubuntu.com/ubuntu/14.04\"/>\n" +
" </libosinfo:libosinfo>\n" +
" </metadata>\n" +
" <memory unit='KiB'>1048576</memory>\n" + // 1024 MB
" <currentMemory unit='KiB'>1048576</currentMemory>\n" + // 1024 MB
" <vcpu placement='static'>2</vcpu>\n" +
" <os>\n" +
" <type arch='x86_64' machine='pc-i440fx-focal'>hvm</type>\n" +
" <boot dev='hd'/>\n" +
" </os>\n" +
" <features>\n" +
" <acpi/>\n" +
" <apic/>\n" +
" <vmport state='off'/>\n" +
" </features>\n" +
" <cpu mode='host-model' check='partial'/>\n" +
" <clock offset='utc'>\n" +
" <timer name='rtc' tickpolicy='catchup'/>\n" +
" <timer name='pit' tickpolicy='delay'/>\n" +
" <timer name='hpet' present='no'/>\n" +
" </clock>\n" +
" <on_poweroff>destroy</on_poweroff>\n" +
" <on_reboot>restart</on_reboot>\n" +
" <on_crash>destroy</on_crash>\n" +
" <pm>\n" +
" <suspend-to-mem enabled='no'/>\n" +
" <suspend-to-disk enabled='no'/>\n" +
" </pm>\n" +
" <devices>\n" +
" <emulator>" + "/usr/bin/qemu-system-x86_64" + "</emulator>\n" +
" <disk type='file' device='disk'>\n" +
" <driver name='qemu' type='qcow2'/>\n" +
" <source file='/home/wyl/KVMImg/" + name + ".img'/>\n" + // FileSource
" <target dev='vda' bus='virtio'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>\n" +
" </disk>\n" +
" <controller type='usb' index='0' model='ich9-ehci1'>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x7'/>\n" +
" </controller>\n" +
" <controller type='usb' index='0' model='ich9-uhci1'>\n" +
" <master startport='0'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0' multifunction='on'/>\n" +
" </controller>\n" +
" <controller type='usb' index='0' model='ich9-uhci2'>\n" +
" <master startport='2'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x1'/>\n" +
" </controller>\n" +
" <controller type='usb' index='0' model='ich9-uhci3'>\n" +
" <master startport='4'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x2'/>\n" +
" </controller>\n" +
" <controller type='pci' index='0' model='pci-root'/>\n" +
" <controller type='virtio-serial' index='0'>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>\n" +
" </controller>\n" +
" <interface type='network'>\n" +
" <mac address='52:54:00:27:6d:ef'/>\n" +
" <source network='default'/>\n" +
" <model type='virtio'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>\n" +
" </interface>\n" +
" <serial type='pty'>\n" +
" <target type='isa-serial' port='0'>\n" +
" <model name='isa-serial'/>\n" +
" </target>\n" +
" </serial>\n" +
" <console type='pty'>\n" +
" <target type='serial' port='0'/>\n" +
" </console>\n" +
" <channel type='spicevmc'>\n" +
" <target type='virtio' name='com.redhat.spice.0'/>\n" +
" <address type='virtio-serial' controller='0' bus='0' port='1'/>\n" +
" </channel>\n" +
" <input type='tablet' bus='usb'>\n" +
" <address type='usb' bus='0' port='1'/>\n" +
" </input>\n" +
" <input type='mouse' bus='ps2'/>\n" +
" <input type='keyboard' bus='ps2'/>\n" +
" <graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0' keymap='en-us'>\n" +
" <listen type='address' address='0.0.0.0'/>\n" +
" </graphics>\n" +
" <sound model='ich6'>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>\n" +
" </sound>\n" +
" <video>\n" +
" <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>\n" +
" </video>\n" +
" <redirdev bus='usb' type='spicevmc'>\n" +
" <address type='usb' bus='0' port='2'/>\n" +
" </redirdev>\n" +
" <redirdev bus='usb' type='spicevmc'>\n" +
" <address type='usb' bus='0' port='3'/>\n" +
" </redirdev>\n" +
" <memballoon model='virtio'>\n" +
" <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>\n" +
" </memballoon>\n" +
" </devices>\n" +
"</domain>";
LibvirtUtils.getConnection().domainDefineXML(xml); // define ------> creat
log.info(name + "虚拟机已创建!");
}
/**
* 删除 虚拟机 xml
*/
@SneakyThrows
private void deleteDomain(Domain domain) {
if (domain.isActive() == 1) domain.destroy(); // 强制关机
domain.undefine();
log.info(domain.getName() + "虚拟机已删除!");
}
public void deleteDomainById(int id) {
deleteDomain(getDomainById(id));
}
public void deleteDomainByName(String name) {
deleteDomain(getDomainByName(name));
}
/**
* get ImgList
*/
public List<ImgFile> getImgList() {
List<ImgFile> list = new ArrayList<>();
File[] files = new File("/home/wyl/KVMImg/").listFiles();
if (files != null) {
for (File file : files) {
list.add(ImgFile.builder()
.name(file.getName())
.size(FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(file)))
.build());
}
}
return list;
}
/**
* 添加 img
*/
@SneakyThrows
public Boolean addImgFile(String name, MultipartFile file) {
if (!file.isEmpty()) {
file.transferTo(new File("/home/wyl/KVMImg/" + name + ".img"));
log.info("文件" + name + ".img已经保存!");
return true;
}
log.info("文件" + name + ".img保存失败!");
return false;
}
/**
* 下载 img
*/
@SneakyThrows
public String downImgFile(String name, HttpServletResponse response) {
File file = new File("/home/wyl/KVMImg/" + name);
if (!file.exists()) return "下载文件不存在";
response.reset();
response.setContentType("application/octet-stream");
response.setCharacterEncoding("utf-8");
response.setContentLength((int) file.length());
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(file.getName(), "UTF-8")); // 设置编码格式
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
OutputStream os = response.getOutputStream();
int i = 0;
byte[] buff = new byte[1024];
while ((i = bis.read(buff)) != -1) {
os.write(buff, 0, i);
os.flush();
}
bis.close();
os.close();
return "下载成功";
}
/**
* 删除 img
*/
public Boolean deleteImgFile(String name) {
if (new File("/home/wyl/KVMImg/" + name).delete()) {
log.info("文件" + name + "已经删除!");
return true;
}
log.info("文件" + name + "文件不存在");
return false;
}
/**
* 关闭网络
*/
@SneakyThrows
public void closeNetWork() {
Domain domain = getDomainByName(getVirtualList().get(0).getName());
Network network = domain.getConnect().networkLookupByName("default");
if (network.isActive() == 1) {
network.destroy();
log.info("网络" + network.getName() + "已经被关闭!");
} else log.info("网络" + network.getName() + "已经处于关闭状态!");
}
/**
* 启动网络
*/
@SneakyThrows
public void openNetWork() {
Domain domain = getDomainByName(getVirtualList().get(0).getName());
Network network = domain.getConnect().networkLookupByName("default");
if (network.isActive() == 0) {
network.create();
log.info("网络" + network.getName() + "已经被打开!");
} else log.info("网络" + network.getName() + "已经处于打开状态!");
}
/**
* 网络 State
*/
@SneakyThrows
public String getNetState() {
Domain domain = getDomainByName(getVirtualList().get(0).getName());
if (domain.getConnect().networkLookupByName("default").isActive() == 1) return "on";
else return "off";
}
/**
* getSnapshotList
*/
@SneakyThrows
public List<Snapshot> getSnapshotListByName(String name) {
// virsh snapshot-list 虚拟机名
String cmd = "virsh snapshot" + "-list " + name;
Process process = Runtime.getRuntime().exec(cmd);
LineNumberReader line = new LineNumberReader(new InputStreamReader(process.getInputStream()));
ArrayList<Snapshot> snapshots = new ArrayList<>();
String str;
int linCount = 0;
int snapshotNum = getDomainByName(name).snapshotNum(); // 2
while ((str = line.readLine()) != null && snapshotNum > 0) {
linCount++;
if (linCount <= 2) continue; // -2 line
else {
snapshotNum--;
String[] lineStr = str.split(" ");
snapshots.add(Snapshot.builder().name(lineStr[0]).creationTime(lineStr[1]).state(lineStr[2]).build());
}
}
return snapshots;
}
/**
* snapshot管理
*/
@SneakyThrows
private void snapshotManger(String op, String name, String snapshotName) {
// virsh snapshot-create-as 虚拟机名称 快照名称
// virsh snapshot-delete 虚拟机名称 快照名称
// virsh snapshot-revert 虚拟机名称 快照名称
String cmd = "";
switch (op) {
case "creat":
cmd = "virsh snapshot" + "-create-as " + name + " " + snapshotName;
break;
case "delete":
cmd = "virsh snapshot" + "-delete " + name + " " + snapshotName;
break;
case "revert":
cmd = "virsh snapshot" + "-revert " + name + " " + snapshotName;
break;
}
Runtime.getRuntime().exec(cmd);
}
/**
* 创建快照
*/
public void createSnapshot(String name, String snapshotName) {
snapshotManger("creat", name, snapshotName);
log.info("虚拟机" + name + "成功创建快照" + snapshotName);
}
/**
* 删除快照
*/
public void deleteSnapshot(String name, String snapshotName) {
snapshotManger("delete", name, snapshotName);
log.info("虚拟机" + name + "成功删除快照" + snapshotName);
}
/**
* 启动快照
*/
public void revertSnapshot(String name, String snapshotName) {
snapshotManger("revert", name, snapshotName);
log.info("虚拟机" + name + "成功切换快照" + snapshotName);
}
/**
* getStoragePoolList
*/
@SneakyThrows
public List<Storagepool> getStoragePoolList() {
String[] pools = LibvirtUtils.getConnection().listStoragePools();
String[] definedPools = LibvirtUtils.getConnection().listDefinedStoragePools();
log.info("pools" + Arrays.toString(pools) + "definedPools" + Arrays.toString(definedPools));
List<Storagepool> storagePoolList = new ArrayList<>();
for (String pool : pools) storagePoolList.add(getStoragePool(pool));
for (String pool : definedPools) storagePoolList.add(getStoragePool(pool));
return storagePoolList;
}
/**
* getStoragePool ByName
*/
@SneakyThrows
public Storagepool getStoragePool(String name) {
StoragePool storagePool = LibvirtUtils.getConnection().storagePoolLookupByName(name);
StoragePoolInfo info = storagePool.getInfo();
return Storagepool.builder()
.name(name) // 名称
.type("qcow2") // 类型
.capacity((int) (info.capacity / 1024.00 / 1024.00 / 1024.00)) // GB 容量
.available((int) (info.available / 1024.00 / 1024.00 / 1024.00)) // GB 可用容量
.allocation((int) (info.allocation / 1024.00 / 1024.00 / 1024.00)) // GB 已用容量
.usage(((int) ((info.allocation / 1024.00 / 1024.00 / 1024.00) / (info.capacity / 1024.00 / 1024.00 / 1024.00) * 100)) + "%") // 使用率(%)
.state(info.state.toString()) // 状态
.xml(storagePool.getXMLDesc(0)) // 描述xml
.build();
}
/**
* 删除StoragePool ByName
*/
@SneakyThrows
public void deleteStoragePool(String name) {
StoragePool storagePool = LibvirtUtils.getConnection().storagePoolLookupByName(name);
for (String pool : LibvirtUtils.getConnection().listStoragePools())
if (pool.equals(name)) storagePool.destroy();
for (String pool : LibvirtUtils.getConnection().listDefinedStoragePools())
if (pool.equals(name)) storagePool.undefine();
}
/**
* 创建Storagepool >>>>>> url必须存在
*/
@SneakyThrows
public boolean createStoragepool(String name, String url) {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"\n" +
"<pool type=\"dir\">\n" +
" <name>" + name + "</name> \n" + // 名称必须唯一
" <source>\n" +
" </source>\n" +
" <target>\n" +
" <path>" + url + "</path> \n" + // StoragePool 在宿主机的路径
" <permissions> \n" + // 权限
" <mode>0711</mode>\n" +
" <owner>0</owner>\n" +
" <group>0</group>\n" +
" </permissions>\n" +
" </target>\n" +
"</pool>";
return LibvirtUtils.getConnection().storagePoolCreateXML(xml, 0) == null ? false : true;
}
}
package com.example.libvirt.controller;
import com.example.libvirt.pojo.*;
import com.example.libvirt.service.LibvirtService;
import lombok.SneakyThrows;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@Controller
public class LibvirtController {
@Resource(name = "libvirtService")
private LibvirtService libvirtService;
@RequestMapping(value = {"/index"})
public String index(Model model) {
Host hostInfo = libvirtService.getHostInfo();
model.addAttribute("hostinfo", hostInfo);
LibvirtConnect connectInformation = libvirtService.getLibvirtConnectInformation();
model.addAttribute("connectInformation", connectInformation);
return "index";
}
@RequestMapping("/main")
public String main(Model model) {
List<Virtual> virtualList = libvirtService.getVirtualList();
model.addAttribute("virtualList", virtualList);
String netState = libvirtService.getNetState();
model.addAttribute("netState", netState);
return "main";
}
@RequestMapping("/openOrCloseNetWork")
public String openOrCloseNetWork(@RequestParam("netState") String netState) {
if ("on".equals(netState)) libvirtService.closeNetWork();
if ("off".equals(netState)) libvirtService.openNetWork();
return "redirect:main";
}
@SneakyThrows
@RequestMapping("/initiate")
public String initiateVirtual(@RequestParam("name") String name) {
libvirtService.initiateDomainByName(name);
Thread.sleep(1000);
return "redirect:main";
}
@SneakyThrows
@RequestMapping("/suspended")
public String suspendedVirtual(@RequestParam("name") String name) {
libvirtService.suspendedDomainName(name);
Thread.sleep(1000);
return "redirect:main";
}
@SneakyThrows
@RequestMapping("/resume")
public String resumeVirtual(@RequestParam("name") String name) {
libvirtService.resumeDomainByName(name);
Thread.sleep(1000);
return "redirect:main";
}
@SneakyThrows
@RequestMapping("/save")
public String saveVirtual(@RequestParam("name") String name) {
libvirtService.saveDomainByName(name);
Thread.sleep(1000);
return "redirect:main";
}
@SneakyThrows
@RequestMapping("/restore")
public String restoreVirtual(@RequestParam("name") String name) {
libvirtService.restoreDomainByName(name);
Thread.sleep(1000);
return "redirect:main";
}
@SneakyThrows
@RequestMapping("/shutdown")
public String shutdownVirtual(@RequestParam("name") String name) {
libvirtService.shutdownDomainByName(name);
Thread.sleep(1000);
return "redirect:main";
}
@SneakyThrows
@RequestMapping("/shutdownMust")
public String shutdownMustVirtual(@RequestParam("name") String name) {
libvirtService.shutdownMustDomainByName(name);
Thread.sleep(1000);
return "redirect:main";
}
@SneakyThrows
@RequestMapping("/reboot")
public String rebootVirtual(@RequestParam("name") String name) {
libvirtService.rebootDomainByName(name);
Thread.sleep(1000);
return "redirect:main";
}
@RequestMapping("/delete")
public String deleteVirtual(@RequestParam("name") String name) {
libvirtService.deleteDomainByName(name);
libvirtService.deleteImgFile(name + ".img");
return "redirect:main";
}
@RequestMapping("/toAddVirtual")
public String toAddVirtual(Model model) {
String netState = libvirtService.getNetState();
model.addAttribute("netState", netState);
return "addVirtual";
}
@RequestMapping(value = "/addVirtual", method = RequestMethod.POST)
public String addVirtual(@RequestParam("virtualName") String name,
@RequestPart("file") MultipartFile file) {
libvirtService.addDomainByName(name);
libvirtService.addImgFile(name, file);
return "redirect:main";
}
@RequestMapping("/img")
public String imgList(Model model) {
List<ImgFile> imgList = libvirtService.getImgList();
model.addAttribute("imgList", imgList);
String netState = libvirtService.getNetState();
model.addAttribute("netState", netState);
return "img";
}
@RequestMapping("/toAddImg")
public String toAddImg(Model model) {
String netState = libvirtService.getNetState();
model.addAttribute("netState", netState);
return "addImg";
}
@RequestMapping("/addImg")
public String addImg(@RequestParam("imgName") String name,
@RequestPart("file") MultipartFile file) {
libvirtService.addImgFile(name, file);
return "redirect:img";
}
@RequestMapping("/deleteImg")
public String deleteImg(@RequestParam("name") String name) {
libvirtService.deleteImgFile(name);
return "redirect:img";
}
@ResponseBody
@RequestMapping("/downImg")
public String downImg(@RequestParam("name") String name, HttpServletResponse response) {
return libvirtService.downImgFile(name, response);
}
@RequestMapping("/getSnapshotList")
public String getSnapshotList(@RequestParam("name") String name,
Model model) {
List<Snapshot> snapshotList = libvirtService.getSnapshotListByName(name);
model.addAttribute("snapshotList", snapshotList);
model.addAttribute("virtualName", name);
return "snapshot";
}
@SneakyThrows
@RequestMapping("/deleteSnapshot")
public String deleteSnapshot(@RequestParam("virtualName") String virtualName,
@RequestParam("snapshotName") String snapshotName) {
libvirtService.deleteSnapshot(virtualName, snapshotName);
Thread.sleep(1000);
return "redirect:/getSnapshotList?name=" + virtualName;
}
@SneakyThrows
@RequestMapping("/revertSnapshot")
public String revertSnapshot(@RequestParam("virtualName") String virtualName,
@RequestParam("snapshotName") String snapshotName) {
libvirtService.revertSnapshot(virtualName, snapshotName);
Thread.sleep(1000);
return "redirect:/getSnapshotList?name=" + virtualName;
}
@SneakyThrows
@RequestMapping("/createSnapshot")
public String createSnapshot(@RequestParam("virtualName") String virtualName,
@RequestParam("snapshotName") String snapshotName) {
libvirtService.createSnapshot(virtualName, snapshotName);
Thread.sleep(1000);
return "redirect:/getSnapshotList?name=" + virtualName;
}
@RequestMapping("/storagePoolList")
public String storagePoolList(Model model) {
List<Storagepool> storagePoolList = libvirtService.getStoragePoolList();
model.addAttribute("storagePoolList", storagePoolList);
String netState = libvirtService.getNetState();
model.addAttribute("netState", netState);
return "storagepool";
}
@SneakyThrows
@RequestMapping("/deleteStoragePool")
public String deleteStoragePool(@RequestParam("name") String name) {
libvirtService.deleteStoragePool(name);
Thread.sleep(1000);
return "redirect:/storagePoolList";
}
@RequestMapping("/toCreateStoragepool")
public String toCreateStoragepool(Model model) {
String netState = libvirtService.getNetState();
model.addAttribute("netState", netState);
return "addStoragepool";
}
@SneakyThrows
@RequestMapping("/createStoragepool")
public String createStoragepool(@RequestParam("storagepoolName") String name,
@RequestParam("storagepoolPath") String url) {
libvirtService.createStoragepool(name, url);
Thread.sleep(1000);
return "redirect:/storagePoolList";
}
}
package com.example.libvirt.controller;
import com.example.libvirt.pojo.Host;
import com.example.libvirt.pojo.LibvirtConnect;
import com.example.libvirt.service.LibvirtService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpSession;
@Controller
public class UserController {
/**
* 跳转登录页面
*/
@RequestMapping(value = {"/"})
public String toLogin() {
return "login";
}
/**
* 登录
*/
@RequestMapping(value = {"/login"})
public String login(@RequestParam(value = "username", required = true) String userName,
@RequestParam(value = "password", required = true) String password,
Model model,
HttpSession session) {
if ("admin".equals(userName) && "admin".equals(password)) {
session.setAttribute("loginUser", userName);//UserName存入Session
return "redirect:index";
}
model.addAttribute("msg", "userName or password error!");
return "login";
}
/**
* 注销
*/
@RequestMapping(value = "/loginOut", method = RequestMethod.GET)
public String loginOut(HttpSession session) {
if (session.getAttribute("loginUser") != null) {
session.removeAttribute("loginUser"); //移除Session 转到登陆界面
}
return "redirect:/";
}
}
package com.example.libvirt.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle拦截的请求路径是{}", request.getRequestURI());
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser != null) return true; // 放行
//拦截住。未登录。跳转到登录页
request.setAttribute("msg", "请先登录");
//response.sendRedirect("/");
request.getRequestDispatcher("/").forward(request, response);
return false;
}
/**
* 目标方法执行完成以后
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}", modelAndView);
}
/**
* 页面渲染以后
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}", ex);
}
}
package com.example.libvirt.config;
import com.example.libvirt.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import java.util.List;
@Configuration(value = "myMvcConfig")
public class MyMvcConfig implements WebMvcConfigurer {
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).
addPathPatterns("/**").
excludePathPatterns("/", "/login").
excludePathPatterns("/css/*", "/js/*", "/img/*");
}
@Override
public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
}
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
}
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
}
@Override
public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {
}
@Override
public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
spring.http.multipart.max-file-size=10240MB
spring.http.multipart.max-request-size=10240MB
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<meta name="description" content=""/>
<meta name="author" content=""/>
<title>云平台管理系统AdminLogin</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"/>
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet"/>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body class="text-center">
<form class="form-signin" th:action="@{/login}" method="post">
<img class="mb-4" th:src="@{/img/log.jpg}" alt="" width="72" height="72"/>
<h1 class="h3 mb-3 font-weight-normal">Admin Log In</h1>
<input type="text" class="form-control" name="username" required="" autofocus=""/>
<input type="password" class="form-control" name="password" required=""/>
<button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
<p class="mt-5 mb-3 " style="color: red" th:text="${msg}"></p>
<p class="mt-5 mb-3 text-muted">© 2021 design by wyl</p>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<meta name="description" content=""/>
<meta name="author" content=""/>
<title>云平台管理系统</title>
<!-- Font Awesome icons (free version)-->
<script src="https://use.fontawesome.com/releases/v6.1.0/js/all.js" crossorigin="anonymous"></script>
<!-- Google fonts-->
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css"/>
<link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet"
type="text/css"/>
<!-- Core theme CSS (includes Bootstrap)-->
<link th:href="@{css/styleindex.css}" rel="stylesheet"/>
<!-- <link th:href="@{/css/signin.css}" rel="stylesheet"/>-->
<!-- Bootstrap core JS-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script th:href="@{js/scripts.js}"></script>
<script src="https://cdn.startbootstrap.com/sb-forms-latest.js"></script>
</head>
<body id="page-top">
<!-- Navigation-->
<nav class="navbar navbar-expand-lg bg-secondary text-uppercase fixed-top" id="mainNav">
<div class="container">
<a class="navbar-brand" href="#page-top">云平台管理系统</a>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto">
<li class="nav-item mx-0 mx-lg-1">
<a class="nav-link py-3 px-0 px-lg-3 rounded" th:href="@{/main}">云平台管理</a>
</li>
<li class="nav-item mx-0 mx-lg-1">
<a class="nav-link py-3 px-0 px-lg-3 rounded" th:href="@{/loginOut}">注销登录</a>
</li>
</ul>
</div>
</div>
</nav>
<!--Masthead-->
<header class="masthead bg-primary text-white text-center">
<div class="container d-flex align-items-center flex-column">
<!-- Masthead Heading-->
<h1 class="masthead-heading text-uppercase mb-0">云平台管理系统</h1>
<!-- Icon Divider-->
<div class="divider-custom divider-light">
<div class="divider-custom-line"></div>
<div class="divider-custom-line"></div>
<div class="divider-custom-line"></div>
<div class="divider-custom-line"></div>
<div class="divider-custom-line"></div>
</div>
<!-- Masthead Subheading-->
<p class="masthead-subheading font-weight-light mb-0">SpringBoot + Thymeleaf + HTML+BootStrap + Libvirt +
Sigar</p>
</div>
</header>
<!-- Portfolio Section-->
<section class="page-section portfolio" id="portfolio">
<div class="container">
<div class="row justify-content-center">
<!-- Portfolio Item 1-->
<div class="col-md-6 col-lg-4 mb-5">
<div class="portfolio-item mx-auto" data-bs-toggle="modal" data-bs-target="#portfolioModal1">
<div class="portfolio-item-caption d-flex align-items-center justify-content-center h-100 w-100">
<div class="portfolio-item-caption-content text-center text-white"><i
class="fas fa-plus fa-3x"></i></div>
</div>
<!-- <p class="mb-5">宿主机详细信息</p>-->
<img class="img-fluid" th:src="@{img/index1.png}" alt="..."/>
</div>
</div>
<!-- Portfolio Item 2-->
<div class="col-md-6 col-lg-4 mb-5">
<div class="portfolio-item mx-auto" data-bs-toggle="modal" data-bs-target="#portfolioModal2">
<div class="portfolio-item-caption d-flex align-items-center justify-content-center h-100 w-100">
<div class="portfolio-item-caption-content text-center text-white"><i
class="fas fa-plus fa-3x"></i></div>
</div>
<!-- <p class="mb-5">Libvirt连接信息</p>-->
<img class="img-fluid" th:src="@{img/index.png}" alt="..."/>
</div>
</div>
</div>
</div>
</section>
<!-- Copyright Section-->
<div class="copyright py-4 text-center text-white">
<div class="container"><small>© 2022 design by wyl</small></div>
</div>
<!-- Portfolio Modals-->
<!-- Portfolio Modal 1-->
<div class="portfolio-modal modal fade" id="portfolioModal1" tabindex="-1" aria-labelledby="portfolioModal1"
aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header border-0">
<button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center pb-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<h5>版本信息</h5>
<p class="mb-3" th:inline="text">型号:[[${hostinfo.vendor}]] 数字版本:[[${hostinfo.vendorVersion}]] 文字版本:[[${hostinfo.vendorCodeName}]] 内核版本:[[${hostinfo.version}]]</p>
<h5>内存信息</h5>
<p class="mb-3" th:inline="text">内存:[[${hostinfo.memory}]]k 使用内存:[[${hostinfo.memoryUsed}]]k 可用内存:[[${hostinfo.memoryFree}]]k</p>
<p class="mb-3" th:inline="text">交换空间:[[${hostinfo.swap}]]k 使用交换空间:[[${hostinfo.swapUsed}]]k 可用交换空间:[[${hostinfo.swapFree}]]k</p>
<h5>CPU信息</h5>
<p class="mb-4" th:inline="text">宿主机CPU数量:[[${hostinfo.cpuNum}]]个 CPU型号:[[${hostinfo.cpuModel}]] CPU频率:[[${hostinfo.cpuMhz}]]Mhz</p>
<h5>磁盘信息</h5>
<p class="mb-4" th:inline="text">设备名称:[[${hostinfo.devName}]] 设备目录:[[${hostinfo.dirName}]] 磁盘总量:[[${hostinfo.fileTotal}]]KB
<br/>使用:[[${hostinfo.fileUsed}]]KB 使用率:[[${hostinfo.fileUsePercent}]]%
</p>
<h5>网络信息</h5>
<p class="mb-4" th:inline="text">网卡描述:[[${hostinfo.netDescription}]] 网卡类型:[[${hostinfo.netType}]] IP地址:[[${hostinfo.netIP}]]
<br/>
MAC地址:[[${hostinfo.netMAC}]] Mask掩码:[[${hostinfo.netMask}]]
</p>
<button class="btn btn-primary" data-bs-dismiss="modal">
<i class="fas fa-xmark fa-fw"></i>
关闭窗口
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Portfolio Modal 2-->
<div class="portfolio-modal modal fade" id="portfolioModal2" tabindex="-1" aria-labelledby="portfolioModal2"
aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header border-0">
<button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center pb-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<h5>Libvirt连接信息</h5>
<p class="mb-4" th:inline="text">主机名称:[[${connectInformation.hostName}]]</p>
<p class="mb-4" th:inline="text">连接URL:[[${connectInformation.url}]]</p>
<p class="mb-4" th:inline="text">libVir版本:[[${connectInformation.libVirVersion}]]</p>
<p class="mb-4" th:inline="text">
hypervisor版本:[[${connectInformation.hypervisorVersion}]]</p>
<button class="btn btn-primary" data-bs-dismiss="modal">
<i class="fas fa-xmark fa-fw">
</i>
关闭窗口
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8"/>
<title>虚拟机管理</title>
<!-- <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>-->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"/>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/index}">首页</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
<li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
<li><a th:href="@{/img}">镜像管理</a></li>
<li><a th:href="@{/toAddImg}">添加镜像</a></li>
<li><a th:href="@{/storagePoolList}">存储池管理</a></li>
<li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a></li>
<li><a th:href="@{/loginOut}">注销登录</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="virtual:${virtualList}">
<td th:text="${virtual.id}"></td>
<td th:text="${virtual.name}"></td>
<td th:text="${virtual.state}"></td>
<td>
<div>
<a th:href="@{/initiate(name=${virtual.name})}">启动</a>
|
<a th:href="@{/suspended(name=${virtual.name})}">挂起</a>
|
<a th:href="@{/resume(name=${virtual.name})}">还原</a>
|
<a th:href="@{/save(name=${virtual.name})}">保存</a>
|
<a th:href="@{/restore(name=${virtual.name})}">恢复</a>
|
<a th:href="@{/shutdown(name=${virtual.name})}">关闭</a>
|
<a th:href="@{/shutdownMust(name=${virtual.name})}">强制关闭</a>
|
<a th:href="@{/reboot(name=${virtual.name})}">重启</a>
|
<a th:href="@{/delete(name=${virtual.name})}">删除</a>
|
<a th:href="@{/getSnapshotList(name=${virtual.name})}">快照管理</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8"/>
<title>快照管理</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
</ul>
<form class="navbar-form navbar-right" th:action="@{/createSnapshot}" method="get">
<div class="form-group">
<input type="text" class="from-control" id="virtualName" name="virtualName" th:value="${virtualName}"/>
</div>
<div class="form-group">
<input type="text" class="from-control" id="snapshotName" name="snapshotName" placeholder="请输入快照名称" required=""/>
</div>
<button type="submit" class="btn btn-default">拍摄</button>
</form>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>名称</th>
<th>时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="snapshot:${snapshotList}">
<td th:text="${snapshot.name}"></td>
<td th:text="${snapshot.creationTime}"></td>
<td th:text="${snapshot.state}"></td>
<td>
<div>
<a th:href="@{/deleteSnapshot(virtualName=${virtualName},snapshotName=${snapshot.name})}">删除</a>
|
<a th:href="@{/revertSnapshot(virtualName=${virtualName},snapshotName=${snapshot.name})}">使用</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8"/>
<title>虚拟机管理</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/index}">首页</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
<li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
<li class="active"><a th:href="@{/img}">镜像管理</a></li>
<li><a th:href="@{/toAddImg}">添加镜像</a></li>
<li><a th:href="@{/storagePoolList}">存储池管理</a></li>
<li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a></li>
<li><a th:href="@{/loginOut}">注销登录</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>名称</th>
<th>大小</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="img:${imgList}">
<td th:text="${img.name}"></td>
<td th:text="${img.size}"></td>
<td>
<div>
<a th:href="@{/deleteImg(name=${img.name})}">删除</a>
|
<a th:href="@{/downImg(name=${img.name})}">下载</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8"/>
<title>存储池管理</title>
<!-- <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>-->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"/>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/index}">首页</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
<li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
<li><a th:href="@{/img}">镜像管理</a></li>
<li><a th:href="@{/toAddImg}">添加镜像</a></li>
<li class="active"><a th:href="@{/storagePoolList}">存储池管理</a></li>
<li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a>
</li>
<li><a th:href="@{/loginOut}">注销登录</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>name</th>
<th>type</th>
<th>capacity</th>
<th>available</th>
<th>allocation</th>
<th>usage</th>
<th>state</th>
</tr>
</thead>
<tbody>
<tr th:each="storagePool:${storagePoolList}">
<td th:text="${storagePool.name}"></td>
<td th:text="${storagePool.type}"></td>
<td th:inline="text">[[${storagePool.capacity}]] GB</td>
<td th:inline="text">[[${storagePool.available}]] GB</td>
<td th:inline="text">[[${storagePool.allocation}]] GB</td>
<td th:text="${storagePool.usage}"></td>
<td th:text="${storagePool.state}"></td>
<td>
<div>
<a th:href="@{/deleteStoragePool(name=${storagePool.name})}">删除</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>添加虚拟机</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/index}">首页</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
<li class="active"><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
<li><a th:href="@{/img}">镜像管理</a></li>
<li><a th:href="@{/toAddImg}">添加镜像</a></li>
<li><a th:href="@{/storagePoolList}">存储池管理</a></li>
<li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a></li>
<li><a th:href="@{/loginOut}">注销登录</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div align="center" class="center">
<form class="form-inline" role="form" th:action="@{/addVirtual}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label class="sr-only" for="virtualName">虚拟机名称</label>
<input type="text" class="from-control" id="virtualName" name="virtualName" placeholder="请输入虚拟机名称"
required=""/>
</div>
<div class="form-group">
<label class="sr-only" for="file">选择镜像文件</label>
<input type="file" id="file" name="file" required=""/><br/>
</div>
<button type="submit" class="btn btn-default">提交</button>
</form>
</div>
</body>
</html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>添加镜像</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/index}">首页</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
<li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
<li><a th:href="@{/img}">镜像管理</a></li>
<li class="active"><a th:href="@{/toAddImg}">添加镜像</a></li>
<li><a th:href="@{/storagePoolList}">存储池管理</a></li>
<li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a></li>
<li><a th:href="@{/loginOut}">注销登录</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div align="center" class="center">
<form class="form-inline" role="form" th:action="@{/addImg}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label class="sr-only" for="imgName">镜像名称</label>
<input type="text" class="from-control" id="imgName" name="imgName" placeholder="请输入镜像名称"
required=""/>
</div>
<div class="form-group">
<label class="sr-only" for="file">选择镜像文件</label>
<input type="file" id="file" name="file" required=""/><br/>
</div>
<button type="submit" class="btn btn-default">提交</button>
</form>
</div>
</body>
</html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>添加虚拟机</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/index}">首页</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
<li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
<li><a th:href="@{/img}">镜像管理</a></li>
<li><a th:href="@{/toAddImg}">添加镜像</a></li>
<li><a th:href="@{/storagePoolList}">存储池管理</a></li>
<li class="active"><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a>
</li>
<li><a th:href="@{/loginOut}">注销登录</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div align="center" class="center">
<form class="form-inline" role="form" th:action="@{/createStoragepool}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label class="sr-only" for="storagepoolName">存储池名称</label>
<input type="text" class="from-control" id="storagepoolName" name="storagepoolName" placeholder="请输入存储池名称"
required=""/>
</div>
<div class="form-group">
<label class="sr-only" for="storagepoolPath">存储池路径</label>
<input type="text" class="from-control" id="storagepoolPath" name="storagepoolPath" placeholder="请输入存储池路径"
required=""/>
</div>
<button type="submit" class="btn btn-default">提交</button>
</form>
</div>
</body>
</html>