From 14a2e8e2f2fcbed22b6967e2839b71c506f7999a Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 11 May 2026 17:58:58 +0530 Subject: [PATCH] fixes, sharedfs restore, restrict unsupported instances Signed-off-by: Abhishek Kumar --- .../command/admin/vm/DeployVMCmdByAdmin.java | 19 ++ .../command/admin/vm/DestroyVMCmdByAdmin.java | 20 +- .../api/command/user/vm/BaseDeployVMCmd.java | 6 +- .../api/command/user/vm/DeployVMCmd.java | 4 + .../api/command/user/vm/DestroyVMCmd.java | 4 + .../storage/sharedfs/SharedFSService.java | 15 +- .../storage/sharedfs/dao/SharedFSDao.java | 2 + .../storage/sharedfs/dao/SharedFSDaoImpl.java | 10 + .../veeam/adapter/ServerAdapter.java | 180 +++++++++++++++--- .../AsyncJobJoinVOToJobConverter.java | 2 +- .../converter/UserVmJoinVOToVmConverter.java | 30 ++- .../VolumeJoinVOToDiskConverter.java | 4 +- .../veeam/api/dto/DiskAttachment.java | 13 ++ .../cloudstack/veeam/api/dto/OvfXmlUtil.java | 57 +++++- .../apache/cloudstack/veeam/api/dto/Vm.java | 40 ++++ .../UserVmJoinVOToVmConverterTest.java | 3 +- .../cloud/api/query/dao/UserVmJoinDao.java | 5 +- .../api/query/dao/UserVmJoinDaoImpl.java | 8 +- .../cloud/storage/VolumeApiServiceImpl.java | 5 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 47 +++-- .../storage/sharedfs/SharedFSServiceImpl.java | 33 ++++ 21 files changed, 445 insertions(+), 62 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java index fb9501ff660..7d08dc667d6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java @@ -47,6 +47,13 @@ public class DeployVMCmdByAdmin extends DeployVMCmd implements AdminCmd { since = "4.23.0") private Boolean blankInstance; + // Internal flag to allow deploying instance with a given type + private String instanceType; + + ///////////////////////////////////////////////////// + ////////////////// Getters ////////////////////////// + ///////////////////////////////////////////////////// + public Long getPodId() { return podId; } @@ -60,6 +67,14 @@ public class DeployVMCmdByAdmin extends DeployVMCmd implements AdminCmd { return Boolean.TRUE.equals(blankInstance); } + @Override + public String getInstanceType() { + if (!isBlankInstance()) { + return null; + } + return instanceType; + } + ///////////////////////////////////////////////////// ////////////////// Setters ////////////////////////// ///////////////////////////////////////////////////// @@ -71,4 +86,8 @@ public class DeployVMCmdByAdmin extends DeployVMCmd implements AdminCmd { public void setBlankInstance(boolean blankInstance) { this.blankInstance = blankInstance; } + + public void setInstanceType(String instanceType) { + this.instanceType = instanceType; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java index 93d4b610b90..cbe6494d400 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.api.command.admin.vm; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.command.admin.AdminCmd; import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; @@ -27,4 +29,20 @@ import com.cloud.vm.VirtualMachine; @APICommand(name = "destroyVirtualMachine", description = "Destroys an Instance. Once destroyed, only the administrator can recover it.", responseObject = UserVmResponse.class, responseView = ResponseView.Full, entityType = {VirtualMachine.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) -public class DestroyVMCmdByAdmin extends DestroyVMCmd implements AdminCmd {} +public class DestroyVMCmdByAdmin extends DestroyVMCmd implements AdminCmd { + + @Parameter( name = ApiConstants.FORCED, + type = CommandType.BOOLEAN, + description = "Force destroy the Instance", + since = "4.23.0") + Boolean forced; + + @Override + public boolean isForced() { + return Boolean.TRUE.equals(forced); + } + + public void setForced(Boolean forced) { + this.forced = forced; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java index 11c1754677a..68b0821ba44 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java @@ -168,7 +168,7 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme @ACL @Parameter(name = ApiConstants.SECURITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = SecurityGroupResponse.class, description = "comma separated list of security groups id that going to be applied to the virtual machine. " + "Should be passed only when vm is created from a zone with Basic Network support." + " Mutually exclusive with securitygroupnames parameter") - private List securityGroupIdList; + protected List securityGroupIdList; @ACL @Parameter(name = ApiConstants.SECURITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = SecurityGroupResponse.class, description = "comma separated list of security groups names that going to be applied to the virtual machine." @@ -799,6 +799,10 @@ public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd impleme return null; } + public String getInstanceType() { + return null; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 4776d01d797..0a943fab118 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -191,6 +191,10 @@ public class DeployVMCmd extends BaseDeployVMCmd { this.sshKeyPairNames = sshKeyPairNames; } + public void setSecurityGroupList(List securityGroupIdList) { + this.securityGroupIdList = securityGroupIdList; + } + @Override public void execute() { UserVm result; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java index 9e2f2bcb72c..aec0688f177 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java @@ -90,6 +90,10 @@ public class DestroyVMCmd extends BaseAsyncCmd implements UserCmd { return volumeIds; } + public boolean isForced() { + return false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSService.java b/api/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSService.java index 21184de27a2..3e349cf0bd3 100644 --- a/api/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSService.java +++ b/api/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSService.java @@ -23,6 +23,10 @@ import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSDis import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSServiceOfferingCmd; import org.apache.cloudstack.api.command.user.storage.sharedfs.CreateSharedFSCmd; import org.apache.cloudstack.api.command.user.storage.sharedfs.DestroySharedFSCmd; +import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd; +import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.SharedFSResponse; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ManagementServerException; @@ -31,11 +35,6 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.VirtualMachineMigrationException; -import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd; -import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd; -import org.apache.cloudstack.api.response.SharedFSResponse; -import org.apache.cloudstack.api.response.ListResponse; - public interface SharedFSService { List getSharedFSProviders(); @@ -69,4 +68,10 @@ public interface SharedFSService { SharedFS recoverSharedFS(Long sharedFSId); void deleteSharedFS(Long sharedFSId); + + SharedFS getSharedFSByUuid(String uuid); + + SharedFS getSharedFSForVmId(long vmId); + + SharedFS updateSharedFSPostRestore(long sharedFsId, long volumeId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDao.java index 4735202a762..82ba9445b25 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDao.java @@ -29,4 +29,6 @@ public interface SharedFSDao extends GenericDao, StateDao listSharedFSToBeDestroyed(Date date); SharedFSVO findSharedFSByNameAccountDomain(String name, Long accountId, Long domainId); + + SharedFSVO findByVm(long vmId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java index da622071671..dd23787f982 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java @@ -114,4 +114,14 @@ public class SharedFSDaoImpl extends GenericDaoBase implements sc.setParameters("domainId", domainId); return findOneBy(sc); } + + @Override + public SharedFSVO findByVm(long vmId) { + SearchBuilder sb = createSearchBuilder(); + sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("vmId", vmId); + return findOneBy(sc); + } } 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 4d93fb3473a..d4abfb19d98 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 @@ -51,6 +51,7 @@ import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolsCmd; import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; import org.apache.cloudstack.api.command.admin.vm.DeployVMCmdByAdmin; +import org.apache.cloudstack.api.command.admin.vm.DestroyVMCmdByAdmin; import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; import org.apache.cloudstack.api.command.user.job.ListAsyncJobsCmd; import org.apache.cloudstack.api.command.user.network.ListNetworksCmd; @@ -92,6 +93,8 @@ import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.sharedfs.SharedFS; +import org.apache.cloudstack.storage.sharedfs.SharedFSService; import org.apache.cloudstack.veeam.VeeamControlService; import org.apache.cloudstack.veeam.api.converter.AsyncJobJoinVOToJobConverter; import org.apache.cloudstack.veeam.api.converter.BackupVOToBackupConverter; @@ -160,8 +163,12 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.hypervisor.Hypervisor; import com.cloud.network.NetworkModel; import com.cloud.network.Networks; +import com.cloud.network.as.AutoScaleVmGroupVmMapVO; +import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.network.security.SecurityGroupVO; +import com.cloud.network.security.dao.SecurityGroupDao; import com.cloud.offering.ServiceOffering; import com.cloud.org.Grouping; import com.cloud.projects.Project; @@ -333,6 +340,15 @@ public class ServerAdapter extends ManagerBase { @Inject GuestOSDao guestOSDao; + @Inject + SecurityGroupDao securityGroupDao; + + @Inject + SharedFSService sharedFSService; + + @Inject + AutoScaleVmGroupVmMapDao autoScaleVmGroupVmMapDao; + protected static Map getDummyTags() { Map tags = new HashMap<>(); Tag rootTag = ResourceTagVOToTagConverter.getRootTag(); @@ -582,11 +598,76 @@ public class ServerAdapter extends ManagerBase { return validatedNames; } + protected AffinityGroupVO getValidatedAffinityGroup(String affinityGroupUuid) { + if (StringUtils.isBlank(affinityGroupUuid)) { + return null; + } + AffinityGroupVO group = affinityGroupDao.findByUuid(affinityGroupUuid); + if (group == null) { + logger.warn("Failed to find affinity group with ID {} specified in Instance creation request, " + + "skipping affinity group assignment", affinityGroupUuid); + return null; + } + return group; + } + + protected UserDataVO getValidatedUserdata(String userdataUuid) { + if (StringUtils.isBlank(userdataUuid)) { + return null; + } + UserDataVO userDataVO = userDataDao.findByUuid(userdataUuid); + if (userDataVO == null) { + logger.warn("Failed to find userdata with ID {} specified in Instance creation request, " + + "skipping userdata assignment", userdataUuid); + return null; + } + return userDataVO; + } + + protected SecurityGroupVO getValidatedSecurityGroup(String securityGroupUuid) { + if (StringUtils.isBlank(securityGroupUuid)) { + return null; + } + SecurityGroupVO group = securityGroupDao.findByUuid(securityGroupUuid); + if (group == null) { + logger.warn("Failed to find userdata with ID {} specified in Instance creation request, " + + "skipping security group assignment", securityGroupUuid); + return null; + } + return group; + } + + protected String getValidatedInstanceType(Vm request) { + String instanceType = StringUtils.trimToNull(request.getInstanceType()); + if (StringUtils.isEmpty(request.getInstanceType())) { + return null; + } + if (!UserVmManager.SHAREDFSVM.equals(instanceType)) { + logger.warn("{} is not supported for restore, returning null Instance type"); + return null; + } + if (StringUtils.isBlank(request.getSharedFSId())) { + logger.warn("Shared Filesystem ID not available, returning null Instance type"); + return null; + } + SharedFS sharedFS = sharedFSService.getSharedFSByUuid(request.getSharedFSId()); + if (sharedFS == null) { + logger.warn("Shared Filesystem for ID: {} not found, returning null Instance type", request.getSharedFSId()); + return null; + } + UserVmVO existingVm = userVmDao.findById(sharedFS.getVmId()); + if (existingVm != null) { + logger.error("{} already has a {}, returning null Instance type", sharedFS, existingVm); + return null; + } + return instanceType; + } + protected Pair createInstance(com.cloud.dc.DataCenter zone, Long clusterId, Account owner, Long domainId, String accountName, Long projectId, String name, String displayName, String serviceOfferingUuid, int cpu, int memory, String templateUuid, GuestOS guestOs, String userdata, ApiConstants.BootType bootType, ApiConstants.BootMode bootMode, String affinityGroupId, String userDataId, String sshKeyPairNames, - Map details) { + String instanceType, String securityGroupId, Map details) { Account account = owner != null ? owner : CallContext.current().getCallingAccount(); ServiceOffering serviceOffering = getServiceOfferingIdForVmCreation(zone, account, serviceOfferingUuid, cpu, memory); @@ -625,27 +706,22 @@ public class ServerAdapter extends ManagerBase { } else if (guestOs != null) { CallContext.current().putContextParameter(ApiConstants.OS_ID, guestOs); } - if (StringUtils.isNotBlank(affinityGroupId)) { - AffinityGroupVO group = affinityGroupDao.findByUuid(affinityGroupId); - if (group == null) { - logger.warn("Failed to find affinity group with ID {} specified in Instance creation request, " + - "skipping affinity group assignment", affinityGroupId); - } else { - cmd.setAffinityGroupIds(List.of(group.getId())); - } + AffinityGroupVO group = getValidatedAffinityGroup(affinityGroupId); + if (group != null) { + cmd.setAffinityGroupIds(List.of(group.getId())); } - if (StringUtils.isNotBlank(userDataId)) { - UserDataVO userData = userDataDao.findByUuid(userDataId); - if (userData == null) { - logger.warn("Failed to find userdata with ID {} specified in Instance creation request, " + - "skipping userdata assignment", userDataId); - } else { - cmd.setUserDataId(userData.getId()); - } + UserDataVO userData = getValidatedUserdata(userDataId); + if (userData != null) { + cmd.setUserDataId(userData.getId()); + } + SecurityGroupVO securityGroup = getValidatedSecurityGroup(securityGroupId); + if (securityGroup != null) { + cmd.setSecurityGroupList(List.of(securityGroup.getId())); } if (StringUtils.isNotBlank(sshKeyPairNames)) { cmd.setSshKeyPairNames(getValidatedSshKeyPairNames(sshKeyPairNames, owner)); } + cmd.setInstanceType(StringUtils.trimToNull(instanceType)); cmd.setHypervisor(Hypervisor.HypervisorType.KVM.name()); Map instanceDetails = getDetailsForInstanceCreation(userdata, serviceOffering, details); if (MapUtils.isNotEmpty(instanceDetails)) { @@ -660,7 +736,7 @@ public class ServerAdapter extends ManagerBase { UserVmJoinVO vo = userVmJoinDao.findById(vm.getId()); Vm vmObj = UserVmJoinVOToVmConverter.toVm(vo, this::getHostById, this::getDetailsByInstanceId, this::listTagsByInstanceId, this::listDiskAttachmentsByInstanceId, this::listNicsByInstance, - false); + null, false); return new Pair<>(vmObj, vm); } catch (InsufficientCapacityException | ResourceUnavailableException | ResourceAllocationException | CloudRuntimeException e) { throw new CloudRuntimeException("Failed to create VM: " + e.getMessage(), e); @@ -714,6 +790,39 @@ public class ServerAdapter extends ManagerBase { vmInstanceDetailsDao.removeDetail(vm.getId(), RESTORE_CONFIG); } + protected void processInstanceRestoreConfigIfNeeded(UserVm userVm, Volume volume) { + VMInstanceDetailVO detail = vmInstanceDetailsDao.findDetail(userVm.getId(), RESTORE_CONFIG); + if (detail == null) { + return; + } + String config = detail.getValue(); + if (StringUtils.isAnyBlank(userVm.getUserVmType(), config)) { + removeInstanceRestoreConfig(userVm); + return; + } + Vm vm = OvfXmlUtil.parseVmRestoreConfig(config, logger); + if (StringUtils.isAnyBlank(vm.getSharedFSId(), vm.getSharedFsVolumeName())) { + removeInstanceRestoreConfig(userVm); + return; + } + if (!vm.getSharedFsVolumeName().equals(volume.getName())) { + return; + } + removeInstanceRestoreConfig(userVm); + SharedFS sharedFS = sharedFSService.getSharedFSByUuid(vm.getSharedFSId()); + if (sharedFS == null) { + logger.warn("Shared Filesystem with ID {} specified in the restore config for {} not found, unable to restore Instance for Shared Filesystem", + vm.getSharedFSId(), userVm); + return; + } + UserVm existingVm = userVmDao.findById(sharedFS.getId()); + if (existingVm != null) { + logger.error("{} specified in the restore config for {} is already associated with {}, unable to restore Instance for Shared Filesystem", + sharedFS, userVm, existingVm); + } + sharedFSService.updateSharedFSPostRestore(sharedFS.getId(), volume.getId()); + } + protected Pair getValidatedInstanceNicDetails(final UserVmVO vm, final NetworkVO network) { if (ObjectUtils.anyNull(vm, network)) { return new Pair<>(null, null); @@ -881,6 +990,20 @@ public class ServerAdapter extends ManagerBase { return vmInstanceDetailsDao.listDetailsKeyPairs(instanceId, true); } + protected SharedFS getSharedFSForInstance(UserVmJoinVO vo) { + if (vo == null || !UserVmManager.SHAREDFSVM.equals(vo.getUserVmType())) { + return null; + } + return sharedFSService.getSharedFSForVmId(vo.getId()); + } + + protected void validateInstanceBackupConditions(UserVm vm) { + List asGroupVmVOs = autoScaleVmGroupVmMapDao.listByVm(vm.getId()); + if (CollectionUtils.isNotEmpty(asGroupVmVOs)) { + throw new CloudRuntimeException("Instance is part of an AutoScale group, unable to proceed with backup"); + } + } + public Pair getServiceAccount() { String serviceAccountUuid = VeeamControlService.ServiceAccountId.value(); if (StringUtils.isEmpty(serviceAccountUuid)) { @@ -1016,14 +1139,15 @@ public class ServerAdapter extends ManagerBase { boolean allContent, Long offset, Long limit) { Filter filter = new Filter(UserVmJoinVO.class, "id", true, offset, limit); Pair, String> ownerDetails = getResourceOwnerFilters(); - List vms = userVmJoinDao.listByHypervisorTypeAndOwners(Hypervisor.HypervisorType.KVM, - ownerDetails.first(), ownerDetails.second(), filter); + List vms = userVmJoinDao.listByHypervisorNotTypesAndOwners(Hypervisor.HypervisorType.KVM, + Arrays.asList(UserVmManager.CKS_NODE), ownerDetails.first(), ownerDetails.second(), filter); return UserVmJoinVOToVmConverter.toVmList(vms, this::getHostById, this::getDetailsByInstanceId, includeTags ? this::listTagsByInstanceId : null, includeDisks ? this::listDiskAttachmentsByInstanceId : null, includeNics ? this::listNicsByInstance : null, + allContent ? this::getSharedFSForInstance: null, allContent); } @@ -1040,6 +1164,7 @@ public class ServerAdapter extends ManagerBase { includeTags ? this::listTagsByInstanceId : null, includeDisks ? this::listDiskAttachmentsByInstanceId : null, includeNics ? this::listNicsByInstance : null, + allContent ? this::getSharedFSForInstance : null, allContent); } @@ -1104,10 +1229,12 @@ public class ServerAdapter extends ManagerBase { templateUuid = request.getTemplate().getId(); } GuestOS guestOs = getGuestOsForInstance(request, StringUtils.isNotEmpty(userdata)); + String instanceType = getValidatedInstanceType(request); Pair result = createInstance(zone, clusterId, owner, ownerDetails.first(), ownerDetails.second(), ownerDetails.third(), name, displayName, serviceOfferingUuid, cpu, memoryMB, templateUuid, guestOs, userdata, bootOptions.first(), bootOptions.second(), request.getAffinityGroupId(), - request.getUserDataId(), request.getSshKeyPairNames(), request.getDetails()); + request.getUserDataId(), request.getSshKeyPairNames(), instanceType, + request.getSecurityGroupId(), request.getDetails()); saveInstanceRestoreConfig(request, result.second()); return result.first(); } @@ -1124,13 +1251,17 @@ public class ServerAdapter extends ManagerBase { if (vo == null) { throw new InvalidParameterValueException("VM with ID " + uuid + " not found"); } + boolean isAdmin = accountService.isRootAdmin(CallContext.current().getCallingAccountId()); try { - DestroyVMCmd cmd = new DestroyVMCmd(); + DestroyVMCmd cmd = isAdmin ? new DestroyVMCmdByAdmin() : new DestroyVMCmd(); cmd.setHttpMethod(BaseCmd.HTTPMethod.POST.name()); ComponentContext.inject(cmd); Map params = new HashMap<>(); params.put(ApiConstants.ID, vo.getUuid()); params.put(ApiConstants.EXPUNGE, Boolean.TRUE.toString()); + if (isAdmin) { + params.put(ApiConstants.FORCED, Boolean.TRUE.toString()); + } ApiServerService.AsyncCmdResult result = processAsyncCmdWithContext(cmd, params); AsyncJobJoinVO jobVo = asyncJobJoinDao.findById(result.jobId); if (jobVo == null) { @@ -1313,7 +1444,6 @@ public class ServerAdapter extends ManagerBase { } accountService.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, vmVo); - removeInstanceRestoreConfig(vmVo); if (vmVo.getAccountId() != volumeVO.getAccountId()) { if (VeeamControlService.InstanceRestoreAssignOwner.value()) { assignVolumeToAccount(volumeVO, vmVo.getAccountId()); @@ -1326,7 +1456,8 @@ public class ServerAdapter extends ManagerBase { if (Boolean.parseBoolean(request.getBootable()) || Volume.Type.ROOT.equals(volumeVO.getVolumeType())) { deviceId = 0L; } - Volume volume = volumeApiService.attachVolumeToVM(vmVo.getId(), volumeVO.getId(), deviceId, false); + Volume volume = volumeApiService.attachVolumeToVM(vmVo.getId(), volumeVO.getId(), deviceId, true); + processInstanceRestoreConfigIfNeeded(vmVo, volume); VolumeJoinVO attachedVolumeVO = volumeJoinDao.findById(volume.getId()); return VolumeJoinVOToDiskConverter.toDiskAttachment(attachedVolumeVO, this::getVolumePhysicalSize); } @@ -1699,6 +1830,7 @@ public class ServerAdapter extends ManagerBase { } accountService.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, vmVo); + validateInstanceBackupConditions(vmVo); validateInstanceStorage(vmVo); try { StartBackupCmd cmd = new StartBackupCmd(); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java index 8eae3d2cce2..fe10673efd5 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java @@ -78,7 +78,7 @@ public class AsyncJobJoinVOToJobConverter { public static VmAction toVmAction(final AsyncJobJoinVO vo, final UserVmJoinVO vm) { VmAction action = new VmAction(); fillAction(action, vo); - action.setVm(UserVmJoinVOToVmConverter.toVm(vm, null, null, null, null, null, false)); + action.setVm(UserVmJoinVOToVmConverter.toVm(vm, null, null, null, null, null, null, false)); return action; } 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 7732901fd5b..827af1e8e97 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 @@ -21,10 +21,12 @@ import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.veeam.VeeamControlService; import org.apache.cloudstack.veeam.api.ApiRouteHandler; import org.apache.cloudstack.veeam.api.VmsRouteHandler; @@ -61,6 +63,7 @@ public final class UserVmJoinVOToVmConverter { final Function> tagsResolver, final Function> disksResolver, final Function> nicsResolver, + final Function sharedFsResolver, final boolean allContent) { if (src == null) { return null; @@ -173,8 +176,11 @@ public final class UserVmJoinVOToVmConverter { dst.setSshKeyPairNames(src.getKeypairNames()); dst.setGuestOsId(src.getGuestOsUuid()); dst.setGuestOsName(src.getGuestOsDisplayName()); + dst.setInstanceType(src.getUserVmType()); + updateSharedFSDetailsIfNeeded(src, sharedFsResolver, dst); + dst.setSecurityGroupId(src.getSecurityGroupUuid()); - // Keep at last + // Keep at end if (allContent) { dst.setInitialization(getOvfInitialization(dst, src)); } @@ -182,6 +188,24 @@ public final class UserVmJoinVOToVmConverter { return dst; } + private static void updateSharedFSDetailsIfNeeded(UserVmJoinVO src, Function sharedFsResolver, Vm dst) { + if (sharedFsResolver == null || dst.getDiskAttachments() == null) { + return; + } + SharedFS sharedFS = sharedFsResolver.apply(src); + if (sharedFS == null) { + return; + } + Optional disk = dst.getDiskAttachments().getItems() + .stream() + .filter(d -> d.getInternalId() == sharedFS.getVolumeId()) + .findFirst(); + disk.ifPresent(diskAttachment -> { + dst.setSharedFSId(sharedFS.getUuid()); + dst.setSharedFsVolumeName(diskAttachment.getLogicalName()); + }); + } + private static Vm.Initialization getOvfInitialization(Vm vm, UserVmJoinVO vo) { final Vm.Initialization.Configuration configuration = new Vm.Initialization.Configuration(); configuration.setType("ovf"); @@ -198,9 +222,11 @@ public final class UserVmJoinVOToVmConverter { final Function> tagsResolver, final Function> disksResolver, final Function> nicsResolver, + final Function sharedFsResolver, final boolean allContent) { return srcList.stream() - .map(v -> toVm(v, hostResolver, detailsResolver, tagsResolver, disksResolver, nicsResolver, allContent)) + .map(v -> toVm(v, hostResolver, detailsResolver, tagsResolver, disksResolver, + nicsResolver, sharedFsResolver, allContent)) .collect(Collectors.toList()); } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java index af92e7a10f2..97eebc40340 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java @@ -153,11 +153,13 @@ public class VolumeJoinVOToDiskConverter { // Properties da.setActive("true"); da.setBootable(String.valueOf(Volume.Type.ROOT.equals(vol.getVolumeType()))); - da.setIface("virtio_scsi"); + da.setIface("virtio"); da.setLogicalName(vol.getName()); da.setReadOnly("false"); da.setPassDiscard("false"); + da.setInternalId(vol.getId()); + return da; } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/DiskAttachment.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/DiskAttachment.java index f22168342e3..6b3518dc8e7 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/DiskAttachment.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/DiskAttachment.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.veeam.api.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -34,6 +35,9 @@ public final class DiskAttachment extends BaseDto { private Disk disk; private Vm vm; + // Internal properties + private long internalId; + public DiskAttachment() { } @@ -108,4 +112,13 @@ public final class DiskAttachment extends BaseDto { public void setVm(Vm vm) { this.vm = vm; } + + @JsonIgnore + public long getInternalId() { + return internalId; + } + + public void setInternalId(long internalId) { + this.internalId = internalId; + } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtil.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtil.java index 06ba47c1375..8c37cf9b0ee 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtil.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtil.java @@ -44,6 +44,7 @@ import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; +import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; @@ -68,6 +69,7 @@ public class OvfXmlUtil { sdf.setTimeZone(UTC); return sdf; }); + private static final org.slf4j.Logger log = LoggerFactory.getLogger(OvfXmlUtil.class); protected enum MemoryAllocationUnit { Bytes("byte", 1), @@ -478,6 +480,16 @@ public class OvfXmlUtil { if (StringUtils.isNotBlank(vm.getGuestOsName())) { sb.append("").append(escapeText(vm.getGuestOsName())).append(""); } + if (StringUtils.isNotBlank(vm.getInstanceType())) { + sb.append("").append(escapeText(vm.getInstanceType())).append(""); + } + if (StringUtils.isNoneBlank(vm.getSharedFSId(), vm.getSharedFsVolumeName())) { + sb.append("").append(escapeText(vm.getSharedFSId())).append(""); + sb.append("").append(escapeText(vm.getSharedFsVolumeName())).append(""); + } + if (StringUtils.isNotBlank(vm.getSecurityGroupId())) { + sb.append("").append(escapeText(vm.getSecurityGroupId())).append(""); + } sb.append(""); sb.append(""); } @@ -583,6 +595,33 @@ public class OvfXmlUtil { return new Pair<>(null, null); } + public static Vm parseVmRestoreConfig(String xmlConfig, Logger logger) { + Vm vm = new Vm(); + if (StringUtils.isBlank(xmlConfig)) { + logger.error("No XML configuration provided for VM restore"); + return vm; + } + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(new ByteArrayInputStream(xmlConfig.getBytes(StandardCharsets.UTF_8))); + + XPathFactory xpf = XPathFactory.newInstance(); + XPath xpath = xpf.newXPath(); + Node metadataSection = (Node) xpath.evaluate( + "//*[local-name()='Section' and @*[local-name()='type']='ovf:CloudStackMetadata_Type']", + doc, + XPathConstants.NODE + ); + updateFromXmlCloudStackMetadataSection(vm, metadataSection, xpath); + } catch (ParserConfigurationException | XPathExpressionException | IOException | SAXException e) { + logger.error("Failed to parse VM configuration XML for restore: {}", e.getMessage()); + } + return vm; + } + private static String nodeToString(Node node) { try { // Implementation using string manipulation @@ -783,6 +822,22 @@ public class OvfXmlUtil { if (StringUtils.isNotBlank(guestOsName)) { vm.setGuestOsId(guestOsName); } + String instanceType = xpathString(xpath, metadataSection, ".//*[local-name()='Type']/text()"); + if (StringUtils.isNotBlank(instanceType)) { + vm.setInstanceType(instanceType); + } + String sharedFSId = xpathString(xpath, metadataSection, ".//*[local-name()='SharedFSId']/text()"); + if (StringUtils.isNotBlank(sharedFSId)) { + vm.setSharedFSId(sharedFSId); + } + String sharedFSVolumeName = xpathString(xpath, metadataSection, ".//*[local-name()='SharedFSVolumeName']/text()"); + if (StringUtils.isNotBlank(sharedFSVolumeName)) { + vm.setSharedFsVolumeName(sharedFSVolumeName); + } + String securityGroupId = xpathString(xpath, metadataSection, ".//*[local-name()='SecurityGroupId']/text()"); + if (StringUtils.isNotBlank(securityGroupId)) { + vm.setInstanceType(securityGroupId); + } final Map details = new HashMap<>(); try { NodeList detailNodes = (NodeList) xpath.evaluate( @@ -918,7 +973,7 @@ public class OvfXmlUtil { private static String mapDiskInterface(String iface) { if (StringUtils.isBlank(iface)) { - return "VirtIO_SCSI"; + return "VirtIO"; } String v = iface.toLowerCase(Locale.ROOT); if (v.contains("virtio") && v.contains("scsi")) { diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java index b5481f608ff..1b4d89f8357 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java @@ -86,6 +86,10 @@ public final class Vm extends BaseDto { private String sshKeyPairNames; private String guestOsId; private String guestOsName; + private String instanceType; + private String sharedFSId; + private String sharedFsVolumeName; + private String securityGroupId; public String getName() { return name; @@ -358,6 +362,42 @@ public final class Vm extends BaseDto { this.guestOsName = guestOsName; } + @JsonIgnore + public String getInstanceType() { + return instanceType; + } + + public void setInstanceType(String instanceType) { + this.instanceType = instanceType; + } + + @JsonIgnore + public String getSharedFSId() { + return sharedFSId; + } + + public void setSharedFSId(String sharedFSId) { + this.sharedFSId = sharedFSId; + } + + @JsonIgnore + public String getSharedFsVolumeName() { + return sharedFsVolumeName; + } + + public void setSharedFsVolumeName(String sharedFsVolumeName) { + this.sharedFsVolumeName = sharedFsVolumeName; + } + + @JsonIgnore + public String getSecurityGroupId() { + return securityGroupId; + } + + public void setSecurityGroupId(String securityGroupId) { + this.securityGroupId = securityGroupId; + } + @JsonInclude(JsonInclude.Include.NON_NULL) public static final class Bios { diff --git a/plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverterTest.java b/plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverterTest.java index eb7442750ea..6f66854bdf9 100644 --- a/plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverterTest.java +++ b/plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverterTest.java @@ -62,7 +62,7 @@ public class UserVmJoinVOToVmConverterTest { when(src.getAffinityGroupUuid()).thenReturn("ag-1"); when(src.getUserDataUuid()).thenReturn("ud-1"); - final Vm vm = UserVmJoinVOToVmConverter.toVm(src, null, null, null, null, null, false); + final Vm vm = UserVmJoinVOToVmConverter.toVm(src, null, null, null, null, null, null, false); assertEquals("vm-1", vm.getId()); assertEquals("vm-1-name", vm.getName()); @@ -122,6 +122,7 @@ public class UserVmJoinVOToVmConverterTest { id -> List.of(tag), id -> List.of(disk), ignored -> List.of(nic), + null, false); assertEquals("down", vm.getStatus()); diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java index 0612e906666..1d9b24852c9 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDao.java @@ -52,6 +52,7 @@ public interface UserVmJoinDao extends GenericDao { List listLeaseInstancesExpiringInDays(int days); - List listByHypervisorTypeAndOwners(Hypervisor.HypervisorType hypervisorType, List accountIds, - String domainPath, Filter filter); + List listByHypervisorNotTypesAndOwners(Hypervisor.HypervisorType hypervisorType, + List excludeTypes, List accountIds, + String domainPath, Filter filter); } diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index bfa5f2c6cd9..d11fa092138 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -845,10 +845,11 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation listByHypervisorTypeAndOwners(Hypervisor.HypervisorType hypervisorType, - List accountIds, String domainPath, Filter filter) { + public List listByHypervisorNotTypesAndOwners(Hypervisor.HypervisorType hypervisorType, + List excludeTypes, List accountIds, String domainPath, Filter filter) { SearchBuilder sb = createSearchBuilder(); sb.and("hypervisorType", sb.entity().getHypervisorType(), Op.EQ); + sb.and("type", sb.entity().getUserVmType(), Op.NOTIN); boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds); boolean domainPathNotBlank = StringUtils.isNotBlank(domainPath); if (accountIdsNotEmpty || domainPathNotBlank) { @@ -859,6 +860,9 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation sc = sb.create(); sc.setParameters("hypervisorType", hypervisorType); + if (CollectionUtils.isNotEmpty(excludeTypes)) { + sc.setParameters("type", excludeTypes.toArray()); + } if (accountIdsNotEmpty) { sc.setParameters("account", accountIds.toArray()); } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 0dfb7bede3d..eda5b66dae4 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -219,10 +219,10 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.fsm.StateMachine2; import com.cloud.vm.DiskProfile; -import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmService; import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; @@ -241,14 +241,13 @@ import com.cloud.vm.VmWorkResizeVolume; import com.cloud.vm.VmWorkSerializer; import com.cloud.vm.VmWorkTakeVolumeSnapshot; import com.cloud.vm.dao.UserVmDao; -import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.snapshot.VMSnapshotDetailsVO; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index eec72e0b032..e73342db8e0 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -3567,6 +3567,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir CallContext ctx = CallContext.current(); long vmId = cmd.getId(); boolean expunge = cmd.getExpunge(); + boolean forced = cmd.isForced(); if (expunge) { String jobParamsString = ((AsyncJobVO) cmd.getJob()).getCmdInfo(); @@ -3581,26 +3582,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (vm == null || vm.getRemoved() != null) { throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); } - if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) { - throw new InvalidParameterValueException("Operation not supported on Shared FileSystem Instance"); - } if (Arrays.asList(State.Destroyed, State.Expunging).contains(vm.getState()) && !expunge) { logger.debug("Vm {} is already destroyed", vm); return vm; } - if (vm.isDeleteProtection()) { - throw new InvalidParameterValueException(String.format( - "Instance [id = %s, name = %s] has delete protection enabled and cannot be deleted.", - vm.getUuid(), vm.getName())); - } - - // check if vm belongs to AutoScale vm group in Disabled state - autoScaleManager.checkIfVmActionAllowed(vmId); - - // check if vm belongs to any plugin resources - checkPluginsIfVmCanBeDestroyed(vm); + validateVmDestroyAllowed(vm, forced); // check if there are active volume snapshots tasks logger.debug("Checking if there are any ongoing Snapshots on the ROOT volumes associated with Instance {}", vm); @@ -3659,6 +3647,26 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return destroyedVm; } + private void validateVmDestroyAllowed(UserVmVO vm, boolean forced) { + if (forced) { + return; + } + if (UserVmManager.SHAREDFSVM.equals(vm.getUserVmType())) { + throw new InvalidParameterValueException("Operation not supported on Shared FileSystem Instance"); + } + if (vm.isDeleteProtection()) { + throw new InvalidParameterValueException(String.format( + "Instance [id = %s, name = %s] has delete protection enabled and cannot be deleted.", + vm.getUuid(), vm.getName())); + } + + // check if vm belongs to AutoScale vm group in Disabled state + autoScaleManager.checkIfVmActionAllowed(vm.getId()); + + // check if vm belongs to any plugin resources + checkPluginsIfVmCanBeDestroyed(vm); + } + private List getVolumesFromIds(DestroyVMCmd cmd) { List volumes = new ArrayList<>(); if (cmd.getVolumeIds() != null) { @@ -4487,7 +4495,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } if (TemplateType.SYSTEM.equals(template.getTemplateType()) && !CKS_NODE.equals(vmType) && - !SHAREDFSVM.equals(vmType) && !_itMgr.isBlankInstanceDefaultTemplate(template)) { + !SHAREDFSVM.equals(vmType) && !_itMgr.isBlankInstance(template)) { throw new InvalidParameterValueException(String.format("Unable to use system template %s to deploy a user vm", template)); } @@ -6416,7 +6424,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private void verifyTemplate(BaseDeployVMCmd cmd, VirtualMachineTemplate template, Long serviceOfferingId) { if (TemplateType.VNF.equals(template.getTemplateType())) { - vnfTemplateManager.validateVnfApplianceNics(template, cmd.getNetworkIds(), cmd.getVmNetworkMap()); + if (!_itMgr.isBlankInstance(template)) { + vnfTemplateManager.validateVnfApplianceNics(template, cmd.getNetworkIds(), cmd.getVmNetworkMap()); + } } else if (cmd instanceof DeployVnfApplianceCmd) { throw new InvalidParameterValueException("Can't deploy VNF appliance from a non-VNF template"); } @@ -6604,6 +6614,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir String keyboard = cmd.getKeyboard(); Map dataDiskTemplateToDiskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap(); Map userVmOVFProperties = cmd.getVmProperties(); + final String instanceType = cmd.getInstanceType(); if (zone.getNetworkType() == NetworkType.Basic) { if (networkIds != null) { throw new InvalidParameterValueException("Can't specify network Ids in Basic zone"); @@ -6619,7 +6630,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, diskOfferingId, size, dataDiskInfoList, group, hypervisor, cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, ipToNetworkMap, addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), - dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId, null, volume, snapshot); + dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId, instanceType, volume, snapshot); } else { if (cmd.getSecurityGroupIdList() != null && !cmd.getSecurityGroupIdList().isEmpty()) { @@ -6627,7 +6638,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, dataDiskInfoList, group, hypervisor, cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, ipToNetworkMap, addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), - cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId, volume, snapshot); + cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, instanceType, overrideDiskOfferingId, volume, snapshot); if (cmd instanceof DeployVnfApplianceCmd) { vnfTemplateManager.createIsolatedNetworkRulesForVnfAppliance(zone, template, owner, vm, (DeployVnfApplianceCmd) cmd); } diff --git a/server/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSServiceImpl.java b/server/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSServiceImpl.java index 4f0aabd3f37..2548752cb68 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSServiceImpl.java @@ -660,6 +660,39 @@ public class SharedFSServiceImpl extends ManagerBase implements SharedFSService, sharedFSDao.remove(sharedFS.getId()); } + @Override + public SharedFS getSharedFSByUuid(String uuid) { + return sharedFSDao.findByUuid(uuid); + } + + @Override + public SharedFS getSharedFSForVmId(long vmId) { + return sharedFSDao.findByVm(vmId); + } + + public SharedFS updateSharedFSPostRestore(long sharedFsId, long volumeId) { + SharedFSVO sharedFS = sharedFSDao.findById(sharedFsId); + if (sharedFS == null) { + throw new CloudRuntimeException("Unable to find the Shared FileSystem"); + } + VolumeVO volume = volumeDao.findById(volumeId); + if (volume == null) { + throw new CloudRuntimeException("Unable to find the Volume"); + } + if (volume.getInstanceId() == null) { + throw new CloudRuntimeException("Volume is not attached to any Instance"); + } + if (sharedFS.getAccountId() != volume.getAccountId() || sharedFS.getDomainId() != volume.getDomainId()) { + throw new CloudRuntimeException("Shared FileSystem and the Volume do not belong to the same account"); + } + sharedFS.setVolumeId(volume.getId()); + sharedFS.setVmId(volume.getInstanceId()); + if (!sharedFSDao.update(sharedFS.getId(), sharedFS)) { + throw new CloudRuntimeException("Failed to update Shared FileSystem with the restored Volume information"); + } + return sharedFS; + } + @Override public String getConfigComponentName() { return SharedFSService.class.getSimpleName();