package org.ovirt.engine.core.bll.exportimport; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.LockMessage; import org.ovirt.engine.core.bll.LockMessagesMatchUtil; import org.ovirt.engine.core.bll.VmCommand; import org.ovirt.engine.core.bll.VmHandler; import org.ovirt.engine.core.bll.VmTemplateHandler; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.network.VmInterfaceManager; import org.ovirt.engine.core.bll.network.macpool.MacPool; import org.ovirt.engine.core.bll.network.vm.ExternalVmMacsFinder; import org.ovirt.engine.core.bll.network.vm.VnicProfileHelper; import org.ovirt.engine.core.bll.profiles.CpuProfileHelper; import org.ovirt.engine.core.bll.storage.disk.image.ImagesHandler; import org.ovirt.engine.core.bll.storage.utils.BlockStorageDiscardFunctionalityHelper; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.bll.validator.ImportValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.ImportVmParameters; import org.ovirt.engine.core.common.action.LockProperties; import org.ovirt.engine.core.common.action.LockProperties.Scope; import org.ovirt.engine.core.common.businessentities.ArchitectureType; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.GraphicsType; import org.ovirt.engine.core.common.businessentities.StorageDomain; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.VmDeviceGeneralType; import org.ovirt.engine.core.common.businessentities.VmDynamic; import org.ovirt.engine.core.common.businessentities.VmStatic; import org.ovirt.engine.core.common.businessentities.VmStatistics; import org.ovirt.engine.core.common.businessentities.VmTemplate; import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.locks.LockingGroup; import org.ovirt.engine.core.common.utils.CompatibilityVersionUtils; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.utils.VmDeviceCommonUtils; import org.ovirt.engine.core.common.utils.VmDeviceType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogable; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableImpl; import org.ovirt.engine.core.dao.ClusterDao; import org.ovirt.engine.core.dao.StorageDomainDao; import org.ovirt.engine.core.dao.VmDynamicDao; import org.ovirt.engine.core.dao.VmStaticDao; import org.ovirt.engine.core.dao.VmStatisticsDao; import org.ovirt.engine.core.dao.VmTemplateDao; import org.ovirt.engine.core.utils.ovf.OvfLogEventHandler; import org.ovirt.engine.core.utils.ovf.VMStaticOvfLogHandler; import org.ovirt.engine.core.utils.transaction.TransactionSupport; public abstract class ImportVmCommandBase<T extends ImportVmParameters> extends VmCommand<T> { protected Map<Guid, Guid> imageToDestinationDomainMap; protected final Map<Guid, DiskImage> newDiskIdForDisk = new HashMap<>(); private Guid sourceDomainId = Guid.Empty; private StorageDomain sourceDomain; private ImportValidator importValidator; private Version effectiveCompatibilityVersion; @Inject ExternalVmMacsFinder externalVmMacsFinder; @Inject private CpuProfileHelper cpuProfileHelper; @Inject private BlockStorageDiscardFunctionalityHelper discardHelper; @Inject private VmStaticDao vmStaticDao; @Inject private ClusterDao clusterDao; @Inject private StorageDomainDao storageDomainDao; @Inject private VmTemplateDao vmTemplateDao; @Inject private VmDynamicDao vmDynamicDao; @Inject private VmStatisticsDao vmStatisticsDao; private final List<String> macsAdded = new ArrayList<>(); private static VmStatic vmStaticForDefaultValues = new VmStatic(); private MacPool macPool; ImportVmCommandBase(T parameters, CommandContext commandContext) { super(parameters, commandContext); } ImportVmCommandBase(Guid commandId) { super(commandId); } @Override protected void setActionMessageParameters() { addValidationMessage(EngineMessage.VAR__ACTION__IMPORT); addValidationMessage(EngineMessage.VAR__TYPE__VM); } @Override protected boolean validate() { macPool = getMacPool(); if (getCluster() == null) { return failValidation(EngineMessage.ACTION_TYPE_FAILED_CLUSTER_CAN_NOT_BE_EMPTY); } return true; } @Override public Guid getVmId() { if (getParameters().isImportAsNewEntity()) { return getParameters().getVm().getId(); } return super.getVmId(); } @Override public VM getVm() { if (getParameters().isImportAsNewEntity()) { return getParameters().getVm(); } return super.getVm(); } protected Version getEffectiveCompatibilityVersion() { return effectiveCompatibilityVersion; } protected void setEffectiveCompatibilityVersion(Version effectiveCompatibilityVersion) { this.effectiveCompatibilityVersion = effectiveCompatibilityVersion; } protected ImportValidator getImportValidator() { if (importValidator == null) { importValidator = new ImportValidator(getParameters()); } return importValidator; } protected boolean validateUniqueVmName() { return VmHandler.isVmWithSameNameExistStatic(getVm().getName(), getStoragePoolId()) ? failValidation(EngineMessage.VM_CANNOT_IMPORT_VM_NAME_EXISTS) : true; } /** * Validates that there is no duplicate VM. * @return <code>true</code> if the validation passes, <code>false</code> otherwise. */ protected boolean validateNoDuplicateVm() { VmStatic duplicateVm = vmStaticDao.get(getVm().getId()); return duplicateVm == null ? true : failValidation(EngineMessage.VM_CANNOT_IMPORT_VM_EXISTS, String.format("$VmName %1$s", duplicateVm.getName())); } /** * Validates if the VM being imported has a valid architecture. */ protected boolean validateVmArchitecture () { return getVm().getClusterArch() == ArchitectureType.undefined ? failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_CANNOT_IMPORT_VM_WITH_NOT_SUPPORTED_ARCHITECTURE) : true; } /** * Validates that that the required cluster exists and is compatible * @return <code>true</code> if the validation passes, <code>false</code> otherwise. */ protected boolean validateVdsCluster() { Cluster cluster = clusterDao.get(getClusterId()); return cluster == null ? failValidation(EngineMessage.VDS_CLUSTER_IS_NOT_VALID) : cluster.getArchitecture() != getVm().getClusterArch() ? failValidation(EngineMessage.ACTION_TYPE_FAILED_VM_CANNOT_IMPORT_VM_ARCHITECTURE_NOT_SUPPORTED_BY_CLUSTER) : true; } protected boolean validateGraphicsAndDisplay() { return vmHandler.isGraphicsAndDisplaySupported(getParameters().getVm().getOs(), getGraphicsTypesForVm(), getVm().getDefaultDisplayType(), getReturnValue().getValidationMessages(), getEffectiveCompatibilityVersion()); } Set<GraphicsType> getGraphicsTypesForVm() { return getDevicesOfType(VmDeviceGeneralType.GRAPHICS) .stream() .map(g -> GraphicsType.fromVmDeviceType(VmDeviceType.getByName(g.getDevice()))) .collect(Collectors.toSet()); } private List<VmDevice> getDevicesOfType(VmDeviceGeneralType type) { if (getVm() == null || getVm().getStaticData() == null || getVm().getStaticData().getManagedDeviceMap() == null) { return Collections.emptyList(); } return getVm().getStaticData() .getManagedDeviceMap() .values() .stream() .filter(d -> d.getType() == type) .collect(Collectors.toList()); } protected boolean setAndValidateCpuProfile() { getVm().getStaticData().setClusterId(getClusterId()); getVm().getStaticData().setCpuProfileId(getParameters().getCpuProfileId()); return validate(cpuProfileHelper.setAndValidateCpuProfile( getVm().getStaticData(), getUserIdIfExternal().orElse(null))); } protected boolean validateBallonDevice() { if (!VmDeviceCommonUtils.isBalloonDeviceExists(getVm().getManagedVmDeviceMap().values())) { return true; } if (!osRepository.isBalloonEnabled(getVm().getStaticData().getOsId(), getEffectiveCompatibilityVersion())) { addValidationMessageVariable("clusterArch", getCluster().getArchitecture()); return failValidation(EngineMessage.BALLOON_REQUESTED_ON_NOT_SUPPORTED_ARCH); } return true; } protected boolean validateSoundDevice() { if (!VmDeviceCommonUtils.isSoundDeviceExists(getVm().getManagedVmDeviceMap().values())) { return true; } if (!osRepository.isSoundDeviceEnabled(getVm().getStaticData().getOsId(), getEffectiveCompatibilityVersion())) { addValidationMessageVariable("clusterArch", getCluster().getArchitecture()); return failValidation(EngineMessage.SOUND_DEVICE_REQUESTED_ON_NOT_SUPPORTED_ARCH); } return true; } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Command); } @Override @SuppressWarnings("serial") protected Map<String, Pair<String, String>> getExclusiveLocks() { if (getParameters().getVm() != null && !StringUtils.isBlank(getParameters().getVm().getName())) { return new HashMap<String, Pair<String, String>>() { { put(getParameters().getVm().getName(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM_NAME, EngineMessage.ACTION_TYPE_FAILED_NAME_ALREADY_USED)); put(getParameters().getVm().getId().toString(), LockMessagesMatchUtil.makeLockingPair(LockingGroup.VM, getVmIsBeingImportedMessage())); } }; } return null; } protected LockMessage getVmIsBeingImportedMessage() { return new LockMessage(EngineMessage.ACTION_TYPE_FAILED_VM_IS_BEING_IMPORTED) .withOptional("VmName", getVmName()); } @Override protected void init() { super.init(); T parameters = getParameters(); // before the execute phase, parameters.getVmId().equals(parameters.getVm().getId() == true // afterwards if will be false if parameters.isImportAsNewEntity() == true, and there is no // better way to check it (can't use the action-state since it will always be EXECUTE // in the postConstruct phase. if (parameters.isImportAsNewEntity() && parameters.getVmId().equals(parameters.getVm().getId())) { parameters.getVm().setId(Guid.newGuid()); } setClusterId(parameters.getClusterId()); setVm(parameters.getVm()); initEffectiveCompatibilityVersion(); } protected void initEffectiveCompatibilityVersion() { setEffectiveCompatibilityVersion( CompatibilityVersionUtils.getEffective(getParameters().getVm(), this::getCluster)); } /** * Cloning a new disk and all its volumes with a new generated id. * The disk will have the same parameters as disk. * Also adding the disk to newDiskGuidForDisk map, so we will be able to link between * the new cloned disk and the old disk id. * * @param diskImagesList * - All the disk volumes * @param disk * - The disk which is about to be cloned */ protected void generateNewDiskId(List<DiskImage> diskImagesList, DiskImage disk) { Guid generatedGuid = generateNewDiskId(disk); for (DiskImage diskImage : diskImagesList) { diskImage.setId(generatedGuid); } } /** * Cloning a new disk with a new generated id, with the same parameters as <code>disk</code>. Also * adding the disk to <code>newDiskGuidForDisk</code> map, so we will be able to link between the new cloned disk * and the old disk id. * * @param disk * - The disk which is about to be cloned */ protected Guid generateNewDiskId(DiskImage disk) { Guid newGuidForDisk = Guid.newGuid(); // Copy the disk so it will preserve the old disk id and image id. newDiskIdForDisk.put(newGuidForDisk, DiskImage.copyOf(disk)); disk.setId(newGuidForDisk); disk.setImageId(Guid.newGuid()); return newGuidForDisk; } /** * Updating managed device map of VM, with the new disk {@link Guid}. * The update of managedDeviceMap is based on the newDiskIdForDisk map, * so this method should be called only after newDiskIdForDisk is initialized. * * @param disk * - The disk which is about to be cloned * @param managedDeviceMap * - The managed device map contained in the VM. */ protected void updateManagedDeviceMap(DiskImage disk, Map<Guid, VmDevice> managedDeviceMap) { Guid oldDiskId = newDiskIdForDisk.get(disk.getId()).getId(); managedDeviceMap.put(disk.getId(), managedDeviceMap.get(oldDiskId)); managedDeviceMap.remove(oldDiskId); } protected void ensureDomainMap(Collection<DiskImage> images, Guid defaultDomainId) { if (imageToDestinationDomainMap == null) { imageToDestinationDomainMap = new HashMap<>(); } if (imageToDestinationDomainMap.isEmpty() && images != null && defaultDomainId != null) { for (DiskImage image : images) { if (isImagesAlreadyOnTarget()) { imageToDestinationDomainMap.put(image.getId(), image.getStorageIds().get(0)); } else if (!Guid.Empty.equals(defaultDomainId)) { imageToDestinationDomainMap.put(image.getId(), defaultDomainId); } } } } /** * Space Validations are done using data extracted from the disks. The disks in question in this command * don't have all the needed data, and in order not to contaminate the command's data structures, an alter * one is created specifically fo this validation - hence dummy. */ protected List<DiskImage> createDiskDummiesForSpaceValidations(List<DiskImage> disksList) { List<DiskImage> dummies = new ArrayList<>(disksList.size()); for (DiskImage image : disksList) { Guid targetSdId = imageToDestinationDomainMap.get(image.getId()); DiskImage dummy = ImagesHandler.createDiskImageWithExcessData(image, targetSdId); dummies.add(dummy); } return dummies; } protected void setImagesWithStoragePoolId(Guid storagePoolId, List<DiskImage> diskImages) { for (DiskImage diskImage : diskImages) { diskImage.setStoragePoolId(storagePoolId); } } protected StorageDomain getSourceDomain() { if (sourceDomain == null && !Guid.Empty.equals(sourceDomainId)) { sourceDomain = storageDomainDao.getForStoragePool(sourceDomainId, getStoragePool().getId()); } return sourceDomain; } protected void setSourceDomainId(Guid storageId) { sourceDomainId = storageId; } protected boolean isImagesAlreadyOnTarget() { return getParameters().isImagesExistOnTargetStorageDomain(); } protected StorageDomain getStorageDomain(Guid domainId) { return storageDomainDao.getForStoragePool(domainId, getStoragePool().getId()); } @Override protected void executeVmCommand() { try { addVmToDb(); processImages(); vmHandler.addVmInitToDB(getVm().getStaticData()); discardHelper.logIfDisksWithIllegalPassDiscardExist(getVmId()); } catch (RuntimeException e) { macPool.freeMacs(macsAdded); throw e; } setSucceeded(true); getReturnValue().setActionReturnValue(getVm()); } private void reportExternalMacs() { final VM vm = getVm(); final Set<String> externalMacAddresses = externalVmMacsFinder.findExternalMacAddresses(vm); if (CollectionUtils.isNotEmpty(externalMacAddresses)) { auditLog(createExternalMacsAuditLog(vm, externalMacAddresses), AuditLogType.MAC_ADDRESS_IS_EXTERNAL); } } private AuditLogable createExternalMacsAuditLog(VM vm, Set<String> externalMacs) { AuditLogable logable = new AuditLogableImpl(); logable.setVmId(vm.getId()); logable.setVmName(vm.getName()); logable.addCustomValue("MACAddr", externalMacs.stream().collect(Collectors.joining(", "))); return logable; } protected abstract void processImages(); protected void addVmToDb() { TransactionSupport.executeInNewTransaction(() -> { addVmStatic(); addVmDynamic(); addVmStatistics(); addVmInterfaces(); getCompensationContext().stateChanged(); return null; }); } protected void addVmStatic() { logImportEvents(); getVm().getStaticData().setId(getVmId()); getVm().getStaticData().setCreationDate(new Date()); getVm().getStaticData().setClusterId(getParameters().getClusterId()); getVm().getStaticData().setMinAllocatedMem(computeMinAllocatedMem()); getVm().getStaticData().setQuotaId(getParameters().getQuotaId()); // if "run on host" field points to a non existent vds (in the current cluster) -> remove field and continue if (!vmHandler.validateDedicatedVdsExistOnSameCluster(getVm().getStaticData(), null)) { getVm().setDedicatedVmForVdsList(Collections.emptyList()); } if (getVm().getOriginalTemplateGuid() != null && !VmTemplateHandler.BLANK_VM_TEMPLATE_ID.equals(getVm().getOriginalTemplateGuid())) { // no need to check this for blank VmTemplate originalTemplate = vmTemplateDao.get(getVm().getOriginalTemplateGuid()); if (originalTemplate != null) { // in case the original template name has been changed in the meantime getVm().getStaticData().setOriginalTemplateName(originalTemplate.getName()); } } if (getParameters().getCopyCollapse()) { getVm().setVmtGuid(VmTemplateHandler.BLANK_VM_TEMPLATE_ID); } vmStaticDao.save(getVm().getStaticData()); getCompensationContext().snapshotNewEntity(getVm().getStaticData()); } private void logImportEvents() { // Some values at the OVF file are used for creating events at the GUI // for the sake of providing information on the content of the VM that // was exported, // but not setting it in the imported VM VmStatic vmStaticFromOvf = getVm().getStaticData(); OvfLogEventHandler<VmStatic> handler = new VMStaticOvfLogHandler(vmStaticFromOvf); Map<String, String> aliasesValuesMap = handler.getAliasesValuesMap(); if (aliasesValuesMap != null) { for (Map.Entry<String, String> entry : aliasesValuesMap.entrySet()) { String fieldName = entry.getKey(); String fieldValue = entry.getValue(); logField(vmStaticFromOvf, fieldName, fieldValue); } } handler.resetDefaults(vmStaticForDefaultValues); } private void logField(VmStatic vmStaticFromOvf, String fieldName, String fieldValue) { String vmName = vmStaticFromOvf.getName(); addCustomValue("FieldName", fieldName); addCustomValue("VmName", vmName); addCustomValue("FieldValue", fieldValue); auditLogDirector.log(this, AuditLogType.VM_IMPORT_INFO); } protected void addVmInterfaces() { VmInterfaceManager vmInterfaceManager = new VmInterfaceManager(macPool); VnicProfileHelper vnicProfileHelper = new VnicProfileHelper(getClusterId(), getStoragePoolId(), AuditLogType.IMPORTEXPORT_IMPORT_VM_INVALID_INTERFACES); List<VmNetworkInterface> nics = getVm().getInterfaces(); vmInterfaceManager.sortVmNics(nics, getVm().getStaticData().getManagedDeviceMap()); // If we import it as a new entity, then we allocate all MAC addresses in advance if (getParameters().isImportAsNewEntity()) { List<String> macAddresses = macPool.allocateMacAddresses(nics.size()); for (int i = 0; i < nics.size(); ++i) { nics.get(i).setMacAddress(macAddresses.get(i)); } } else { if (isExternalMacsToBeReported()) { reportExternalMacs(); } } for (VmNetworkInterface iface : getVm().getInterfaces()) { initInterface(iface); vnicProfileHelper.updateNicWithVnicProfileForUser(iface, getCurrentUser()); final boolean reassignMac = shouldMacBeReassigned(iface); final boolean reserveExistingMac = !(reassignMac || getParameters().isImportAsNewEntity()); vmInterfaceManager.add(iface, getCompensationContext(), reserveExistingMac, reassignMac, getVm().getOs(), getEffectiveCompatibilityVersion()); macsAdded.add(iface.getMacAddress()); } vnicProfileHelper.auditInvalidInterfaces(getVmName()); } protected boolean shouldMacBeReassigned(VmNetworkInterface iface) { return getParameters().isReassignBadMacs() && vnicHasBadMac(iface); } protected boolean vnicHasBadMac(VmNetworkInterface vnic) { return false; } protected boolean isExternalMacsToBeReported() { return true; } private void initInterface(VmNic iface) { if (iface.getId() == null) { iface.setId(Guid.newGuid()); } fillMacAddressIfMissing(iface); iface.setVmTemplateId(null); iface.setVmId(getVmId()); } private void fillMacAddressIfMissing(VmNic iface) { if (StringUtils.isEmpty(iface.getMacAddress()) && macPool.getAvailableMacsCount() > 0) { iface.setMacAddress(macPool.allocateNewMac()); } } private void addVmDynamic() { VmDynamic tempVar = createVmDynamic(); vmDynamicDao.save(tempVar); getCompensationContext().snapshotNewEntity(tempVar); } protected VmDynamic createVmDynamic() { VmDynamic vmDynamic = new VmDynamic(); vmDynamic.setId(getVmId()); vmDynamic.setStatus(VMStatus.ImageLocked); vmDynamic.setVmHost(""); vmDynamic.setIp(""); vmDynamic.setFqdn(""); vmDynamic.setLastStopTime(new Date()); vmDynamic.setAppList(getParameters().getVm().getAppList()); return vmDynamic; } private void addVmStatistics() { VmStatistics stats = new VmStatistics(getVmId()); vmStatisticsDao.save(stats); getCompensationContext().snapshotNewEntity(stats); getCompensationContext().stateChanged(); } private int computeMinAllocatedMem() { if (getVm().getMinAllocatedMem() > 0) { return getVm().getMinAllocatedMem(); } Cluster cluster = getCluster(); if (cluster != null && cluster.getMaxVdsMemoryOverCommit() > 0) { return (getVm().getMemSizeMb() * 100) / cluster.getMaxVdsMemoryOverCommit(); } return getVm().getMemSizeMb(); } @Override public List<PermissionSubject> getPermissionCheckSubjects() { Set<PermissionSubject> permissionSet = new HashSet<>(); // Destination domains for (Guid storageId : imageToDestinationDomainMap.values()) { permissionSet.add(new PermissionSubject(storageId, VdcObjectType.Storage, getActionType().getActionGroup())); } return new ArrayList<>(permissionSet); } @Override public Map<String, String> getJobMessageProperties() { if (jobProperties == null) { jobProperties = super.getJobMessageProperties(); jobProperties.put(VdcObjectType.VM.name().toLowerCase(), (getVmName() == null) ? "" : getVmName()); jobProperties.put(VdcObjectType.Cluster.name().toLowerCase(), getClusterName()); } return jobProperties; } }