diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java index 761abb3f0ab..0d62758af67 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -37,6 +38,7 @@ import org.apache.cloudstack.acl.Rule; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiServerService; import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.command.admin.backup.DeleteVmCheckpointCmd; import org.apache.cloudstack.api.command.admin.backup.FinalizeBackupCmd; import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; import org.apache.cloudstack.api.command.admin.vm.DeployVMCmdByAdmin; @@ -84,6 +86,7 @@ import org.apache.cloudstack.veeam.api.converter.NetworkVOToVnicProfileConverter import org.apache.cloudstack.veeam.api.converter.NicVOToNicConverter; import org.apache.cloudstack.veeam.api.converter.StoreVOToStorageDomainConverter; import org.apache.cloudstack.veeam.api.converter.UserVmJoinVOToVmConverter; +import org.apache.cloudstack.veeam.api.converter.UserVmVOToCheckpointConverter; import org.apache.cloudstack.veeam.api.converter.VmSnapshotVOToSnapshotConverter; import org.apache.cloudstack.veeam.api.converter.VolumeJoinVOToDiskConverter; import org.apache.cloudstack.veeam.api.dto.Backup; @@ -442,7 +445,7 @@ public class ServerAdapter extends ManagerBase { } Integer cpu = null; try { - cpu = request.getCpu().getTopology().getSockets(); + cpu = Integer.valueOf(request.getCpu().getTopology().getSockets()); } catch (Exception ignored) {} if (cpu == null) { throw new InvalidParameterValueException("CPU topology sockets must be specified"); @@ -1078,9 +1081,8 @@ public class ServerAdapter extends ManagerBase { if (vmVo == null) { throw new InvalidParameterValueException("VM with ID " + vmUuid + " not found"); } - // Register a context as resource owner - Account account = accountService.getAccount(vmVo.getAccountId()); - CallContext ctx = CallContext.register(vmVo.getUserId(), vmVo.getAccountId()); + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { StartBackupCmd cmd = new StartBackupCmd(); ComponentContext.inject(cmd); @@ -1089,8 +1091,8 @@ public class ServerAdapter extends ManagerBase { params.put(ApiConstants.NAME, request.getName()); params.put(ApiConstants.DESCRIPTION, request.getDescription()); ApiServerService.AsyncCmdResult result = - apiServerService.processAsyncCmd(cmd, params, ctx, vmVo.getUserId(), account); - if (result.objectId == null) { + apiServerService.processAsyncCmd(cmd, params, ctx, vmVo.getUserId(), serviceUserAccount.second()); + if (result == null || result.objectId == null) { throw new CloudRuntimeException("Unexpected backup ID returned"); } BackupVO vo = backupDao.findById(result.objectId); @@ -1169,14 +1171,16 @@ public class ServerAdapter extends ManagerBase { throw new InvalidParameterValueException("Backup with ID " + backupUuid + " not found"); } Pair serviceUserAccount = createServiceAccountIfNeeded(); - CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); + CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); try { FinalizeBackupCmd cmd = new FinalizeBackupCmd(); ComponentContext.inject(cmd); - cmd.setBackupId(backup.getId()); - cmd.setVmId(vm.getId()); - boolean result = incrementalBackupService.finalizeBackup(cmd); - if (!result) { + Map params = new HashMap<>(); + params.put(ApiConstants.VIRTUAL_MACHINE_ID, vm.getUuid()); + params.put(ApiConstants.ID, backup.getUuid()); + ApiServerService.AsyncCmdResult result = + apiServerService.processAsyncCmd(cmd, params, ctx, vm.getUserId(), serviceUserAccount.second()); + if (result == null) { throw new CloudRuntimeException("Failed to finalize backup"); } backup = backupDao.findById(backup.getId()); @@ -1197,42 +1201,37 @@ public class ServerAdapter extends ManagerBase { } public List listCheckpointsByInstanceUuid(final String uuid) { - throw new InvalidParameterValueException("Checkpoints for VM with ID " + uuid + " not implemented"); -// UserVmVO vo = userVmDao.findByUuid(uuid); -// if (vo == null) { -// throw new InvalidParameterValueException("VM with ID " + uuid + " not found"); -// } -// List checkpoints = checkpointDao.findByVmId(vo.getId()); -// return CheckpointVOToCheckpointConverter.toCheckpointList(checkpoints, vo.getUuid()); + UserVmVO vo = userVmDao.findByUuid(uuid); + if (vo == null) { + throw new InvalidParameterValueException("VM with ID " + uuid + " not found"); + } + Checkpoint checkpoint = UserVmVOToCheckpointConverter.toCheckpoint(vo); + if (checkpoint == null) { + return Collections.emptyList(); + } + return List.of(checkpoint); } - public ResourceAction deleteCheckpoint(String uuid, boolean async) { - throw new InvalidParameterValueException("Delete Checkpoint with ID " + uuid + " not implemented"); -// ResourceAction action = null; -// CheckpointVO vo = checkpointDao.findByUuid(uuid); -// if (vo == null) { -// throw new InvalidParameterValueException("Checkpoint with ID " + uuid + " not found"); -// } -// Pair serviceUserAccount = createServiceAccountIfNeeded(); -// CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); -// try { -// DeleteCheckpointCmd cmd = new DeleteCheckpointCmd(); -// ComponentContext.inject(cmd); -// Map params = new HashMap<>(); -// params.put(ApiConstants.CHECKPOINT_ID, vo.getUuid()); -// ApiServerService.AsyncCmdResult result = -// apiServerService.processAsyncCmd(cmd, params, ctx, serviceUserAccount.first().getId(), -// serviceUserAccount.second()); -// AsyncJobJoinVO jobVo = asyncJobJoinDao.findById(result.jobId); -// if (jobVo == null) { -// throw new CloudRuntimeException("Failed to find job for checkpoint deletion"); -// } -// action = AsyncJobJoinVOToJobConverter.toAction(jobVo); -// } catch (Exception e) { -// throw new CloudRuntimeException("Failed to delete checkpoint: " + e.getMessage(), e); -// } finally { -// CallContext.unregister(); -// } -// return action; + public void deleteCheckpoint(String vmUuid, String checkpointId) { + UserVmVO vo = userVmDao.findByUuid(vmUuid); + if (vo == null) { + throw new InvalidParameterValueException("VM with ID " + vmUuid + " not found"); + } + if (!Objects.equals(vo.getActiveCheckpointId(), checkpointId)) { + logger.warn("Checkpoint ID {} does not match active checkpoint for VM {}", checkpointId, vmUuid); + return; + } + Pair serviceUserAccount = createServiceAccountIfNeeded(); + CallContext.register(serviceUserAccount.first(), serviceUserAccount.second()); + try { + DeleteVmCheckpointCmd cmd = new DeleteVmCheckpointCmd(); + ComponentContext.inject(cmd); + cmd.setVmId(vo.getId()); + incrementalBackupService.deleteVmCheckpoint(cmd); + } catch (Exception e) { + throw new CloudRuntimeException("Failed to delete checkpoint: " + e.getMessage(), e); + } finally { + CallContext.unregister(); + } } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java index 4618aa2ae54..1b66b37e431 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java @@ -30,7 +30,6 @@ import org.apache.cloudstack.veeam.VeeamControlServlet; import org.apache.cloudstack.veeam.adapter.ServerAdapter; import org.apache.cloudstack.veeam.api.dto.Backup; import org.apache.cloudstack.veeam.api.dto.Checkpoint; -import org.apache.cloudstack.veeam.api.dto.Checkpoints; import org.apache.cloudstack.veeam.api.dto.Disk; import org.apache.cloudstack.veeam.api.dto.DiskAttachment; import org.apache.cloudstack.veeam.api.dto.DiskAttachments; @@ -208,7 +207,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { return; } else if ("checkpoints".equals(subPath)) { if ("DELETE".equalsIgnoreCase(method)) { - handleDeleteCheckpointById(subId, req, resp, outFormat, io); + handleDeleteCheckpoint(id, subId, resp, outFormat, io); } else { io.methodNotAllowed(resp, "DELETE", outFormat); } @@ -545,25 +544,19 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { try { List checkpoints = serverAdapter.listCheckpointsByInstanceUuid(id); - Checkpoints response = new Checkpoints(checkpoints); + NamedList response = NamedList.of("checkpoints", checkpoints); io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat); } catch (InvalidParameterValueException e) { io.notFound(resp, e.getMessage(), outFormat); } } - protected void handleDeleteCheckpointById(final String id, final HttpServletRequest req, + protected void handleDeleteCheckpoint(final String vmId, final String checkpointId, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { - String asyncStr = req.getParameter("async"); - boolean async = !Boolean.FALSE.toString().equals(asyncStr); try { - ResourceAction action = serverAdapter.deleteCheckpoint(id, async); - if (action != null) { - io.getWriter().write(resp, HttpServletResponse.SC_ACCEPTED, action, outFormat); - } else { - io.getWriter().write(resp, HttpServletResponse.SC_OK, null, outFormat); - } + serverAdapter.deleteCheckpoint(vmId, checkpointId); + io.getWriter().write(resp, HttpServletResponse.SC_OK, null, outFormat); } catch (CloudRuntimeException e) { io.badRequest(resp, e.getMessage(), outFormat); } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/HostJoinVOToHostConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/HostJoinVOToHostConverter.java index 6f4acbd4550..d627aa4d63f 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/HostJoinVOToHostConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/HostJoinVOToHostConverter.java @@ -66,7 +66,7 @@ public class HostJoinVOToHostConverter { // --- CPU --- final Cpu cpu = new Cpu(); - cpu.setSpeed(Math.toIntExact(vo.getSpeed())); + cpu.setSpeed(String.valueOf(Math.toIntExact(vo.getSpeed()))); final Topology topo = new Topology(vo.getCpuSockets(), vo.getCpus(), 1); cpu.setTopology(topo); h.setCpu(cpu); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/NicVOToNicConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/NicVOToNicConverter.java index 1eb5eaf29cb..7ccaf45e2fd 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/NicVOToNicConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/NicVOToNicConverter.java @@ -32,6 +32,7 @@ import org.apache.cloudstack.veeam.api.dto.Ref; import org.apache.cloudstack.veeam.api.dto.ReportedDevice; import org.apache.cloudstack.veeam.api.dto.ReportedDevices; import org.apache.cloudstack.veeam.api.dto.Vm; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; @@ -76,17 +77,19 @@ public class NicVOToNicConverter { device.setName("eth0"); device.setDescription(String.format("%s device", vo.getReserver())); device.setMac(mac); - Ip ip = new Ip(); - if (vo.getIPv4Address() != null) { - ip.setAddress(vo.getIPv4Address()); - ip.setGateway(vo.getIPv4Gateway()); - ip.setVersion("v4"); - } else if (vo.getIPv6Address() != null) { - ip.setAddress(vo.getIPv6Address()); - ip.setGateway(vo.getIPv6Gateway()); - ip.setVersion("v6"); + if (ObjectUtils.anyNotNull(vo.getIPv4Address(), vo.getIPv6Address())) { + Ip ip = new Ip(); + if (vo.getIPv4Address() != null) { + ip.setAddress(vo.getIPv4Address()); + ip.setGateway(vo.getIPv4Gateway()); + ip.setVersion("v4"); + } else if (vo.getIPv6Address() != null) { + ip.setAddress(vo.getIPv6Address()); + ip.setGateway(vo.getIPv6Gateway()); + ip.setVersion("v6"); + } + device.setIps(new Ips(List.of(ip))); } - device.setIps(new Ips(List.of(ip))); device.setHref(vm.getHref() + "/reporteddevices/" + vo.getUuid()); device.setVm(vm); return device; diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverter.java index 09e058a3eaa..03a7ead0cc1 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverter.java @@ -25,7 +25,6 @@ import java.util.stream.Collectors; import org.apache.cloudstack.veeam.VeeamControlService; import org.apache.cloudstack.veeam.api.ApiService; -import org.apache.cloudstack.veeam.api.JobsRouteHandler; import org.apache.cloudstack.veeam.api.VmsRouteHandler; import org.apache.cloudstack.veeam.api.dto.Actions; import org.apache.cloudstack.veeam.api.dto.BaseDto; @@ -40,7 +39,6 @@ import org.apache.cloudstack.veeam.api.dto.Os; import org.apache.cloudstack.veeam.api.dto.Ref; import org.apache.cloudstack.veeam.api.dto.Topology; import org.apache.cloudstack.veeam.api.dto.Vm; -import org.apache.cloudstack.veeam.api.dto.VmAction; import org.apache.commons.lang3.StringUtils; import com.cloud.api.query.vo.HostJoinVO; @@ -156,15 +154,6 @@ public final class UserVmJoinVOToVmConverter { .collect(Collectors.toList()); } - public static VmAction toVmAction(final UserVmJoinVO vm) { - VmAction action = new VmAction(); - final String basePath = VeeamControlService.ContextPath.value(); - action.setVm(toVm(vm, null, null, null)); - action.setJob(Ref.of(basePath + JobsRouteHandler.BASE_ROUTE + vm.getUuid(), vm.getUuid())); - action.setStatus("complete"); - return action; - } - private static String mapStatus(final VirtualMachine.State state) { // CloudStack-ish states -> oVirt-ish up/down if (Arrays.asList(VirtualMachine.State.Running, diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmVOToCheckpointConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmVOToCheckpointConverter.java new file mode 100644 index 00000000000..019bc8264c8 --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmVOToCheckpointConverter.java @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.veeam.api.converter; + +import java.time.Instant; + +import org.apache.cloudstack.veeam.api.dto.Checkpoint; +import org.apache.commons.lang3.StringUtils; + +import com.cloud.vm.UserVmVO; + +public class UserVmVOToCheckpointConverter { + + public static Checkpoint toCheckpoint(final UserVmVO vm) { + if (StringUtils.isEmpty(vm.getActiveCheckpointId())) { + return null; + } + Checkpoint checkpoint = new Checkpoint(); + checkpoint.setId(vm.getActiveCheckpointId()); + checkpoint.setName(vm.getActiveCheckpointId()); + Long createTimeSeconds = vm.getActiveCheckpointCreateTime(); + if (createTimeSeconds != null) { + checkpoint.setCreationDate(String.valueOf(Instant.ofEpochSecond(createTimeSeconds).toEpochMilli())); + } else { + checkpoint.setCreationDate(String.valueOf(System.currentTimeMillis())); + } + checkpoint.setState("created"); + return checkpoint; + } +} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Cpu.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Cpu.java index 97459b40cd8..c5cea76f33e 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Cpu.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Cpu.java @@ -22,15 +22,15 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public final class Cpu { private String name; - private Integer speed; + private String speed; private String architecture; private String type; private Topology topology; public String getName() { return name; } public void setName(String name) { this.name = name; } - public Integer getSpeed() { return speed; } - public void setSpeed(Integer speed) { this.speed = speed; } + public String getSpeed() { return speed; } + public void setSpeed(String speed) { this.speed = speed; } public String getArchitecture() { return architecture; } public void setArchitecture(String architecture) { this.architecture = architecture; } public String getType() { return type; } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/SummaryCount.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/SummaryCount.java index 280704f9b51..ac26619ff02 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/SummaryCount.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/SummaryCount.java @@ -22,19 +22,22 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public final class SummaryCount { - private Integer active; - private Integer total; + private String active; + private String total; - public SummaryCount(Integer active, Integer total) { - this.active = active; - this.total = total; + public SummaryCount() { } - public Integer getActive() { + public SummaryCount(Integer active, Integer total) { + this.active = String.valueOf(active); + this.total = String.valueOf(total); + } + + public String getActive() { return active; } - public Integer getTotal() { + public String getTotal() { return total; } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Topology.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Topology.java index 564df5b5304..fa20db9d658 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Topology.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Topology.java @@ -21,40 +21,40 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public final class Topology { - public Integer sockets; - public Integer cores; - public Integer threads; + public String sockets; + public String cores; + public String threads; public Topology() { } public Topology(final Integer sockets, final Integer cores, final Integer threads) { - this.sockets = sockets; - this.cores = cores; - this.threads = threads; + this.sockets = String.valueOf(sockets); + this.cores = String.valueOf(cores); + this.threads = String.valueOf(threads); } - public Integer getSockets() { + public String getSockets() { return sockets; } - public void setSockets(Integer sockets) { + public void setSockets(String sockets) { this.sockets = sockets; } - public Integer getCores() { + public String getCores() { return cores; } - public void setCores(Integer cores) { + public void setCores(String cores) { this.cores = cores; } - public Integer getThreads() { + public String getThreads() { return threads; } - public void setThreads(Integer threads) { + public void setThreads(String threads) { this.threads = threads; } }