diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java index 66a558abf98..f6e811da605 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java @@ -77,6 +77,10 @@ public class DetachVolumeCmd extends BaseAsyncCmd implements UserCmd { return virtualMachineId; } + public void setId(Long id) { + this.id = id; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// 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 0a31b969e55..6ce56fb2210 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 @@ -73,6 +73,7 @@ import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd; import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; import org.apache.cloudstack.api.command.user.volume.DestroyVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd; import org.apache.cloudstack.api.command.user.volume.UpdateVolumeCmd; import org.apache.cloudstack.api.command.user.zone.ListZonesCmd; @@ -198,6 +199,7 @@ import com.cloud.vm.NicVO; import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceDetailVO; +import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; @@ -1265,6 +1267,30 @@ public class ServerAdapter extends ManagerBase { return VolumeJoinVOToDiskConverter.toDiskAttachment(attachedVolumeVO, this::getVolumePhysicalSize); } + @ApiAccess(command = DetachVolumeCmd.class) + public void detachInstanceDisk(final String vmUuid, final String volumeUuid) { + UserVmVO vmVo = userVmDao.findByUuid(vmUuid); + if (vmVo == null) { + throw new InvalidParameterValueException("VM with ID " + vmUuid + " not found"); + } + if (!VirtualMachine.State.Stopped.equals(vmVo.getState())) { + throw new InvalidParameterValueException("VM with ID " + vmUuid + " must be in stopped state to detach disk"); + } + accountService.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, + false, vmVo); + VolumeVO volumeVo = volumeDao.findByUuid(volumeUuid); + if (volumeVo == null) { + throw new InvalidParameterValueException("Volume with ID " + volumeUuid + " not found"); + } + if (volumeVo.getInstanceId() != vmVo.getId()) { + throw new InvalidParameterValueException("Volume with ID " + volumeUuid + " is not attached to VM with ID " + vmUuid); + } + DetachVolumeCmd cmd = new DetachVolumeCmd(); + ComponentContext.inject(cmd); + cmd.setId(volumeVo.getId()); + volumeApiService.detachVolumeFromVM(cmd); + } + @ApiAccess(command = CreateVolumeCmd.class) public Disk createDisk(Disk request) { if (request == null) { 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 2a3e3a17ae4..07d719d987d 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 @@ -19,6 +19,7 @@ package org.apache.cloudstack.veeam.api; import java.io.IOException; import java.util.List; +import java.util.Map; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; @@ -174,7 +175,14 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { } else if (idAndSubPath.size() == 3) { String subPath = idAndSubPath.get(1); String subId = idAndSubPath.get(2); - if ("snapshots".equals(subPath)) { + if ("diskattachments".equals(subPath)) { + if (!"DELETE".equalsIgnoreCase(method)) { + io.methodNotAllowed(resp, "DELETE", outFormat); + } else { + handleDeleteDiskAttachmentForVmId(id, subId, req, resp, outFormat, io); + } + return; + } else if ("snapshots".equals(subPath)) { if (!"GET".equalsIgnoreCase(method) && !"DELETE".equalsIgnoreCase(method)) { io.methodNotAllowed(resp, "GET, DELETE", outFormat); } else if ("GET".equalsIgnoreCase(method)) { @@ -410,6 +418,24 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { } } + protected void handleDeleteDiskAttachmentForVmId(final String vmId, final String diskId, + final HttpServletRequest req, final HttpServletResponse resp, + final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { + boolean delete = Boolean.FALSE.toString().equals(req.getParameter("detach_only")); + try { + serverAdapter.detachInstanceDisk(vmId, diskId); + if (delete) { + serverAdapter.deleteDisk(diskId); + } + Map response = Map.of("status", "complete"); + io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat); + } catch (InvalidParameterValueException e) { + io.notFound(resp, e.getMessage(), outFormat); + } catch (CloudRuntimeException e) { + io.badRequest(resp, e.getMessage(), outFormat); + } + } + protected void handleGetSnapshotById(final String id, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException { try {