package org.ovirt.engine.core.bll.exportimport; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import org.ovirt.engine.core.bll.CommandActionState; import org.ovirt.engine.core.bll.DisableInPrepareMode; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.ValidationResult; import org.ovirt.engine.core.bll.VmHandler; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.profiles.DiskProfileHelper; import org.ovirt.engine.core.bll.quota.QuotaConsumptionParameter; import org.ovirt.engine.core.bll.quota.QuotaStorageConsumptionParameter; import org.ovirt.engine.core.bll.quota.QuotaStorageDependent; import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler; import org.ovirt.engine.core.bll.storage.domain.IsoDomainListSynchronizer; import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.AddDiskParameters; import org.ovirt.engine.core.common.action.ConvertVmParameters; import org.ovirt.engine.core.common.action.ImportVmFromExternalProviderParameters; import org.ovirt.engine.core.common.action.RemoveVmParameters; import org.ovirt.engine.core.common.action.VdcActionParametersBase.EndProcedure; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.businessentities.ArchitectureType; import org.ovirt.engine.core.common.businessentities.DisplayType; import org.ovirt.engine.core.common.businessentities.OriginType; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.StorageDomainStatus; import org.ovirt.engine.core.common.businessentities.StoragePoolStatus; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VDSStatus; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.VmDynamic; import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.businessentities.storage.DiskInterface; import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement; import org.ovirt.engine.core.common.errors.EngineException; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.queries.IdQueryParameters; import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.ClusterDao; import org.ovirt.engine.core.dao.StorageDomainDao; import org.ovirt.engine.core.dao.VdsDao; import org.ovirt.engine.core.dao.VmDeviceDao; @DisableInPrepareMode @NonTransactiveCommandAttribute(forceCompensation = true) public class ImportVmFromExternalProviderCommand<T extends ImportVmFromExternalProviderParameters> extends ImportVmCommandBase<T> implements QuotaStorageDependent { private static final Pattern VMWARE_DISK_NAME_PATTERN = Pattern.compile("\\[.*?\\] .*/(.*).vmdk"); private static final Pattern DISK_NAME_PATTERN = Pattern.compile(".*/([^.]+).*"); private static final String VDSM_COMPAT_VERSION_1_1 = "1.1"; @Inject private DiskProfileHelper diskProfileHelper; @Inject private IsoDomainListSynchronizer isoDomainListSynchronizer; @Inject private ClusterDao clusterDao; @Inject private VdsDao vdsDao; @Inject private StorageDomainDao storageDomainDao; @Inject private VmDeviceDao vmDeviceDao; public ImportVmFromExternalProviderCommand(Guid cmdId) { super(cmdId); } public ImportVmFromExternalProviderCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); } @Override protected void init() { super.init(); setVmName(getParameters().getExternalName()); setVdsId(getParameters().getProxyHostId()); setStorageDomainId(getParameters().getDestDomainId()); setStoragePoolId(getCluster() != null ? getCluster().getStoragePoolId() : null); setSingleQxlPci(); checkImageTarget(); VmHandler.updateMaxMemorySize(getVm().getStaticData(), getEffectiveCompatibilityVersion()); } @Override protected boolean validate() { if (!super.validate()) { return false; } if (getStorageDomain() == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_NOT_EXIST); } if (!getStorageDomain().getStoragePoolId().equals(getStoragePoolId())) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_AND_CLUSTER_IN_DIFFERENT_POOL); } if (getStoragePool().getStatus() != StoragePoolStatus.Up) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_POOL_STATUS_ILLEGAL); } if (getStorageDomain().getStatus() != StorageDomainStatus.Active) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_STORAGE_DOMAIN_STATUS_ILLEGAL); } if (!Guid.isNullOrEmpty(getVdsId()) && !validate(validateRequestedProxyHost())) { return false; } if (Guid.isNullOrEmpty(getVdsId()) && !validate(validateEligibleProxyHostExists())) { return false; } if (!validateBallonDevice()) { return false; } if (!validateSoundDevice()) { return false; } if (!validateNoDuplicateVm()) { return false; } if (!validateUniqueVmName()) { return false; } if (!validateVmArchitecture()) { return false; } if (!validateVdsCluster()) { return false; } if (!setAndValidateCpuProfile()) { return false; } if (!setAndValidateDiskProfiles()) { return false; } if (!validateStorageSpace()) { return false; } if (getParameters().getVirtioIsoName() != null && getActiveIsoDomainId() == null) { return failValidation(EngineMessage.ERROR_CANNOT_FIND_ISO_IMAGE_PATH); } if (!validate(VmHandler.validateMaxMemorySize(getVm().getStaticData(), getEffectiveCompatibilityVersion()))) { return false; } return true; } protected boolean validateStorageSpace() { List<DiskImage> dummiesDisksList = createDiskDummiesForSpaceValidations(getVm().getImages()); return validate(getImportValidator().validateSpaceRequirements(dummiesDisksList)); } protected boolean setAndValidateDiskProfiles() { Map<DiskImage, Guid> map = new HashMap<>(); for (DiskImage diskImage : getVm().getImages()) { map.put(diskImage, getStorageDomainId()); } return validate(diskProfileHelper.setAndValidateDiskProfiles(map, getCurrentUser())); } private ValidationResult validateRequestedProxyHost() { if (getVds() == null) { return new ValidationResult(EngineMessage.VDS_DOES_NOT_EXIST); } if (!isHostInSupportedClusterForProxyHost(getVds())) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_HOST_CANNOT_BE_PROXY_FOR_IMPORT_VM, String.format("$vdsName %s", getVdsName())); } if (!getStoragePoolId().equals(getVds().getStoragePoolId())) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VDS_NOT_IN_DEST_STORAGE_POOL); } if (getVds().getStatus() != VDSStatus.Up) { return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_VDS_STATUS_ILLEGAL); } return ValidationResult.VALID; } private ValidationResult validateEligibleProxyHostExists() { for (VDS host : vdsDao.getAllForStoragePoolAndStatus(getStoragePoolId(), VDSStatus.Up)) { if (isHostInSupportedClusterForProxyHost(host)) { return ValidationResult.VALID; } } return new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_NO_HOST_CAN_BE_PROXY_FOR_IMPORT_VM, String.format("$storagePoolName %s", getStoragePoolName())); } private boolean isHostInSupportedClusterForProxyHost(VDS host) { return clusterDao.get(host.getClusterId()).getArchitecture() != ArchitectureType.ppc64; } @Override protected List<DiskImage> createDiskDummiesForSpaceValidations(List<DiskImage> disksList) { List<DiskImage> dummies = new ArrayList<>(disksList.size()); ArrayList<Guid> emptyStorageIds = new ArrayList<>(); for (DiskImage image : disksList) { image.setStorageIds(emptyStorageIds); dummies.add(ImagesHandler.createDiskImageWithExcessData(image, getStorageDomainId())); } return dummies; } @Override public List<QuotaConsumptionParameter> getQuotaStorageConsumptionParameters() { List<QuotaConsumptionParameter> list = new ArrayList<>(); for (DiskImage diskImage : getVm().getImages()) { list.add(new QuotaStorageConsumptionParameter( diskImage.getQuotaId(), null, QuotaConsumptionParameter.QuotaAction.CONSUME, getStorageDomainId(), (double)diskImage.getSizeInGigabytes())); } return list; } @Override protected void processImages() { ArrayList<Guid> diskIds = new ArrayList<>(); boolean isFirstDisk = true; for (DiskImage image : getVm().getImages()) { Guid diskId = createDisk(image, getVm().getOrigin() == OriginType.KVM && isFirstDisk); diskIds.add(diskId); isFirstDisk = false; } getParameters().setDisks(diskIds); setSucceeded(true); } @Override protected VmDynamic createVmDynamic() { VmDynamic vmDynamic = super.createVmDynamic(); vmDynamic.setStatus(VMStatus.Down); return vmDynamic; } @Override protected void addVmStatic() { super.addVmStatic(); getSnapshotsManager().addActiveSnapshot( Guid.newGuid(), getVm(), "", getCompensationContext()); } @Override protected void addVmInterfaces() { super.addVmInterfaces(); for (VmNetworkInterface iface : getVm().getInterfaces()) { getVmDeviceUtils().addInterface(getVmId(), iface.getId(), iface.isPlugged(), false); } } private Guid createDisk(DiskImage image, boolean isBoot) { image.setDiskAlias(renameDiskAlias(getVm().getOrigin(), image.getDiskAlias())); AddDiskParameters diskParameters = new AddDiskParameters(new DiskVmElement(null, getVmId()), image); diskParameters.setStorageDomainId(getStorageDomainId()); diskParameters.setParentCommand(getActionType()); diskParameters.setParentParameters(getParameters()); diskParameters.setShouldRemainIllegalOnFailedExecution(true); diskParameters.setStorageDomainId(getParameters().getDestDomainId()); DiskVmElement dve = new DiskVmElement(image.getId(), getVmId()); dve.setDiskInterface(DiskInterface.VirtIO); dve.setBoot(isBoot); diskParameters.setDiskVmElement(dve); VdcReturnValueBase vdcReturnValueBase = runInternalActionWithTasksContext(VdcActionType.AddDisk, diskParameters); if (!vdcReturnValueBase.getSucceeded()) { throw new EngineException(vdcReturnValueBase.getFault().getError(), "Failed to create disk!"); } getTaskIdList().addAll(vdcReturnValueBase.getInternalVdsmTaskIdList()); return vdcReturnValueBase.getActionReturnValue(); } private void checkImageTarget() { // TODO: // This is a workaround until bz 1332019 will be merged // since KVM is currently the only source for which we use Libvirt streaming API. // Libvirt currently reports the actual disk size and the virtual one, // when using preallocated qcow2 on block domain the size that the Libvirt stream emits // can be larger then the actual size. if (getVm().getOrigin() == OriginType.KVM && getActionState() == CommandActionState.EXECUTE) { StorageDomain domain = storageDomainDao.get(getStorageDomainId()); if (domain.getStorageType().isBlockDomain()) { getVm().getImages().forEach(image -> image.setActualSizeInBytes(image.getSize())); } } } private static String replaceInvalidDiskAliasChars(String alias) { return alias.replace(' ', '_'); } protected static String renameDiskAlias(OriginType originType, String alias) { Matcher matcher; if (originType == OriginType.VMWARE) { matcher = VMWARE_DISK_NAME_PATTERN.matcher(alias); } else { matcher = DISK_NAME_PATTERN.matcher(alias); } if (matcher.matches()) { return replaceInvalidDiskAliasChars(matcher.group(1)); } return replaceInvalidDiskAliasChars(alias); } @Override protected void endSuccessfully() { endActionOnDisks(); // Lock will be acquired by the convert command. // Note that the VM is not locked for a short period of time. This should be fixed // when locks that are passed by caller could be released by command's callback. freeLock(); convert(); setSucceeded(true); } @Override protected void addVmToDb() { super.addVmToDb(); if (getVm().getOrigin() == OriginType.KVM) { ImportUtils.updateGraphicsDevices(getVm().getStaticData(), getStoragePool().getCompatibilityVersion()); if (getParameters().isImportAsNewEntity()) { for (VmDevice device : getVm().getStaticData().getManagedDeviceMap().values()) { device.getId().setVmId(getVmId()); } } vmDeviceDao.saveAll(getVm().getStaticData().getManagedDeviceMap().values()); } } protected void convert() { CommandCoordinatorUtil.executeAsyncCommand( VdcActionType.ConvertVm, buildConvertVmParameters(), cloneContextAndDetachFromParent()); } private ConvertVmParameters buildConvertVmParameters() { ConvertVmParameters parameters = new ConvertVmParameters(getVmId()); parameters.setUrl(getParameters().getUrl()); parameters.setUsername(getParameters().getUsername()); parameters.setPassword(getParameters().getPassword()); parameters.setVmName(getVmName()); parameters.setOriginType(getVm().getOrigin()); parameters.setDisks(getDisks()); parameters.setStoragePoolId(getStoragePoolId()); parameters.setCompatVersion(getCompatVersion()); parameters.setStorageDomainId(getStorageDomainId()); parameters.setProxyHostId(getParameters().getProxyHostId()); parameters.setClusterId(getClusterId()); parameters.setVirtioIsoName(getParameters().getVirtioIsoName()); parameters.setEndProcedure(EndProcedure.COMMAND_MANAGED); return parameters; } private String getCompatVersion() { int version = Integer.parseInt(getStoragePool().getStoragePoolFormatType().getValue()); // compat version 1.1 supported from storage version 4 if (version >= 4 && getVm().getOrigin() != OriginType.KVM) { return VDSM_COMPAT_VERSION_1_1; } return null; } @Override protected void endWithFailure() { // Since AddDisk is called internally, its audit log on end-action will not be logged auditLog(this, AuditLogType.ADD_DISK_INTERNAL_FAILURE); endActionOnDisks(); removeVm(); setSucceeded(true); } private void removeVm() { runInternalActionWithTasksContext( VdcActionType.RemoveVm, new RemoveVmParameters(getVmId(), true)); } protected List<DiskImage> getDisks() { List<DiskImage> disks = new ArrayList<>(); for (Guid diskId : getParameters().getDisks()) { disks.add(getDisk(diskId)); } return disks; } private DiskImage getDisk(Guid diskId) { return runInternalQuery( VdcQueryType.GetDiskByDiskId, new IdQueryParameters(diskId)) .getReturnValue(); } @Override public AuditLogType getAuditLogTypeValue() { switch (getActionState()) { case EXECUTE: return getSucceeded() ? AuditLogType.IMPORTEXPORT_STARTING_IMPORT_VM : AuditLogType.IMPORTEXPORT_IMPORT_VM_FAILED; case END_FAILURE: return AuditLogType.IMPORTEXPORT_IMPORT_VM_FAILED; case END_SUCCESS: default: return super.getAuditLogTypeValue(); } } @Override public List<PermissionSubject> getPermissionCheckSubjects() { Set<PermissionSubject> permissionSet = new HashSet<>(); // Destination domain permissionSet.add(new PermissionSubject(getStorageDomainId(), VdcObjectType.Storage, getActionType().getActionGroup())); return new ArrayList<>(permissionSet); } protected Guid getActiveIsoDomainId() { return isoDomainListSynchronizer.findActiveISODomain(getStoragePoolId()); } private void setSingleQxlPci() { if (!osRepository.isSingleQxlDeviceEnabled(getVm().getVmOsId()) || getVm().getDefaultDisplayType() != DisplayType.qxl) { getVm().getStaticData().setSingleQxlPci(false); } } }