fixes, sharedfs restore, restrict unsupported instances

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-05-11 17:58:58 +05:30
parent bc7ec163f3
commit 14a2e8e2f2
21 changed files with 445 additions and 62 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Long> securityGroupIdList;
protected List<Long> 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///////////////////
/////////////////////////////////////////////////////

View File

@ -191,6 +191,10 @@ public class DeployVMCmd extends BaseDeployVMCmd {
this.sshKeyPairNames = sshKeyPairNames;
}
public void setSecurityGroupList(List<Long> securityGroupIdList) {
this.securityGroupIdList = securityGroupIdList;
}
@Override
public void execute() {
UserVm result;

View File

@ -90,6 +90,10 @@ public class DestroyVMCmd extends BaseAsyncCmd implements UserCmd {
return volumeIds;
}
public boolean isForced() {
return false;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -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<SharedFSProvider> 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);
}

View File

@ -29,4 +29,6 @@ public interface SharedFSDao extends GenericDao<SharedFSVO, Long>, StateDao<Shar
List<SharedFSVO> listSharedFSToBeDestroyed(Date date);
SharedFSVO findSharedFSByNameAccountDomain(String name, Long accountId, Long domainId);
SharedFSVO findByVm(long vmId);
}

View File

@ -114,4 +114,14 @@ public class SharedFSDaoImpl extends GenericDaoBase<SharedFSVO, Long> implements
sc.setParameters("domainId", domainId);
return findOneBy(sc);
}
@Override
public SharedFSVO findByVm(long vmId) {
SearchBuilder<SharedFSVO> sb = createSearchBuilder();
sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<SharedFSVO> sc = sb.create();
sc.setParameters("vmId", vmId);
return findOneBy(sc);
}
}

View File

@ -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<String, Tag> getDummyTags() {
Map<String, Tag> 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<Vm, UserVm> 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<String, String> details) {
String instanceType, String securityGroupId, Map<String, String> 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<String, String> 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<String, String> 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<AutoScaleVmGroupVmMapVO> 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<User, Account> 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<List<Long>, String> ownerDetails = getResourceOwnerFilters();
List<UserVmJoinVO> vms = userVmJoinDao.listByHypervisorTypeAndOwners(Hypervisor.HypervisorType.KVM,
ownerDetails.first(), ownerDetails.second(), filter);
List<UserVmJoinVO> 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<Vm, UserVm> 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<String, String> 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();

View File

@ -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;
}

View File

@ -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<Long, List<Tag>> tagsResolver,
final Function<Long, List<DiskAttachment>> disksResolver,
final Function<UserVmJoinVO, List<Nic>> nicsResolver,
final Function<UserVmJoinVO, SharedFS> 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<UserVmJoinVO, SharedFS> sharedFsResolver, Vm dst) {
if (sharedFsResolver == null || dst.getDiskAttachments() == null) {
return;
}
SharedFS sharedFS = sharedFsResolver.apply(src);
if (sharedFS == null) {
return;
}
Optional<DiskAttachment> 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<Long, List<Tag>> tagsResolver,
final Function<Long, List<DiskAttachment>> disksResolver,
final Function<UserVmJoinVO, List<Nic>> nicsResolver,
final Function<UserVmJoinVO, SharedFS> 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());
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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("<GuestOsName>").append(escapeText(vm.getGuestOsName())).append("</GuestOsName>");
}
if (StringUtils.isNotBlank(vm.getInstanceType())) {
sb.append("<Type>").append(escapeText(vm.getInstanceType())).append("</Type>");
}
if (StringUtils.isNoneBlank(vm.getSharedFSId(), vm.getSharedFsVolumeName())) {
sb.append("<SharedFSId>").append(escapeText(vm.getSharedFSId())).append("</SharedFSId>");
sb.append("<SharedFSVolumeName>").append(escapeText(vm.getSharedFsVolumeName())).append("</SharedFSVolumeName>");
}
if (StringUtils.isNotBlank(vm.getSecurityGroupId())) {
sb.append("<SecurityGroupId>").append(escapeText(vm.getSecurityGroupId())).append("</SecurityGroupId>");
}
sb.append("</CloudStack>");
sb.append("</Section>");
}
@ -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<String, String> 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")) {

View File

@ -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 {

View File

@ -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());

View File

@ -52,6 +52,7 @@ public interface UserVmJoinDao extends GenericDao<UserVmJoinVO, Long> {
List<UserVmJoinVO> listLeaseInstancesExpiringInDays(int days);
List<UserVmJoinVO> listByHypervisorTypeAndOwners(Hypervisor.HypervisorType hypervisorType, List<Long> accountIds,
String domainPath, Filter filter);
List<UserVmJoinVO> listByHypervisorNotTypesAndOwners(Hypervisor.HypervisorType hypervisorType,
List<String> excludeTypes, List<Long> accountIds,
String domainPath, Filter filter);
}

View File

@ -845,10 +845,11 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
}
@Override
public List<UserVmJoinVO> listByHypervisorTypeAndOwners(Hypervisor.HypervisorType hypervisorType,
List<Long> accountIds, String domainPath, Filter filter) {
public List<UserVmJoinVO> listByHypervisorNotTypesAndOwners(Hypervisor.HypervisorType hypervisorType,
List<String> excludeTypes, List<Long> accountIds, String domainPath, Filter filter) {
SearchBuilder<UserVmJoinVO> 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<UserVmJo
sb.done();
SearchCriteria<UserVmJoinVO> sc = sb.create();
sc.setParameters("hypervisorType", hypervisorType);
if (CollectionUtils.isNotEmpty(excludeTypes)) {
sc.setParameters("type", excludeTypes.toArray());
}
if (accountIdsNotEmpty) {
sc.setParameters("account", accountIds.toArray());
}

View File

@ -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;

View File

@ -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<VolumeVO> getVolumesFromIds(DestroyVMCmd cmd) {
List<VolumeVO> 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<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap();
Map<String, String> 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);
}

View File

@ -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();