package org.ovirt.engine.core.bll.exportimport; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.NonTransactiveCommandAttribute; import org.ovirt.engine.core.bll.ValidationResult; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.network.macpool.MacPool; import org.ovirt.engine.core.bll.network.predicate.VnicWithBadMacPredicate; import org.ovirt.engine.core.bll.storage.ovfstore.OvfHelper; import org.ovirt.engine.core.bll.validator.ImportValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.action.AttachDetachVmDiskParameters; 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.action.VdcActionType; import org.ovirt.engine.core.common.action.VdcReturnValueBase; import org.ovirt.engine.core.common.businessentities.OvfEntityData; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.network.VmNetworkInterface; import org.ovirt.engine.core.common.businessentities.storage.Disk; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.UnregisteredDisksDao; import org.ovirt.engine.core.dao.UnregisteredOVFDataDao; import org.ovirt.engine.core.utils.ReplacementUtils; import org.ovirt.engine.core.utils.ovf.OvfReaderException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @NonTransactiveCommandAttribute(forceCompensation = true) public class ImportVmFromConfigurationCommand<T extends ImportVmParameters> extends ImportVmCommand<T> { private static final Logger log = LoggerFactory.getLogger(ImportVmFromConfigurationCommand.class); private Collection<Disk> vmDisksToAttach; private OvfEntityData ovfEntityData; private VM vmFromConfiguration; @Inject private OvfHelper ovfHelper; @Inject private ExternalVnicProfileMappingValidator externalVnicProfileMappingValidator; @Inject private ImportedNetworkInfoUpdater importedNetworkInfoUpdater; @Inject private UnregisteredOVFDataDao unregisteredOVFDataDao; @Inject private UnregisteredDisksDao unregisteredDisksDao; public ImportVmFromConfigurationCommand(Guid commandId) { super(commandId); } public ImportVmFromConfigurationCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); setCommandShouldBeLogged(false); } @Override protected boolean validate() { if (isImagesAlreadyOnTarget()) { if (!validateExternalVnicProfileMapping()) { return false; } ImportValidator importValidator = getImportValidator(); if (!validate(importValidator.validateUnregisteredEntity(vmFromConfiguration, ovfEntityData))) { return false; } if (!validateMacs(vmFromConfiguration)) { return false; } if (!validate(importValidator.validateStorageExistForUnregisteredEntity(getImages(), getParameters().isAllowPartialImport(), imageToDestinationDomainMap, failedDisksToImportForAuditLog))) { return false; } setImagesWithStoragePoolId(getParameters().getStoragePoolId(), getVm().getImages()); } return super.validate(); } private boolean validateExternalVnicProfileMapping() { final ValidationResult validationResult = externalVnicProfileMappingValidator.validateExternalVnicProfileMapping( getParameters().getExternalVnicProfileMappings(), getParameters().getClusterId()); return validate(validationResult); } private boolean validateMacs(VM vm) { if (getParameters().isReassignBadMacs()) { return true; } final List<VmNetworkInterface> vnics = vm.getInterfaces(); return reportDuplicateMacs(vnics); } private boolean reportDuplicateMacs(List<VmNetworkInterface> vnics) { final MacPool macPool = getMacPool(); return macPool.isDuplicateMacAddressesAllowed() || validate(validateForMacsInUse(vnics, macPool)); } private ValidationResult validateForMacsInUse(List<VmNetworkInterface> vnics, MacPool macPool) { if (macPool.isDuplicateMacAddressesAllowed()) { return ValidationResult.VALID; } final List<String> macsInUse = vnics .stream() .map(VmNetworkInterface::getMacAddress) .filter(Objects::nonNull) .filter(macPool::isMacInUse) .collect(Collectors.toList()); final EngineMessage msg = EngineMessage.NETWORK_MAC_ADDRESS_IN_USE_DETAILED; return ValidationResult .failWith(msg, ReplacementUtils.getListVariableAssignmentString(msg, macsInUse)) .unless(macsInUse.isEmpty()); } @Override protected boolean isExternalMacsToBeReported() { return !getParameters().isReassignBadMacs(); } @Override protected boolean vnicHasBadMac(VmNetworkInterface vnic) { final MacPool macPool = getMacPool(); final Predicate<VmNetworkInterface> vnicWithBadMacPredicate = new VnicWithBadMacPredicate(macPool); return vnicWithBadMacPredicate.test(vnic); } @Override protected LockProperties applyLockProperties(LockProperties lockProperties) { return lockProperties.withScope(Scope.Execution); } @Override protected void init() { VM vmFromConfiguration = getParameters().getVm(); if (vmFromConfiguration != null) { vmFromConfiguration.getStaticData().setClusterId(getParameters().getClusterId()); if (!isImagesAlreadyOnTarget()) { setDisksToBeAttached(vmFromConfiguration); } getParameters().setContainerId(vmFromConfiguration.getId()); } else { initUnregisteredVM(); } if (Guid.Empty.equals(getParameters().getVmId()) && getParameters().getVm() != null) { getParameters().setVmId(getParameters().getVm().getId()); } setClusterId(getParameters().getClusterId()); getParameters().setStoragePoolId(getCluster().getStoragePoolId()); super.init(); } private void initUnregisteredVM() { List<OvfEntityData> ovfEntityDataList = unregisteredOVFDataDao.getByEntityIdAndStorageDomain(getParameters().getContainerId(), getParameters().getStorageDomainId()); if (!ovfEntityDataList.isEmpty()) { try { // We should get only one entity, since we fetched the entity with a specific Storage Domain ovfEntityData = ovfEntityDataList.get(0); vmFromConfiguration = ovfHelper.readVmFromOvf(ovfEntityData.getOvfData()); vmFromConfiguration.setClusterId(getParameters().getClusterId()); mapVnicProfiles(vmFromConfiguration.getInterfaces()); getParameters().setVm(vmFromConfiguration); getParameters().setDestDomainId(ovfEntityData.getStorageDomainId()); getParameters().setSourceDomainId(ovfEntityData.getStorageDomainId()); // For quota, update disks when required if (getParameters().getDiskMap() != null) { vmFromConfiguration.setDiskMap(getParameters().getDiskMap()); vmFromConfiguration.setImages(getDiskImageListFromDiskMap(getParameters().getDiskMap())); } } catch (OvfReaderException e) { log.error("Failed to parse a given ovf configuration: {}:\n{}", e.getMessage(), ovfEntityData.getOvfData()); log.debug("Exception", e); } } } private void mapVnicProfiles(List<VmNetworkInterface> vnics) { vnics.forEach(vnic -> importedNetworkInfoUpdater.updateNetworkInfo(vnic, getParameters().getExternalVnicProfileMappings())); } private static ArrayList<DiskImage> getDiskImageListFromDiskMap(Map<Guid, Disk> diskMap) { return diskMap.values().stream().map(disk -> (DiskImage) disk).collect(Collectors.toCollection(ArrayList::new)); } private void setDisksToBeAttached(VM vmFromConfiguration) { vmDisksToAttach = vmFromConfiguration.getDiskMap().values(); clearVmDisks(vmFromConfiguration); getParameters().setCopyCollapse(true); } @Override public void executeVmCommand() { addAuditLogForPartialVMs(); super.executeVmCommand(); if (getSucceeded()) { if (isImagesAlreadyOnTarget()) { getImages().stream().forEach(diskImage -> { initQcowVersionForDisks(diskImage.getId()); }); unregisteredOVFDataDao.removeEntity(ovfEntityData.getEntityId(), null); unregisteredDisksDao.removeUnregisteredDiskRelatedToVM(ovfEntityData.getEntityId(), null); auditLogDirector.log(this, AuditLogType.VM_IMPORT_FROM_CONFIGURATION_EXECUTED_SUCCESSFULLY); } else if (!vmDisksToAttach.isEmpty()) { auditLogDirector.log(this, attemptToAttachDisksToImportedVm(vmDisksToAttach)); } } setActionReturnValue(getVm().getId()); } private void addAuditLogForPartialVMs() { if (getParameters().isAllowPartialImport() && !failedDisksToImportForAuditLog.isEmpty()) { addCustomValue("DiskAliases", StringUtils.join(failedDisksToImportForAuditLog.values(), ", ")); auditLogDirector.log(this, AuditLogType.IMPORTEXPORT_PARTIAL_VM_DISKS_NOT_EXISTS); } } private static void clearVmDisks(VM vm) { vm.setDiskMap(Collections.emptyMap()); vm.getImages().clear(); vm.getDiskList().clear(); } private AuditLogType attemptToAttachDisksToImportedVm(Collection<Disk> disks) { List<String> failedDisks = new LinkedList<>(); for (Disk disk : disks) { DiskVmElement dve = disk.getDiskVmElements().iterator().next(); AttachDetachVmDiskParameters params = new AttachDetachVmDiskParameters( new DiskVmElement(disk.getId(), getVm().getId()), dve.isPlugged(), dve.isReadOnly()); VdcReturnValueBase returnVal = runInternalAction(VdcActionType.AttachDiskToVm, params, cloneContextAndDetachFromParent()); if (!returnVal.getSucceeded()) { failedDisks.add(disk.getDiskAlias()); } } if (!failedDisks.isEmpty()) { this.addCustomValue("DiskAliases", StringUtils.join(failedDisks, ",")); return AuditLogType.VM_IMPORT_FROM_CONFIGURATION_ATTACH_DISKS_FAILED; } return AuditLogType.VM_IMPORT_FROM_CONFIGURATION_EXECUTED_SUCCESSFULLY; } @Override protected boolean validateAndSetVmFromExportDomain() { // We have the VM configuration so there is no need to get it from the export domain. return true; } @Override protected Guid getSourceDomainId(DiskImage image) { return image.getStorageIds().get(0); } }