Roy Golan has uploaded a new change for review. Change subject: core: HostMonitoring - handle network exception from all verbs ......................................................................
core: HostMonitoring - handle network exception from all verbs Since host monitoring is separated from VMs monitoring the Host monitoring never detects a failure after its up. To compensate and keep the behaviour(and expand it a bit) all NetworkException are throwing CDI-Events. Those events are handled by the VdsEventListener to invoke the VdsManager.handleNetworkException Bug-Url: https://bugzilla.redhat.com/show_bug.cgi?id=1099068 Change-Id: I59d26baa4d372905c8249edb0aabe28ea4c4a74a Signed-off-by: Roy Golan <rgo...@redhat.com> --- M backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsEventListener.java M backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VDSError.java M backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/VDSCommandType.java M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/HostMonitoring.java M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/ResourceManager.java M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsIdVDSCommandBase.java M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsManager.java M backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VdsBrokerCommand.java M backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/HostMonitoringTest.java A backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/VmStatisticsFetcherTest.java 10 files changed, 111 insertions(+), 56 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/21/35521/1 diff --git a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsEventListener.java b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsEventListener.java index ea06d14..7c4744b 100644 --- a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsEventListener.java +++ b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/VdsEventListener.java @@ -8,11 +8,9 @@ import java.util.Map; import java.util.concurrent.Callable; -import javax.ejb.DependsOn; -import javax.ejb.Local; -import javax.ejb.Stateless; -import javax.ejb.TransactionAttribute; -import javax.ejb.TransactionAttributeType; +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.inject.Singleton; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.bll.context.CommandContext; @@ -81,14 +79,16 @@ import org.ovirt.engine.core.vdsbroker.MonitoringStrategyFactory; import org.ovirt.engine.core.vdsbroker.ResourceManager; import org.ovirt.engine.core.vdsbroker.irsbroker.IrsBrokerCommand; +import org.ovirt.engine.core.vdsbroker.vdsbroker.VDSNetworkException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@Stateless(name = "VdsEventListener") -@TransactionAttribute(TransactionAttributeType.SUPPORTS) -@Local(IVdsEventListener.class) -@DependsOn("Backend") +@Singleton public class VdsEventListener implements IVdsEventListener { + + @Inject + ResourceManager resourceManager; + private static final Logger log = LoggerFactory.getLogger(VdsEventListener.class); @Override @@ -99,7 +99,6 @@ ExecutionHandler.updateSpecificActionJobCompleted(vds.getId(), VdcActionType.MaintenanceVds, true); } } - private void processStorageOnVdsInactive(final VDS vds) { @@ -519,4 +518,13 @@ } }); } + + public void onError(@Observes final VDSNetworkException vdsException) { + ThreadPoolUtil.execute(new Runnable() { + @Override public void run() { + resourceManager.GetVdsManager( + vdsException.getVdsError().getVdsId()).handleNetworkException(vdsException); + } + }); + } } diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VDSError.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VDSError.java index 8181ff8..3279f43 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VDSError.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/errors/VDSError.java @@ -1,5 +1,7 @@ package org.ovirt.engine.core.common.errors; +import org.ovirt.engine.core.compat.Guid; + import java.util.ArrayList; public class VDSError { @@ -7,6 +9,7 @@ private String message; private VdcBllErrors code; private ArrayList<Object> args; + private Guid vdsId; public VDSError(VdcBllErrors code, String message) { this.code = code; @@ -41,4 +44,12 @@ public VDSError() { code = VdcBllErrors.forValue(0); } + + public Guid getVdsId() { + return vdsId; + } + + public void setVdsId(Guid vdsId) { + this.vdsId = vdsId; + } } diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/VDSCommandType.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/VDSCommandType.java index bdbaafc..2c41867 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/VDSCommandType.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/vdscommands/VDSCommandType.java @@ -166,7 +166,11 @@ List("org.ovirt.engine.core.vdsbroker.vdsbroker"), // get a list of VMs with status only GetVmStats("org.ovirt.engine.core.vdsbroker.vdsbroker"), // get a VM with full data and statistics GetAllVmStats("org.ovirt.engine.core.vdsbroker.vdsbroker"), // get a list of VMs with full data and statistics - Destroy("org.ovirt.engine.core.vdsbroker.vdsbroker"); // Clean a DOWN VM from the vms list + Destroy("org.ovirt.engine.core.vdsbroker.vdsbroker"), // Clean a DOWN VM from the vms list + /** + * Get Host capabilities + */ + GetCapabilities("org.ovirt.engine.core.vdsbroker.vdsbroker"); String packageName; diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/HostMonitoring.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/HostMonitoring.java index 93f3b56..cb0c6cd 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/HostMonitoring.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/HostMonitoring.java @@ -137,7 +137,7 @@ new SetVdsStatusVDSCommandParameters(vds.getId(), VDSStatus.Error)); } } catch (VDSNetworkException e) { - saveVdsDynamic = vdsManager.handleNetworkException(e, vds); + saveVdsDynamic = vdsManager.handleNetworkException(e); throw e; } catch (Throwable t) { log.error("Failure to refresh Vds runtime info: {}", t.getMessage()); @@ -480,7 +480,7 @@ ? statsReturnValue.getExceptionObject() : null); if (ex != null) { - if (vdsManager.handleNetworkException(ex, vds)) { + if (vdsManager.handleNetworkException(ex)) { saveVdsDynamic = true; } log.error("vds::refreshVdsStats Failed getVdsStats, vds='{}'({}): {}", diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/ResourceManager.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/ResourceManager.java index 5b6f76d..cecd678 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/ResourceManager.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/ResourceManager.java @@ -10,6 +10,14 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.PostConstruct; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.InjectionTarget; +import javax.inject.Inject; +import javax.inject.Singleton; + import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.businessentities.IVdsEventListener; @@ -39,20 +47,17 @@ import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableBase; import org.ovirt.engine.core.utils.ReflectionUtils; import org.ovirt.engine.core.utils.collections.MultiValueMapUtils; -import org.ovirt.engine.core.utils.ejb.BeanProxyType; -import org.ovirt.engine.core.utils.ejb.BeanType; -import org.ovirt.engine.core.utils.ejb.EjbUtils; import org.ovirt.engine.core.vdsbroker.irsbroker.IrsBrokerCommand; import org.ovirt.engine.core.vdsbroker.vdsbroker.FutureVDSCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.PostConstruct; -import javax.enterprise.event.Observes; -import javax.inject.Singleton; - @Singleton public class ResourceManager { + + @Inject + private BeanManager beanManager; + private IVdsEventListener eventListener; private static ResourceManager instance; private final Map<Guid, HashSet<Guid>> vdsAndVmsList = new ConcurrentHashMap<>(); @@ -192,7 +197,13 @@ } public IVdsEventListener getEventListener() { - return EjbUtils.findBean(BeanType.VDS_EVENT_LISTENER, BeanProxyType.LOCAL); + if (eventListener == null) { + Bean<?> bean = beanManager.getBeans(IVdsEventListener.class).iterator().next(); + eventListener = (IVdsEventListener) beanManager.getReference( + bean, + bean.getBeanClass(), beanManager.createCreationalContext(bean)); + } + return eventListener; } public void reestablishConnection(Guid vdsId) { @@ -394,7 +405,11 @@ ReflectionUtils.findConstructor(type, parameters.getClass()); if (constructor != null) { - return constructor.newInstance(new Object[] { parameters }); + VDSCommandBase<P> cmd = constructor.newInstance(new Object[] { parameters }); + InjectionTarget injectionTarget = + beanManager.createInjectionTarget(beanManager.createAnnotatedType(cmd.getClass())); + injectionTarget.inject(cmd, beanManager.createCreationalContext(null)); + return cmd; } } catch (Exception e) { if (e.getCause() != null) { diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsIdVDSCommandBase.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsIdVDSCommandBase.java index 7c9720d..80ff0ca 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsIdVDSCommandBase.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsIdVDSCommandBase.java @@ -44,8 +44,11 @@ @Override protected void executeVDSCommand() { if (_vdsManager != null) { - synchronized (_vdsManager.getLockObj()) { + try { + _vdsManager.getLockObj().lock(); executeVdsIdCommand(); + } finally { + _vdsManager.getLockObj().unlock(); } } else { executeVdsIdCommand(); diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsManager.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsManager.java index 8254179..32a24fd 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsManager.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/VdsManager.java @@ -10,6 +10,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.FeatureSupported; @@ -53,7 +55,6 @@ import org.ovirt.engine.core.utils.transaction.TransactionSupport; import org.ovirt.engine.core.vdsbroker.irsbroker.IRSErrorException; import org.ovirt.engine.core.vdsbroker.irsbroker.IrsBrokerCommand; -import org.ovirt.engine.core.vdsbroker.vdsbroker.GetCapabilitiesVDSCommand; import org.ovirt.engine.core.vdsbroker.vdsbroker.HostNetworkTopologyPersister; import org.ovirt.engine.core.vdsbroker.vdsbroker.HostNetworkTopologyPersisterImpl; import org.ovirt.engine.core.vdsbroker.vdsbroker.IVdsServer; @@ -67,7 +68,7 @@ private static Logger log = LoggerFactory.getLogger(VdsManager.class); private static Map<Guid, String> recoveringJobIdMap = new ConcurrentHashMap<Guid, String>(); private final int numberRefreshesBeforeSave = Config.<Integer> getValue(ConfigValues.NumberVmRefreshesBeforeSave); - private final Object lockObj = new Object(); + private final Lock lockObj = new ReentrantLock(); private final AtomicInteger mFailedToRunVmAttempts; private final AtomicInteger mUnrespondedAttempts; private final AtomicBoolean sshSoftFencingExecuted; @@ -593,10 +594,10 @@ public VDSStatus refreshCapabilities(AtomicBoolean processHardwareCapsNeeded, VDS vds) { log.debug("monitoring: refresh '{}' capabilities", vds); VDS oldVDS = vds.clone(); - GetCapabilitiesVDSCommand<VdsIdAndVdsVDSCommandParametersBase> vdsBrokerCommand = - new GetCapabilitiesVDSCommand<VdsIdAndVdsVDSCommandParametersBase>(new VdsIdAndVdsVDSCommandParametersBase(vds)); - vdsBrokerCommand.execute(); - if (vdsBrokerCommand.getVDSReturnValue().getSucceeded()) { + VDSReturnValue caps = ResourceManager.getInstance().runVdsCommand( + VDSCommandType.GetCapabilities, + new VdsIdAndVdsVDSCommandParametersBase(vds)); + if (caps.getSucceeded()) { // Verify version capabilities HashSet<Version> hostVersions = null; Version clusterCompatibility = vds.getVdsGroupCompatibilityVersion(); @@ -659,19 +660,19 @@ processHardwareCapsNeeded.set(monitoringStrategy.processHardwareCapabilitiesNeeded(oldVDS, vds)); return returnStatus; - } else if (vdsBrokerCommand.getVDSReturnValue().getExceptionObject() != null) { + } else if (caps.getExceptionObject() != null) { // if exception is VDSNetworkException then call to // handleNetworkException - if (vdsBrokerCommand.getVDSReturnValue().getExceptionObject() instanceof VDSNetworkException - && handleNetworkException((VDSNetworkException) vdsBrokerCommand.getVDSReturnValue() - .getExceptionObject(), vds)) { + if (caps.getExceptionObject() instanceof VDSNetworkException + && handleNetworkException((VDSNetworkException) caps + .getExceptionObject())) { updateDynamicData(vds.getDynamicData()); updateStatisticsData(vds.getStatisticsData()); } - throw vdsBrokerCommand.getVDSReturnValue().getExceptionObject(); + throw caps.getExceptionObject(); } else { log.error("refreshCapabilities:GetCapabilitiesVDSCommand failed with no exception!"); - throw new RuntimeException(vdsBrokerCommand.getVDSReturnValue().getExceptionString()); + throw new RuntimeException(caps.getExceptionString()); } } @@ -700,49 +701,49 @@ * @param ex * @return */ - public boolean handleNetworkException(VDSNetworkException ex, VDS vds) { - if (vds.getStatus() != VDSStatus.Down) { - long timeoutToFence = calcTimeoutToFence(vds.getVmCount(), vds.getSpmStatus()); + public boolean handleNetworkException(VDSNetworkException ex) { + if (cachedVds.getStatus() != VDSStatus.Down) { + long timeoutToFence = calcTimeoutToFence(cachedVds.getVmCount(), cachedVds.getSpmStatus()); log.warn("Host '{}' is not responding. It will stay in Connecting state for a grace period " + - "of {} seconds and after that an attempt to fence the host will be issued.", - vds.getName(), - TimeUnit.MILLISECONDS.toSeconds(timeoutToFence)); + "of {} seconds and after that an attempt to fence the host will be issued.", + cachedVds.getName(), + TimeUnit.MILLISECONDS.toSeconds(timeoutToFence)); AuditLogableBase logable = new AuditLogableBase(); - logable.setVdsId(vds.getId()); + logable.setVdsId(cachedVds.getId()); logable.addCustomValue("Seconds", Long.toString(TimeUnit.MILLISECONDS.toSeconds(timeoutToFence))); AuditLogDirector.log(logable, AuditLogType.VDS_HOST_NOT_RESPONDING_CONNECTING); - if (mUnrespondedAttempts.get() < Config.<Integer> getValue(ConfigValues.VDSAttemptsToResetCount) + if (mUnrespondedAttempts.get() < Config.<Integer>getValue(ConfigValues.VDSAttemptsToResetCount) || (lastUpdate + timeoutToFence) > System.currentTimeMillis()) { boolean result = false; - if (vds.getStatus() != VDSStatus.Connecting && vds.getStatus() != VDSStatus.PreparingForMaintenance - && vds.getStatus() != VDSStatus.NonResponsive) { - setStatus(VDSStatus.Connecting, vds); + if (cachedVds.getStatus() != VDSStatus.Connecting && cachedVds.getStatus() != VDSStatus.PreparingForMaintenance + && cachedVds.getStatus() != VDSStatus.NonResponsive) { + setStatus(VDSStatus.Connecting, cachedVds); result = true; } mUnrespondedAttempts.incrementAndGet(); return result; } - if (vds.getStatus() == VDSStatus.NonResponsive || vds.getStatus() == VDSStatus.Maintenance) { - setStatus(VDSStatus.NonResponsive, vds); + if (cachedVds.getStatus() == VDSStatus.NonResponsive || cachedVds.getStatus() == VDSStatus.Maintenance) { + setStatus(VDSStatus.NonResponsive, cachedVds); return true; } - setStatus(VDSStatus.NonResponsive, vds); + setStatus(VDSStatus.NonResponsive, cachedVds); moveVMsToUnknown(); log.info( "Server failed to respond, vds_id='{}', vds_name='{}', vm_count={}, " + - "spm_status='{}', non-responsive_timeout (seconds)={}, error: {}", - vds.getId(), vds.getName(), vds.getVmCount(), vds.getSpmStatus(), + "spm_status='{}', non-responsive_timeout (seconds)={}, error: {}", + cachedVds.getId(), cachedVds.getName(), cachedVds.getVmCount(), cachedVds.getSpmStatus(), TimeUnit.MILLISECONDS.toSeconds(timeoutToFence), ex.getMessage()); - logable = new AuditLogableBase(vds.getId()); + logable = new AuditLogableBase(cachedVds.getId()); logable.updateCallStackFromThrowable(ex); AuditLogDirector.log(logable, AuditLogType.VDS_FAILURE); boolean executeSshSoftFencing = false; if (!sshSoftFencingExecuted.getAndSet(true)) { executeSshSoftFencing = true; } - ResourceManager.getInstance().getEventListener().vdsNotResponding(vds, executeSshSoftFencing, lastUpdate); + ResourceManager.getInstance().getEventListener().vdsNotResponding(cachedVds, executeSshSoftFencing, lastUpdate); } return true; } @@ -924,7 +925,7 @@ return (refreshIteration == numberRefreshesBeforeSave); } - public Object getLockObj() { + public Lock getLockObj() { return lockObj; } diff --git a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VdsBrokerCommand.java b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VdsBrokerCommand.java index 92a90bc..8a3f24d 100644 --- a/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VdsBrokerCommand.java +++ b/backend/manager/modules/vdsbroker/src/main/java/org/ovirt/engine/core/vdsbroker/vdsbroker/VdsBrokerCommand.java @@ -13,10 +13,15 @@ import org.ovirt.engine.core.vdsbroker.VdsManager; import org.ovirt.engine.core.vdsbroker.xmlrpc.XmlRpcRunTimeException; +import javax.enterprise.event.Event; +import javax.inject.Inject; + public abstract class VdsBrokerCommand<P extends VdsIdVDSCommandParametersBase> extends BrokerCommandBase<P> { private final IVdsServer mVdsBroker; private VdsStatic vdsStatic; private VDS vds; + @Inject + Event<VDSNetworkException> networkError; /** * Construct the command using the parameters and the {@link VDS} which is loaded from the DB. * @@ -100,6 +105,7 @@ } catch (XmlRpcRunTimeException ex) { VDSNetworkException networkException = createNetworkException(ex); printReturnValue(); + networkError.fire(networkException); throw networkException; } @@ -130,7 +136,9 @@ networkException = new VDSNetworkException(ex); message = ex.getMessage(); } - networkException.setVdsError(new VDSError(VdcBllErrors.VDS_NETWORK_ERROR, message)); + VDSError value = new VDSError(VdcBllErrors.VDS_NETWORK_ERROR, message); + value.setVdsId(getVds().getId()); + networkException.setVdsError(value); return networkException; } diff --git a/backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/HostMonitoringTest.java b/backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/HostMonitoringTest.java index 32e9f97..651a6cc 100644 --- a/backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/HostMonitoringTest.java +++ b/backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/HostMonitoringTest.java @@ -129,7 +129,7 @@ try { updater.refreshVdsStats(); } catch (Exception e) { - verify(vdsManager).handleNetworkException(any(VDSNetworkException.class), vds); + verify(vdsManager).handleNetworkException(any(VDSNetworkException.class)); verify(vdsManager).updateDynamicData(vds.getDynamicData()); } diff --git a/backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/VmStatisticsFetcherTest.java b/backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/VmStatisticsFetcherTest.java new file mode 100644 index 0000000..546f71f --- /dev/null +++ b/backend/manager/modules/vdsbroker/src/test/java/org/ovirt/engine/core/vdsbroker/VmStatisticsFetcherTest.java @@ -0,0 +1,5 @@ +package org.ovirt.engine.core.vdsbroker; + +public class VmStatisticsFetcherTest { + +} -- To view, visit http://gerrit.ovirt.org/35521 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I59d26baa4d372905c8249edb0aabe28ea4c4a74a Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Roy Golan <rgo...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches