package org.ovirt.engine.core.bll.network.vm; import java.util.ArrayList; import java.util.List; import java.util.Objects; 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.ExternalNetworkManager; import org.ovirt.engine.core.bll.network.cluster.NetworkHelper; import org.ovirt.engine.core.bll.network.macpool.MacPool; import org.ovirt.engine.core.bll.utils.PermissionSubject; import org.ovirt.engine.core.bll.validator.VmNicValidator; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.AddVmInterfaceParameters; import org.ovirt.engine.core.common.action.PlugAction; import org.ovirt.engine.core.common.businessentities.VMStatus; import org.ovirt.engine.core.common.businessentities.VmDevice; import org.ovirt.engine.core.common.businessentities.VmDeviceId; import org.ovirt.engine.core.common.businessentities.network.Network; import org.ovirt.engine.core.common.businessentities.network.VdsNetworkInterface; import org.ovirt.engine.core.common.businessentities.network.VmInterfaceType; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.network.VnicProfile; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.utils.VmDeviceType; import org.ovirt.engine.core.common.validation.group.UpdateVmNic; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VmNicDeviceVDSParameters; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.core.dao.VmDeviceDao; import org.ovirt.engine.core.dao.VmDynamicDao; import org.ovirt.engine.core.dao.network.InterfaceDao; import org.ovirt.engine.core.dao.network.VmNicDao; import org.ovirt.engine.core.dao.network.VnicProfileDao; import org.ovirt.engine.core.utils.transaction.TransactionSupport; @NonTransactiveCommandAttribute(forceCompensation = true) public class UpdateVmInterfaceCommand<T extends AddVmInterfaceParameters> extends AbstractVmInterfaceCommand<T> { private VmNic oldIface; private VmDevice oldVmDevice; private boolean macShouldBeChanged; private RequiredAction requiredAction = null; @Inject private VmDeviceDao vmDeviceDao; @Inject private VmNicDao vmNicDao; @Inject private VmDynamicDao vmDynamicDao; @Inject private InterfaceDao interfaceDao; @Inject private VnicProfileDao vnicProfileDao; public UpdateVmInterfaceCommand(T parameters, CommandContext cmdContext) { super(parameters, cmdContext); setVmId(parameters.getVmId()); } public UpdateVmInterfaceCommand(Guid commandId) { super(commandId); } private RequiredAction getRequiredAction() { if (requiredAction == null) { if (!oldVmDevice.isPlugged() && getInterface().isPlugged()) { requiredAction = RequiredAction.PLUG; } else if (oldVmDevice.isPlugged() && !getInterface().isPlugged()) { requiredAction = RequiredAction.UNPLUG; } else if (liveActionRequired() && propertiesRequiringVmUpdateDeviceWereUpdated()) { requiredAction = RequiredAction.UPDATE_VM_DEVICE; } } return requiredAction; } private boolean liveActionRequired() { return oldVmDevice.isPlugged() && getInterface().isPlugged() && getVm().getStatus() == VMStatus.Up; } @Override protected void executeVmCommand() { addCustomValue("InterfaceType", VmInterfaceType.forValue(getInterface().getType()).getDescription().toString()); boolean succeeded = false; boolean macAddedToPool = false; try { if (isVnicProfileChanged(oldIface, getInterface())) { Network newNetwork = NetworkHelper.getNetworkByVnicProfileId(getInterface().getVnicProfileId()); Network oldNetwork = NetworkHelper.getNetworkByVnicProfileId(oldIface.getVnicProfileId()); if (!Objects.equals(oldNetwork, newNetwork)) { new ExternalNetworkManager(oldIface).deallocateIfExternal(); } } if (macShouldBeChanged) { macAddedToPool = addMacToPool(getMacAddress()); } if (mustChangeAddress(oldIface.getType(), getInterface().getType())) { vmDeviceDao.clearDeviceAddress(getInterface().getId()); } getInterface().setSpeed(VmInterfaceType.forValue(getInterface().getType()).getSpeed()); TransactionSupport.executeInNewTransaction(() -> { bumpVmVersion(); updatePassthoughDeviceIfNeeded(); getCompensationContext().snapshotEntity(oldIface); vmNicDao.update(getInterface()); getCompensationContext().stateChanged(); return null; }); succeeded = updateHost(); } finally { setSucceeded(succeeded); MacPool macPool = getMacPool(); if (macAddedToPool) { if (succeeded) { macPool.freeMac(oldIface.getMacAddress()); } else { macPool.freeMac(getMacAddress()); } } } } private boolean updateHost() { if (getVm().getStatus() == VMStatus.Up) { setVdsId(getVm().getRunOnVds()); } if (getRequiredAction() != null){ switch (getRequiredAction()) { case PLUG: return activateOrDeactivateExistingNic(getInterface(), PlugAction.PLUG); case UNPLUG: return activateOrDeactivateExistingNic(oldIface, PlugAction.UNPLUG); case UPDATE_VM_DEVICE: runVdsCommand(VDSCommandType.UpdateVmInterface, new VmNicDeviceVDSParameters(getVdsId(), getVm(), vmNicDao.get(getInterface().getId()), oldVmDevice)); break; } } return true; } private boolean activateOrDeactivateExistingNic(VmNic nic, PlugAction plugAction) { return activateOrDeactivateNic(nic, plugAction, false); } private boolean propertiesRequiringVmUpdateDeviceWereUpdated() { return !Objects.equals(oldIface.getVnicProfileId(), getInterface().getVnicProfileId()) || oldIface.isLinked() != getInterface().isLinked(); } private void updatePassthoughDeviceIfNeeded() { if (mustChangeVmDevice()) { getCompensationContext().snapshotEntity(oldVmDevice); oldVmDevice.setDevice(getInterface().isPassthrough() ? VmDeviceType.HOST_DEVICE.getName() : VmDeviceType.BRIDGE.getName()); vmDeviceDao.update(oldVmDevice); } } @Override protected boolean validate() { if (getVm() == null) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND); return false; } if (!canRunActionOnNonManagedVm()) { return false; } if (!validate(vmStatusLegal(getVm().getStatus()))) { return false; } oldVmDevice = vmDeviceDao.get(new VmDeviceId(getInterface().getId(), getVmId())); List<VmNic> interfaces = vmNicDao.getAllForVm(getVmId()); oldIface = interfaces.stream().filter(i -> i.getId().equals(getInterface().getId())).findFirst().orElse(null); if (oldIface == null || oldVmDevice == null) { addValidationMessage(EngineMessage.VM_INTERFACE_NOT_EXIST); return false; } if (!updateVnicForBackwardCompatibility(oldIface)) { return false; } if (!StringUtils.equals(oldIface.getName(), getInterfaceName()) && !uniqueInterfaceName(interfaces)) { return false; } // check that not exceeded PCI and IDE limit List<VmNic> allInterfaces = new ArrayList<>(interfaces); allInterfaces.remove(oldIface); allInterfaces.add(getInterface()); if (!pciAndIdeWithinLimit(getVm(), allInterfaces)) { return false; } if (!validate(vmTemplateEmpty())) { return false; } UpdateVmNicValidator nicValidator = new UpdateVmNicValidator(getInterface(), getVm().getClusterCompatibilityVersion(), getVm().getOs()); if (!validate(nicValidator.unplugPlugNotRequired()) || !validate(nicValidator.isCompatibleWithOs()) || !validate(nicValidator.hotUpdatePossible()) || !validate(nicValidator.profileValid(getVm().getClusterId())) || !validate(nicValidator.canVnicWithExternalNetworkBePlugged()) || !validate(nicValidator.typeMatchesProfile()) || !validate(nicValidator.passthroughIsLinked()) || !validate(nicValidator.validateProfileNotEmptyForHostedEngineVm(getVm()))) { return false; } Network network = NetworkHelper.getNetworkByVnicProfileId(getInterface().getVnicProfileId()); if (getRequiredAction() == RequiredAction.UPDATE_VM_DEVICE) { Network oldNetwork = NetworkHelper.getNetworkByVnicProfileId(oldIface.getVnicProfileId()); if (!validate(nicValidator.hotUpdateDoneWithInternalNetwork(oldNetwork, network)) || !validate(nicValidator.networkExistsOnHost(network))) { return false; } } macShouldBeChanged = !StringUtils.equals(oldIface.getMacAddress(), getMacAddress()); if (macShouldBeChanged && !validate(macAvailable())) { return false; } return true; } @Override protected List<Class<?>> getValidationGroups() { addValidationGroup(UpdateVmNic.class); return super.getValidationGroups(); } /** * Set the parameters for bll messages, such as type and action, */ @Override protected void setActionMessageParameters() { super.setActionMessageParameters(); addValidationMessage(EngineMessage.VAR__ACTION__UPDATE); } @Override public AuditLogType getAuditLogTypeValue() { if (getSucceeded()) { if (oldIface.isLinked() != getInterface().isLinked()) { AuditLogType customValue = getInterface().isLinked() ? AuditLogType.NETWORK_UPDATE_VM_INTERFACE_LINK_UP : AuditLogType.NETWORK_UPDATE_VM_INTERFACE_LINK_DOWN; addCustomValue("LinkState", auditLogDirector.getMessage(customValue)); } else { addCustomValue("LinkState", " "); } return AuditLogType.NETWORK_UPDATE_VM_INTERFACE; } return AuditLogType.NETWORK_UPDATE_VM_INTERFACE_FAILED; } @Override public List<PermissionSubject> getPermissionCheckSubjects() { List<PermissionSubject> permissionList = super.getPermissionCheckSubjects(); if (getInterface() != null && getInterface().getVnicProfileId() != null && getVm() != null) { VmNic oldNic = vmNicDao.get(getInterface().getId()); if (oldNic == null || isVnicProfileChanged(oldNic, getInterface())) { permissionList.add(new PermissionSubject(getInterface().getVnicProfileId(), VdcObjectType.VnicProfile, getActionType().getActionGroup())); } } return permissionList; } /** * Check if address must be changed after change NIC type * @param oldType - Old nic type * @param newType - New nic type */ private boolean mustChangeAddress (int oldType, int newType) { int spaprVlanType = VmInterfaceType.spaprVlan.getValue(); return wasTypeChangedToOrFromTestedType(oldType, newType, spaprVlanType); } private boolean mustChangeVmDevice() { int passthroughType = VmInterfaceType.pciPassthrough.getValue(); return wasTypeChangedToOrFromTestedType(oldIface.getType(), getInterface().getType(), passthroughType); } private boolean wasTypeChangedToOrFromTestedType(int oldType, int newType, int testedType) { return oldType == testedType ^ newType == testedType; } private boolean isVnicProfileChanged(VmNic oldNic, VmNic newNic) { return !Objects.equals(oldNic.getVnicProfileId(), newNic.getVnicProfileId()); } private enum RequiredAction { PLUG, UNPLUG, UPDATE_VM_DEVICE } /** * Internal validator that adds checks specific to this class, but uses info from the {@link VmNicValidator}. */ private class UpdateVmNicValidator extends VmNicValidator { public UpdateVmNicValidator(VmNic nic, Version version, int osId) { super(nic, version, osId); } public ValidationResult networkExistsOnHost(Network network) { if (network == null) { return ValidationResult.VALID; } Guid vdsId = vmDynamicDao.get(nic.getVmId()).getRunOnVds(); List<VdsNetworkInterface> hostNics = interfaceDao.getAllInterfacesForVds(vdsId); for (VdsNetworkInterface hostNic : hostNics) { if (network.getName().equals(hostNic.getNetworkName())) { return ValidationResult.VALID; } } return new ValidationResult(EngineMessage.ACTIVATE_DEACTIVATE_NETWORK_NOT_IN_VDS); } /** * @return An error if hot updated is needed, and either network linking is not supported or the NIC has port * mirroring set. */ public ValidationResult hotUpdatePossible() { if (getRequiredAction() == RequiredAction.UPDATE_VM_DEVICE) { if (portMirroringEnabled(getInterface().getVnicProfileId()) || portMirroringEnabled(oldIface.getVnicProfileId())) { return new ValidationResult(EngineMessage.CANNOT_PERFORM_HOT_UPDATE_WITH_PORT_MIRRORING); } } return ValidationResult.VALID; } private boolean portMirroringEnabled(Guid profileId) { VnicProfile vnicProfile = profileId == null ? null : vnicProfileDao.get(profileId); return vnicProfile != null && vnicProfile.isPortMirroring(); } /** * @return An error if live action is required and the properties requiring the NIC to be unplugged and then * plugged again have changed. */ public ValidationResult unplugPlugNotRequired() { return liveActionRequired() && propertiesRequiringUnplugPlugWereUpdated() ? new ValidationResult(EngineMessage.CANNOT_PERFORM_HOT_UPDATE) : ValidationResult.VALID; } private boolean propertiesRequiringUnplugPlugWereUpdated() { return !oldIface.getType().equals(getInterface().getType()) || !oldIface.getMacAddress().equals(getMacAddress()); } /** * @param oldNetwork * The old network (can be <code>null</code>). * @param newNetwork * The new network (can be <code>null</code>). * @return An error if either the old or new network is an external network, otherwise hot update is allowed. */ public ValidationResult hotUpdateDoneWithInternalNetwork(Network oldNetwork, Network newNetwork) { return (oldNetwork == null || !oldNetwork.isExternal()) && (newNetwork == null || !newNetwork.isExternal()) ? ValidationResult.VALID : new ValidationResult(EngineMessage.ACTION_TYPE_FAILED_EXTERNAL_NETWORK_CANNOT_BE_REWIRED); } public ValidationResult canVnicWithExternalNetworkBePlugged() { return ValidationResult.failWith(EngineMessage.PLUGGED_UNLINKED_VM_INTERFACE_WITH_EXTERNAL_NETWORK_IS_NOT_SUPPORTED) .when(RequiredAction.PLUG == getRequiredAction() && !nic.isLinked() && isVnicAttachedToExternalNetwork()); } private boolean isVnicAttachedToExternalNetwork() { final Network network = getNetwork(); return network != null && network.isExternal(); } } }