package org.ovirt.engine.core.bll.network.vm; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; 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.VmCommand; import org.ovirt.engine.core.bll.context.CommandContext; import org.ovirt.engine.core.bll.hostdev.HostDeviceManager; import org.ovirt.engine.core.bll.network.ExternalNetworkManager; import org.ovirt.engine.core.bll.network.VmInterfaceManager; import org.ovirt.engine.core.bll.network.cluster.ManagementNetworkUtil; import org.ovirt.engine.core.bll.network.cluster.NetworkHelper; import org.ovirt.engine.core.bll.network.host.NetworkDeviceHelper; import org.ovirt.engine.core.bll.network.host.VfScheduler; import org.ovirt.engine.core.bll.provider.ProviderProxyFactory; import org.ovirt.engine.core.bll.provider.network.NetworkProviderProxy; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.FeatureSupported; import org.ovirt.engine.core.common.action.ActivateDeactivateVmNicParameters; import org.ovirt.engine.core.common.action.PlugAction; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.action.VdsActionParameters; import org.ovirt.engine.core.common.businessentities.Provider; 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.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.EngineException; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.common.vdscommands.VmNicDeviceVDSParameters; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dao.VmDeviceDao; import org.ovirt.engine.core.dao.network.InterfaceDao; import org.ovirt.engine.core.dao.network.VnicProfileDao; import org.ovirt.engine.core.dao.provider.ProviderDao; import org.ovirt.engine.core.utils.ReplacementUtils; import org.ovirt.engine.core.utils.StringMapUtils; import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsProperties; import org.ovirt.engine.core.vdsbroker.vdsbroker.VmInfoReturn; /** * Activate or deactivate a virtual network interface of a VM in case it is in a valid status. If the VM is down, simply * update the device, if it is Up - HotPlug / HotUnPlug the virtual network interface */ @NonTransactiveCommandAttribute public class ActivateDeactivateVmNicCommand<T extends ActivateDeactivateVmNicParameters> extends VmCommand<T> { private static List<VMStatus> ALLOWED_VM_STATES = Arrays.asList(VMStatus.Up, VMStatus.Down, VMStatus.ImageLocked, VMStatus.PoweringDown, VMStatus.PoweringUp); private static final List<VMStatus> VM_STATUSES_FOR_WHICH_HOT_PLUG_IS_REQUIRED = Arrays.asList(VMStatus.Up, VMStatus.PoweringUp); private VmDevice vmDevice; private VnicProfile vnicProfile; private Network network; private NetworkProviderProxy providerProxy; @Inject private ManagementNetworkUtil managementNetworkUtil; @Inject private VfScheduler vfScheduler; @Inject private NetworkDeviceHelper networkDeviceHelper; @Inject private HostDeviceManager hostDeviceManager; @Inject private VmDeviceDao vmDeviceDao; @Inject private VnicProfileDao vnicProfileDao; @Inject private ProviderDao providerDao; @Inject private InterfaceDao interfaceDao; public ActivateDeactivateVmNicCommand(T parameters, CommandContext commandContext) { super(parameters, commandContext); setVmId(parameters.getVmId()); } @Override protected boolean validate() { if (getVm() == null) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_VM_NOT_FOUND); return false; } if (!canRunActionOnNonManagedVm()) { return false; } if (getVm().isHostedEngine() && !getVm().isManagedHostedEngine()) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_UNMANAGED_HOSTED_ENGINE); return false; } if (getNetwork() != null && managementNetworkUtil.isManagementNetwork(getNetwork().getId(), getVm().getClusterId()) && getVm().isManagedHostedEngine()) { addValidationMessage(EngineMessage.DEACTIVATE_MANAGEMENT_NETWORK_FOR_HOSTED_ENGINE); return false; } if (!ALLOWED_VM_STATES.contains(getVm().getStatus())) { addValidationMessage(EngineMessage.ACTION_TYPE_FAILED_VM_STATUS_ILLEGAL); addValidationMessage(ReplacementUtils.createSetVariableString("vmStatus", getVm().getStatus())); return false; } if (hotPlugVmNicRequired(getVm().getStatus())) { setVdsId(getVm().getRunOnVds()); if (!isNicSupportedForPlugUnPlug()) { return false; } // External networks are handled by their provider, so only check if exists on host for internal networks. if (getNetwork() != null && !getNetwork().isExternal() && !isPassthrough() && !networkAttachedToVds(getNetwork().getName(), getVdsId())) { addValidationMessage(EngineMessage.ACTIVATE_DEACTIVATE_NETWORK_NOT_IN_VDS); return false; } if (isPassthrough()) { if (!checkSriovHotPlugSupported()) { return false; } if (getParameters().getAction() == PlugAction.PLUG) { if (getVfPreallocatedForNic() == null && findFreeVf() == null) { return failValidationCannotPlugPassthroughVnicNoSuitableVf(); } } } } vmDevice = vmDeviceDao.get(new VmDeviceId(getParameters().getNic().getId(), getParameters().getVmId())); if (vmDevice == null) { addValidationMessage(EngineMessage.VM_INTERFACE_NOT_EXIST); return false; } if (getParameters().getAction() == PlugAction.PLUG && !validate(macAvailable())) { return false; } return true; } private String getVfPreallocatedForNic() { Guid nicId = getParameters().getNic().getId(); Map<Guid, String> vnicToVfMap = vfScheduler.getVnicToVfMap(getVmId(), getVdsId()); if (vnicToVfMap == null) { return null; } return vnicToVfMap.get(nicId); } private String findFreeVf() { return vfScheduler.findFreeVfForVnic(getVdsId(), getNetwork(), getVmId()); } private boolean failValidationCannotPlugPassthroughVnicNoSuitableVf() { return failValidation(EngineMessage.CANNOT_PLUG_PASSTHROUGH_VNIC_NO_SUITABLE_VF, String.format("$vnicName %1$s", getInterfaceName())); } private boolean isPassthrough() { return VmInterfaceType.pciPassthrough == VmInterfaceType.forValue(getParameters().getNic().getType()); } public Network getNetwork() { if (getParameters().getNic().getVnicProfileId() != null && network == null) { vnicProfile = vnicProfileDao.get(getParameters().getNic().getVnicProfileId()); network = NetworkHelper.getNetworkByVnicProfile(vnicProfile); } return network; } public String getInterfaceName() { return getParameters().getNic().getName(); } public String getInterfaceType() { return VmInterfaceType.forValue(getParameters().getNic().getType()).getDescription(); } @Override protected void executeVmCommand() { switch (getParameters().getAction()) { case PLUG: plugNic(); break; case UNPLUG: unplugNic(); break; default: throw new RuntimeException("Coding error: unknown enum value"); } // In any case, the device is updated updateDevice(); setSucceeded(true); } private void plugNic() { clearAddressIfPciSlotIsDuplicated(vmDevice); // HotPlug in the host is called only if the Vm is UP if (hotPlugVmNicRequired(getVm().getStatus())) { boolean externalNetworkIsPlugged = getNetwork() != null && getNetwork().isExternal(); if (externalNetworkIsPlugged) { plugToExternalNetwork(); } String vfToUse = null; try { if (isPassthrough()) { String preallocatedVfForNic = getVfPreallocatedForNic(); boolean preallocatedVfExist = preallocatedVfForNic != null; if (preallocatedVfExist) { vfToUse = preallocatedVfForNic; } else { vfToUse = acquireVF(); if (vfToUse == null) { failValidationCannotPlugPassthroughVnicNoSuitableVf(); return; } networkDeviceHelper.setVmIdOnVfs(getVdsId(), getVmId(), Collections.singleton(vfToUse)); } vmDevice.setHostDevice(vfToUse); } if (executePlugOrUnplug(PlugAction.PLUG)) { if (isPassthrough()) { runInternalAction(VdcActionType.RefreshHost, new VdsActionParameters(getVdsId())); } } else { clearPassthroughData(vfToUse); } } catch (EngineException e) { if (externalNetworkIsPlugged && getParameters().isNewNic()) { unplugFromExternalNetwork(); } clearPassthroughData(vfToUse); throw e; } } } private void unplugNic() { if (hotPlugVmNicRequired(getVm().getStatus())) { if (executePlugOrUnplug(PlugAction.UNPLUG)) { if (isPassthrough()) { clearPassthroughData(vmDevice.getHostDevice()); runInternalAction(VdcActionType.RefreshHost, new VdsActionParameters(getVdsId())); } } } } private boolean executePlugOrUnplug(PlugAction action) { VDSReturnValue vdsReturnValue = runVdsCommand(action.getvNicVdsCommandType(), new VmNicDeviceVDSParameters(getVdsId(), getVm(), getParameters().getNic(), vmDevice)); updateVmDeviceWithDataReturnedFromHost(vdsReturnValue); return vdsReturnValue.getSucceeded(); } private void updateVmDeviceWithDataReturnedFromHost(VDSReturnValue vdsReturnValue) { if (vdsReturnValue.getSucceeded() && getParameters().getAction() == PlugAction.PLUG) { VmInfoReturn vmInfoReturn = (VmInfoReturn) vdsReturnValue.getReturnValue(); if (vmInfoReturn.getVmInfo() != null) { Map<String, Object> vmInfo = (Map<String, Object>) vmInfoReturn.getVmInfo(); for (Object o : (Object[]) vmInfo.get(VdsProperties.Devices)) { Map<String, Object> vdsmDevice = (Map<String, Object>) o; if (vmDevice.getId().getDeviceId().toString().equals(vdsmDevice.get(VdsProperties.DeviceId))) { vmDevice.setAddress(vdsmDevice.get(VdsProperties.Address).toString()); vmDevice.setAlias(StringUtils.defaultString((String) vdsmDevice.get(VdsProperties.Alias))); break; } } } } } private String acquireVF() { try { hostDeviceManager.acquireHostDevicesLock(getVdsId()); return findFreeVf(); } finally { hostDeviceManager.releaseHostDevicesLock(getVdsId()); } } private void clearPassthroughData(String vfToUse) { if (vfToUse != null) { networkDeviceHelper.setVmIdOnVfs(getVdsId(), null, Collections.singleton(vfToUse)); } } private void clearAddressIfPciSlotIsDuplicated(VmDevice vmDeviceToHotplug) { if (searchForDuplicatesWithExistingVmDevices(vmDeviceToHotplug)){ vmDeviceToHotplug.setAddress(""); } } private boolean searchForDuplicatesWithExistingVmDevices(VmDevice vmDeviceToHotplug){ String deviceAddress = vmDeviceToHotplug.getAddress(); if (StringUtils.isEmpty(deviceAddress)){ return false; } Map<String, String> addressMapToHotplug = StringMapUtils.string2Map(deviceAddress); List<VmDevice> allVmDevices = vmDeviceDao.getVmDeviceByVmId(getVm().getId()); for (VmDevice vmDevice : allVmDevices) { if (!vmDeviceToHotplug.getId().equals(vmDevice.getId())){ Map<String, String> deviceAddressMap = StringMapUtils.string2Map(vmDevice.getAddress()); boolean duplicatedAddress = deviceAddressMap.equals(addressMapToHotplug); boolean ambiguousInterfaceState = StringUtils.isEmpty(vmDevice.getAddress()) && vmDevice.isPlugged() && VmDeviceGeneralType.INTERFACE.equals(vmDevice.getType()); if(duplicatedAddress || ambiguousInterfaceState) { return true; } } } return false; } private void plugToExternalNetwork() { Map<String, String> runtimeProperties = getProviderProxy().allocate(getNetwork(), vnicProfile, getParameters().getNic(), getVds()); if (runtimeProperties != null) { getVm().getRuntimeDeviceCustomProperties().put(vmDevice.getId(), runtimeProperties); } } private void unplugFromExternalNetwork() { new ExternalNetworkManager(getParameters().getNic(), getNetwork()).deallocateIfExternal(); } private NetworkProviderProxy getProviderProxy() { if (providerProxy == null) { Provider<?> provider = providerDao.get(getNetwork().getProvidedBy().getProviderId()); providerProxy = ProviderProxyFactory.getInstance().create(provider); } return providerProxy; } private void updateDevice() { vmDevice.setPlugged(getParameters().getAction() == PlugAction.PLUG); vmDeviceDao.update(vmDevice); } @Override protected void setActionMessageParameters() { addValidationMessage((getParameters().getAction() == PlugAction.PLUG) ? EngineMessage.VAR__ACTION__ACTIVATE : EngineMessage.VAR__ACTION__DEACTIVATE); addValidationMessage(EngineMessage.VAR__TYPE__INTERFACE); } @Override public AuditLogType getAuditLogTypeValue() { if (getParameters().getAction() == PlugAction.PLUG) { return getSucceeded() ? AuditLogType.NETWORK_ACTIVATE_VM_INTERFACE_SUCCESS : AuditLogType.NETWORK_ACTIVATE_VM_INTERFACE_FAILURE; } else { return getSucceeded() ? AuditLogType.NETWORK_DEACTIVATE_VM_INTERFACE_SUCCESS : AuditLogType.NETWORK_DEACTIVATE_VM_INTERFACE_FAILURE; } } private boolean networkAttachedToVds(String networkName, Guid vdsId) { List<VdsNetworkInterface> listOfInterfaces = interfaceDao.getAllInterfacesForVds(vdsId); for (VdsNetworkInterface vdsNetworkInterface : listOfInterfaces) { if (networkName.equals(vdsNetworkInterface.getNetworkName())) { return true; } } return false; } private boolean hotPlugVmNicRequired(VMStatus vmStatus) { return VM_STATUSES_FOR_WHICH_HOT_PLUG_IS_REQUIRED.contains(vmStatus); } protected ValidationResult macAvailable() { VmNic nic = getParameters().getNic(); EngineMessage failMessage = EngineMessage.NETWORK_MAC_ADDRESS_IN_USE; return ValidationResult .failWith(failMessage, ReplacementUtils.getVariableAssignmentString(failMessage, nic.getMacAddress())) .when(new VmInterfaceManager().tooManyPluggedInterfaceWithSameMac(nic, getMacPool())); } protected boolean checkSriovHotPlugSupported() { if (!FeatureSupported.sriovHotPlugSupported(getVm().getClusterCompatibilityVersion())) { return failValidation(EngineMessage.HOT_PLUG_UNPLUG_PASSTHROUGH_VNIC_NOT_SUPPORTED); } return true; } }