package org.ovirt.engine.core.bll.pm; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; import org.ovirt.engine.core.common.businessentities.FencingPolicy; import org.ovirt.engine.core.common.businessentities.NonOperationalReason; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VDSStatus; import org.ovirt.engine.core.common.businessentities.pm.FenceAgent; import org.ovirt.engine.core.common.businessentities.pm.FenceProxySourceType; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.utils.FencingPolicyHelper; import org.ovirt.engine.core.common.utils.pm.FenceProxySourceTypeHelper; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.Version; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.utils.ThreadUtils; import org.ovirt.engine.core.utils.pm.VdsFenceOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * It manages selection of fence proxy for specified host and fencing policy */ public class FenceProxyLocator { private static final Logger log = LoggerFactory.getLogger(FenceProxyLocator.class); private final VDS fencedHost; private FencingPolicy fencingPolicy; public FenceProxyLocator(VDS fencedHost) { this(fencedHost, null); } public FenceProxyLocator(VDS fencedHost, FencingPolicy fencingPolicy) { this.fencedHost = fencedHost; this.fencingPolicy = fencingPolicy; } public boolean isProxyHostAvailable() { return findProxyHost(false) != null; } public VDS findProxyHost() { return findProxyHost(false, null); } public VDS findProxyHost(boolean withRetries) { return findProxyHost(withRetries, null); } public VDS findProxyHost(boolean withRetries, Guid excludedHostId) { int retries = getFindFenceProxyRetries(); long delayInMs = getDelayBetweenRetries(); VDS proxyHost = null; // get PM Proxy preferences or use defaults if not defined for (FenceProxySourceType fenceProxySource : getFenceProxySources()) { proxyHost = selectBestProxy(fenceProxySource, excludedHostId); int count = 0; // If can not find a proxy host retry and delay between retries as configured. while (proxyHost == null && withRetries && count < retries) { log.warn("Attempt {} to find fence proxy for host '{}' failed...", ++count, fencedHost.getHostName()); ThreadUtils.sleep(delayInMs); proxyHost = selectBestProxy(fenceProxySource, excludedHostId); } if (proxyHost != null) { break; } } if (proxyHost == null) { log.error("Can not run fence action on host '{}', no suitable proxy host was found.", fencedHost.getName()); return null; } return proxyHost; } protected List<FenceProxySourceType> getFenceProxySources() { List<FenceProxySourceType> fenceProxySources = fencedHost.getFenceProxySources(); if (CollectionUtils.isEmpty(fenceProxySources)) { fenceProxySources = getDefaultFenceProxySources(); } return fenceProxySources; } protected VDS selectBestProxy(FenceProxySourceType fenceProxySource, Guid excludedHostId) { return getDbFacade().getVdsDao().getAll().stream() .peek(vds -> log.debug("Evaluating host '{}'", vds.getHostName())) .filter(vds -> !Objects.equals(vds.getId(), fencedHost.getId())) .filter(vds -> !isHostExcluded(vds, excludedHostId)) .filter(vds -> isHostFromSelectedSource(vds, fenceProxySource)) .filter(this::areAgentsVersionCompatible) .filter(vds -> isFencingPolicySupported(vds, getMinSupportedVersionForFencingPolicy())) .filter(vds -> !isHostNetworkUnreachable(vds)) .sorted(Comparator.comparingInt(vds -> vds.getStatus() == VDSStatus.Up ? -1 : 1)) .findFirst() .orElse(null); } protected boolean isHostExcluded(VDS proxyCandidate, Guid excludedHostId) { boolean excluded = proxyCandidate.getId().equals(excludedHostId); log.debug("Proxy candidate '{}' was excluded intentionally: {}", proxyCandidate.getHostName(), excluded); return excluded; } private boolean isHostFromSelectedSource(VDS proxyCandidate, FenceProxySourceType fenceProxySource) { boolean fromSelectedSource = false; switch (fenceProxySource) { case CLUSTER: fromSelectedSource = proxyCandidate.getClusterId().equals(fencedHost.getClusterId()); break; case DC: fromSelectedSource = proxyCandidate.getStoragePoolId().equals(fencedHost.getStoragePoolId()); break; case OTHER_DC: fromSelectedSource = !proxyCandidate.getStoragePoolId().equals(fencedHost.getStoragePoolId()); break; } log.debug("Proxy candidate '{}' matches proxy source '{}': {}", proxyCandidate.getHostName(), fenceProxySource, fromSelectedSource); return fromSelectedSource; } protected boolean areAgentsVersionCompatible(VDS proxyCandidate) { VdsFenceOptions options = createVdsFenceOptions(proxyCandidate.getClusterCompatibilityVersion().getValue()); boolean compatible = true; for (FenceAgent agent : fencedHost.getFenceAgents()) { if (!options.isAgentSupported(agent.getType())) { compatible = false; break; } } log.debug("Proxy candidate '{}' has compatible fence agents: {}", proxyCandidate.getHostName(), compatible); return compatible; } protected boolean isFencingPolicySupported(VDS proxyCandidate, Version minimalSupportedVersion) { boolean supported = minimalSupportedVersion == null; if (!supported) { for (Version version : proxyCandidate.getSupportedClusterVersionsSet()) { if (version.compareTo(minimalSupportedVersion) >= 0) { supported = true; break; } } } log.debug("Proxy candidate '{}' supports fencing policy '{}': {}", proxyCandidate.getHostName(), fencingPolicy, supported); return supported; } protected boolean isHostNetworkUnreachable(VDS proxyCandidate) { boolean unreachable = proxyCandidate.getStatus() == VDSStatus.Down || proxyCandidate.getStatus() == VDSStatus.Reboot || proxyCandidate.getStatus() == VDSStatus.Kdumping || proxyCandidate.getStatus() == VDSStatus.NonResponsive || proxyCandidate.getStatus() == VDSStatus.PendingApproval || (proxyCandidate.getStatus() == VDSStatus.NonOperational && proxyCandidate.getNonOperationalReason() == NonOperationalReason.NETWORK_UNREACHABLE); log.debug("Proxy candidate '{}' with status '{}' is unreachable: {}", proxyCandidate.getHostName(), proxyCandidate.getStatus(), unreachable); return unreachable; } protected List<FenceProxySourceType> getDefaultFenceProxySources() { return FenceProxySourceTypeHelper.parseFromString( Config.getValue(ConfigValues.FenceProxyDefaultPreferences)); } protected int getFindFenceProxyRetries() { // make sure that loop is executed at least once , no matter what is the value in config return Math.max(Config.<Integer>getValue(ConfigValues.FindFenceProxyRetries), 1); } protected long getDelayBetweenRetries() { return TimeUnit.SECONDS.toMillis( Config.<Integer>getValue(ConfigValues.FindFenceProxyDelayBetweenRetriesInSec)); } protected Version getMinSupportedVersionForFencingPolicy() { return fencingPolicy == null ? null : FencingPolicyHelper.getMinimalSupportedVersion(fencingPolicy); } protected VdsFenceOptions createVdsFenceOptions(String version) { return new VdsFenceOptions(version); } // TODO Investigate if injection is possible protected DbFacade getDbFacade() { return DbFacade.getInstance(); } }