feat(image): 添加虚拟机克隆功能并优化相关操作
- 新增 ExternalTorrentClient 接口,用于调用第三方 API - 在 ImageVirtualMachinesServiceImpl 中实现虚拟机克隆功能 - 优化虚拟机启动、关闭、销毁和重启操作 - 在 application.yml 中添加外部 API 客户端配置 - 更新 ExternalApiClient 接口,增加获取虚拟机信息的方法master
parent
a421493709
commit
f7a5e8c69c
|
|
@ -128,6 +128,8 @@ public class ImageVirtualMachinesReq {
|
|||
@JsonProperty("memory_total")
|
||||
@ApiModelProperty("内存大小(MB)")
|
||||
private Integer memoryTotal;
|
||||
@JsonProperty("auto_mount_virtio")
|
||||
private Boolean autoMountVirtio;
|
||||
/**
|
||||
* 系统盘大小(GB)
|
||||
**/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.unisinsight.project.entity.dto.ApiResponse;
|
|||
import com.unisinsight.project.entity.dto.Network;
|
||||
import com.unisinsight.project.entity.dto.NetworkData;
|
||||
import com.unisinsight.project.entity.dto.StoragePoolData;
|
||||
import com.unisinsight.project.entity.dto.VmInfoDTO;
|
||||
import com.unisinsight.project.entity.req.*;
|
||||
import com.unisinsight.project.entity.res.ImageStatusRes;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
|
|
@ -75,6 +76,9 @@ public interface ExternalApiClient {
|
|||
@PostMapping("/api/v1/vm/start")
|
||||
ApiResponse<ImageStatusRes> startImage(@RequestBody ImageOperationReq operationReq);
|
||||
|
||||
@GetMapping("/api/v1/vm/{vm_name}")
|
||||
ApiResponse<VmInfoDTO> getVmInfo(@PathVariable("vm_name") String vmName);
|
||||
|
||||
@PostMapping("/api/v1/vm/shutdown")
|
||||
ApiResponse<ImageStatusRes> shutdownImage(@RequestBody ImageOperationReq operationReq);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.unisinsight.project.feign;
|
||||
|
||||
|
||||
/**
|
||||
* @Author : ch
|
||||
* @version : 1.0
|
||||
* @ClassName : ExternalTorrentClient
|
||||
* @Description :
|
||||
* @DATE : Created in 17:12 2025/9/2
|
||||
* <pre> Copyright: Copyright(c) 2025 </pre>
|
||||
* <pre> Company : 紫光汇智信息技术有限公司 </pre>
|
||||
* Modification History:
|
||||
* Date Author Version Discription
|
||||
* --------------------------------------------------------------------------
|
||||
* 2025/09/02 ch 1.0 Why & What is modified: <修改原因描述> *
|
||||
*/
|
||||
|
||||
import com.unisinsight.project.config.FeignConfig;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
* 第三方API客户端
|
||||
* @author ch
|
||||
*/
|
||||
@FeignClient(
|
||||
name = "external-torrent-client",
|
||||
url = "${external.torrent.url:http://localhost:8114}",
|
||||
configuration = FeignConfig.class
|
||||
)
|
||||
public interface ExternalTorrentClient {
|
||||
@GetMapping("/vdi/desk-image")
|
||||
boolean start(@RequestParam("srcFile") String srcFile,
|
||||
@RequestParam("detFile") String detFile,
|
||||
@RequestParam("name") String name,
|
||||
@RequestParam("type") String type);
|
||||
|
||||
@GetMapping("/vdi/progress")
|
||||
Double progress(@RequestParam("name") String name);
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import cn.hutool.json.JSONUtil;
|
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.unisinsight.project.entity.dao.Image;
|
||||
import com.unisinsight.project.entity.dao.ImageTool;
|
||||
import com.unisinsight.project.entity.req.DeleteIdReq;
|
||||
import com.unisinsight.project.entity.req.ImageToolReq;
|
||||
|
|
@ -20,6 +21,7 @@ import com.unisinsight.project.service.ChunkedUploadService;
|
|||
import com.unisinsight.project.service.ImageToolService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -56,6 +58,8 @@ public class ImageToolServiceImpl extends ServiceImpl<ImageToolMapper, ImageTool
|
|||
public Result<PageResult<ImageToolRes>> selectPage(ImageToolReq imageToolReq) {
|
||||
Page<ImageTool> page = new Page<>(imageToolReq.getPageNum(), imageToolReq.getPageSize());
|
||||
LambdaQueryWrapper<ImageTool> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(StringUtils.isNotBlank(imageToolReq.getFileName()), ImageTool::getFileName, imageToolReq.getFileName());
|
||||
queryWrapper.eq(StringUtils.isNotBlank(imageToolReq.getFileType()), ImageTool::getFileType, imageToolReq.getFileType());
|
||||
queryWrapper.orderByAsc(ImageTool::getId);
|
||||
Page<ImageTool> imageToolPage = mapper.selectPage(page, queryWrapper);
|
||||
log.info("分页查询返回:{}", JSONUtil.toJsonStr(imageToolPage));
|
||||
|
|
|
|||
|
|
@ -9,25 +9,20 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.unisinsight.project.entity.dao.Image;
|
||||
import com.unisinsight.project.entity.dao.ImageDesktop;
|
||||
import com.unisinsight.project.entity.dao.ImageTool;
|
||||
import com.unisinsight.project.entity.dao.ImageVirtualMachines;
|
||||
import com.unisinsight.project.entity.dao.ImageDesktop;
|
||||
import com.unisinsight.project.entity.dto.ApiResponse;
|
||||
import com.unisinsight.project.entity.req.DeleteIdReq;
|
||||
import com.unisinsight.project.entity.req.ImageCloneToDesktopReq;
|
||||
import com.unisinsight.project.entity.req.ImageCreateReq;
|
||||
import com.unisinsight.project.entity.req.ImageDeleteReq;
|
||||
import com.unisinsight.project.entity.req.ImageOperationReq;
|
||||
import com.unisinsight.project.entity.req.ImageUpdateReq;
|
||||
import com.unisinsight.project.entity.req.ImageVirtualMachinesReq;
|
||||
import com.unisinsight.project.entity.dto.VmInfoDTO;
|
||||
import com.unisinsight.project.entity.req.*;
|
||||
import com.unisinsight.project.entity.res.ImageStatusRes;
|
||||
import com.unisinsight.project.entity.res.ImageVirtualMachinesRes;
|
||||
import com.unisinsight.project.entity.res.PageResult;
|
||||
import com.unisinsight.project.exception.BaseErrorCode;
|
||||
import com.unisinsight.project.exception.Result;
|
||||
import com.unisinsight.project.feign.ExternalApiClient;
|
||||
import com.unisinsight.project.feign.ExternalTorrentClient;
|
||||
import com.unisinsight.project.mapper.ImageVirtualMachinesMapper;
|
||||
|
||||
import com.unisinsight.project.service.ImageDesktopService;
|
||||
import com.unisinsight.project.service.ImageService;
|
||||
import com.unisinsight.project.service.ImageToolService;
|
||||
|
|
@ -36,7 +31,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
|
@ -59,6 +53,8 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
private ImageService imageService;
|
||||
@Autowired
|
||||
private ExternalApiClient externalApiClient;
|
||||
@Autowired
|
||||
private ExternalTorrentClient externalTorrentClient;
|
||||
@Resource
|
||||
private ImageToolService imageToolService;
|
||||
@Autowired
|
||||
|
|
@ -161,7 +157,7 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
.storagePoolName(imageVirtualMachinesReq.getStoragePoolName())
|
||||
.networkName(imageVirtualMachinesReq.getNetworkModule())
|
||||
.isoPath(systemImage.getStoragePath())
|
||||
.autoMountVirtio(false)
|
||||
.autoMountVirtio(imageVirtualMachinesReq.getAutoMountVirtio())
|
||||
.autostart(false)
|
||||
//驱动
|
||||
.virtioWinPath(imageTool.getStorePath())
|
||||
|
|
@ -190,7 +186,7 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
public Result<?> update(ImageVirtualMachinesReq imageVirtualMachinesReq) {
|
||||
//查询驱动信息
|
||||
LambdaQueryWrapper<ImageTool> imageToolLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||
imageToolLambdaQueryWrapper.eq(ImageTool::getFileName, imageVirtualMachinesReq.getImageName());
|
||||
imageToolLambdaQueryWrapper.eq(ImageTool::getFileName, imageVirtualMachinesReq.getImageToolName());
|
||||
ImageTool imageTool = imageToolService.getOne(imageToolLambdaQueryWrapper);
|
||||
// 调用镜像修改服务
|
||||
ImageUpdateReq updateReq = ImageUpdateReq.builder()
|
||||
|
|
@ -265,24 +261,33 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
|
||||
@Override
|
||||
public Result<?> cloneTemplate(ImageVirtualMachinesReq req) {
|
||||
// todo 改为调用脚本方式执行
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
LambdaQueryWrapper<ImageVirtualMachines> queryWrapper=new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImageVirtualMachines::getImageName, req.getImageName());
|
||||
ImageVirtualMachines imageVirtualMachines = getOne(queryWrapper);
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
}
|
||||
|
||||
// 调用克隆虚拟机到桌面镜像服务
|
||||
ImageCloneToDesktopReq cloneReq = ImageCloneToDesktopReq.builder()
|
||||
.vmName(imageVirtualMachines.getImageName())
|
||||
.desktopName(req.getImageName() + "_desktop") // 使用虚拟机名称加desktop作为桌面镜像名称
|
||||
.storagePath("/vms/iso") // 默认存储路径
|
||||
.description("从虚拟机克隆的桌面镜像")
|
||||
.build();
|
||||
// 调用远程接口获取虚拟机详细信息
|
||||
ApiResponse<VmInfoDTO> vmInfoResponse = externalApiClient.getVmInfo(req.getImageName());
|
||||
if (vmInfoResponse == null || !"200".equals(vmInfoResponse.getCode()) || vmInfoResponse.getData() == null) {
|
||||
log.error("获取虚拟机信息失败: {}", vmInfoResponse);
|
||||
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "获取虚拟机信息失败");
|
||||
}
|
||||
|
||||
ApiResponse<ImageStatusRes> response = externalApiClient.cloneTemplate(cloneReq);
|
||||
if (response == null || !"200".equals(response.getCode())) {
|
||||
// 从返回数据中提取需要的信息
|
||||
VmInfoDTO vmInfo = vmInfoResponse.getData();
|
||||
|
||||
String diskPath = vmInfo.getDiskPath();
|
||||
|
||||
// 根据虚拟机信息调用远程虚拟机信息
|
||||
boolean response = externalTorrentClient.start(diskPath,
|
||||
diskPath.replaceAll(imageVirtualMachines.getImageName(),imageVirtualMachines.getImageName()+"_desktop"),
|
||||
imageVirtualMachines.getImageName()+".json", "vhd");
|
||||
|
||||
if (!response) {
|
||||
return Result.errorResult(BaseErrorCode.HTTP_REQUEST_FAILURE, "克隆虚拟机到桌面镜像失败");
|
||||
}
|
||||
|
||||
|
|
@ -291,9 +296,6 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
imageDesktop.setDesktopName(req.getImageName() + "_desktop");
|
||||
imageDesktop.setImageVirtualId(Math.toIntExact(req.getId()));
|
||||
imageDesktop.setOsVersion(imageVirtualMachines.getOsVersion());
|
||||
// imageDesktop.setStoragePath("/vms/iso/" + req.getImageName() + "_desktop.qcow2"); // 假设是qcow2格式
|
||||
// imageDesktop.setDesktopType(3); // QCOW2格式
|
||||
// imageDesktop.setPublishStatus("unpublished"); // 默认未发布状态
|
||||
imageDesktop.setCreateUser("admin");
|
||||
imageDesktop.setUpdateUser("admin");
|
||||
imageDesktop.setDescription("从虚拟机克隆的桌面镜像");
|
||||
|
|
@ -311,7 +313,9 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
@Override
|
||||
public Result<?> start(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
LambdaQueryWrapper<ImageVirtualMachines> queryWrapper=new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImageVirtualMachines::getImageName, req.getImageName());
|
||||
ImageVirtualMachines imageVirtualMachines = getOne(queryWrapper);
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
|
|
@ -332,7 +336,9 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
@Override
|
||||
public Result<?> shutdown(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
LambdaQueryWrapper<ImageVirtualMachines> queryWrapper=new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImageVirtualMachines::getImageName, req.getImageName());
|
||||
ImageVirtualMachines imageVirtualMachines = getOne(queryWrapper);
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
|
|
@ -353,7 +359,9 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
@Override
|
||||
public Result<?> destroy(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
LambdaQueryWrapper<ImageVirtualMachines> queryWrapper=new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImageVirtualMachines::getImageName, req.getImageName());
|
||||
ImageVirtualMachines imageVirtualMachines = getOne(queryWrapper);
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
|
|
@ -374,7 +382,9 @@ public class ImageVirtualMachinesServiceImpl extends ServiceImpl<ImageVirtualMac
|
|||
@Override
|
||||
public Result<?> reboot(ImageVirtualMachinesReq req) {
|
||||
// 查询虚拟机信息
|
||||
ImageVirtualMachines imageVirtualMachines = machinesMapper.selectById(req.getId());
|
||||
LambdaQueryWrapper<ImageVirtualMachines> queryWrapper=new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImageVirtualMachines::getImageName, req.getImageName());
|
||||
ImageVirtualMachines imageVirtualMachines = getOne(queryWrapper);
|
||||
if (ObjectUtils.isEmpty(imageVirtualMachines)) {
|
||||
log.info("查询镜像虚拟机返回为空");
|
||||
return Result.errorResult(BaseErrorCode.HTTP_ERROR_CODE_500, "虚拟机不存在");
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ feign:
|
|||
config:
|
||||
external-api-client: # 对应 FeignClient 的 name
|
||||
logger-level: full # 完整日志级别
|
||||
external-torrent-client:
|
||||
logger-level: full
|
||||
default: # 全局默认配置
|
||||
logger-level: basic
|
||||
|
||||
|
|
@ -69,3 +71,5 @@ logging:
|
|||
external:
|
||||
api:
|
||||
url: http://10.100.51.178:8000
|
||||
torrent:
|
||||
url: http://10.100.51.178:8114
|
||||
Loading…
Reference in New Issue