package org.ovirt.engine.core.vdsbroker.monitoring;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.common.businessentities.Cluster;
import org.ovirt.engine.core.common.businessentities.IVdsEventListener;
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.VmRngDevice;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.ClusterDao;
import org.ovirt.engine.core.dao.VdsDao;
import org.ovirt.engine.core.dao.VmDynamicDao;
/**
* This class defines virt strategy entry points, which are needed in host monitoring phase
*/
@Singleton
public class VirtMonitoringStrategy implements MonitoringStrategy {
private final ClusterDao clusterDao;
private final VdsDao vdsDao;
private final VmDynamicDao vmDynamicDao;
@Inject
private Instance<IVdsEventListener> eventListener;
@Inject
public VirtMonitoringStrategy(ClusterDao clusterDao, VdsDao vdsDao, VmDynamicDao vmDynamicDao) {
this.clusterDao = clusterDao;
this.vdsDao = vdsDao;
this.vmDynamicDao = vmDynamicDao;
}
@Override
public boolean canMoveToMaintenance(VDS vds) {
// We can only move to maintenance in case no VMs are running on the host
return vds.getVmCount() == 0 && !isAnyVmRunOnVdsInDb(vds.getId());
}
protected IVdsEventListener getEventListener() {
return eventListener.get();
}
protected boolean isAnyVmRunOnVdsInDb(Guid vdsId) {
return vmDynamicDao.isAnyVmRunOnVds(vdsId);
}
@Override
public boolean isMonitoringNeeded(VDS vds) {
return true;
}
@Override
public void processSoftwareCapabilities(VDS vds) {
// If we can't test for those capabilities, we don't say they don't exist
if (vds.getKvmEnabled() != null && vds.getKvmEnabled().equals(false) && vds.getStatus() != VDSStatus.NonOperational) {
vdsNonOperational(vds, NonOperationalReason.KVM_NOT_RUNNING, null);
vds.setStatus(VDSStatus.NonOperational);
}
Cluster cluster = clusterDao.get(vds.getClusterId());
if (!hostCompliesWithClusterEmulationMode(vds, cluster) && vds.getStatus() != VDSStatus.NonOperational) {
Map<String, String> customLogValues = new HashMap<>();
customLogValues.put("hostSupportedEmulatedMachines", vds.getSupportedEmulatedMachines());
if (cluster.isDetectEmulatedMachine()) {
customLogValues.put("clusterEmulatedMachines", Config.<List<String>>getValue(ConfigValues.ClusterEmulatedMachines, vds.getClusterCompatibilityVersion().getValue()).toString());
vdsNonOperational(vds, NonOperationalReason.EMULATED_MACHINES_INCOMPATIBLE_WITH_CLUSTER_LEVEL, customLogValues);
} else {
customLogValues.put("clusterEmulatedMachines", cluster.getEmulatedMachine());
vdsNonOperational(vds, NonOperationalReason.EMULATED_MACHINES_INCOMPATIBLE_WITH_CLUSTER, customLogValues);
}
vds.setStatus(VDSStatus.NonOperational);
}
if (vds.getStatus() != VDSStatus.NonOperational
&& !cluster.isInUpgradeMode()) {
checkIfNotMixingRhels(vds, cluster);
}
if (!hostCompliesWithRngDeviceSources(vds, cluster) && vds.getStatus() != VDSStatus.NonOperational) {
Map<String, String> customLogValues = new HashMap<>();
customLogValues.put("hostSupportedRngSources", VmRngDevice.sourcesToCsv(vds.getSupportedRngSources()));
customLogValues.put("clusterRequiredRngSources", VmRngDevice.sourcesToCsv(cluster.getRequiredRngSources()));
vdsNonOperational(vds, NonOperationalReason.RNG_SOURCES_INCOMPATIBLE_WITH_CLUSTER, customLogValues);
vds.setStatus(VDSStatus.NonOperational);
}
}
/**
* Sets the new host to non-operational if adding a RHEL6 machine to a cluster with RHEL7s or RHEL7 to cluster with RHEL6s
*
* It tries to be as non-invasive as possible and only if the above is the case, turns the host into non-operational.
*/
private void checkIfNotMixingRhels(VDS vds, Cluster cluster) {
if (vds.getHostOs() == null) {
return;
}
String[] hostOsInfo = vds.getHostOs().split("-");
if (hostOsInfo.length != 3) {
return;
}
String newOsName = hostOsInfo[0].trim();
String newRelease = hostOsInfo[2].trim();
// both the CentOS and RHEL has osName RHEL
if (newOsName.equals("RHEL") || newOsName.equals("oVirt Node") || newOsName.equals("RHEV Hypervisor")) {
VDS beforeRhel = vdsDao.getFirstUpRhelForCluster(cluster.getId());
boolean firstHostInCluster = beforeRhel == null;
if (firstHostInCluster) {
// no need to do any checks
return;
}
// if not first host in cluster, need to check if the version is the same
if (beforeRhel.getHostOs() == null) {
return;
}
String[] prevOsInfo = beforeRhel.getHostOs().split("-");
if (prevOsInfo.length != 3) {
return;
}
String prevRelease = prevOsInfo[2].trim();
boolean addingRhel6toRhel7 = newRelease.contains("el6") && prevRelease.contains("el7");
boolean addingRhel7toRhel6 = newRelease.contains("el7") && prevRelease.contains("el6");
if (addingRhel7toRhel6 || addingRhel6toRhel7) {
Map<String, String> customLogValues = new HashMap<>();
customLogValues.put("previousRhel", beforeRhel.getHostOs());
customLogValues.put("addingRhel", vds.getHostOs());
vdsNonOperational(vds, NonOperationalReason.MIXING_RHEL_VERSIONS_IN_CLUSTER, customLogValues);
vds.setStatus(VDSStatus.NonOperational);
}
}
}
private boolean hostCompliesWithRngDeviceSources(VDS vds, Cluster cluster) {
/*
* For purpose of this method 'random' and 'urandom' are considered to be equivalent. It's because vdsm can't
* report 'urandom' yet.
* This 'hack' can be removed when engine will not be required to work with vdsm that doesn't report 'urandom',
* i.e. when engine 4.0 will not be supported.
*/
return vds.getSupportedRngSources().containsAll(cluster.getAdditionalRngSources())
&& (vds.getSupportedRngSources().contains(VmRngDevice.Source.URANDOM)
|| vds.getSupportedRngSources().contains(VmRngDevice.Source.RANDOM));
}
@Override
public void processHardwareCapabilities(VDS vds) {
getEventListener().processOnCpuFlagsChange(vds.getId());
}
protected void vdsNonOperational(VDS vds, NonOperationalReason reason, Map<String, String> customLogValues) {
getEventListener().vdsNonOperational(vds.getId(), reason, true, Guid.Empty, customLogValues);
}
@Override
public boolean processHardwareCapabilitiesNeeded(VDS oldVds, VDS newVds) {
return !StringUtils.equals(oldVds.getCpuFlags(), newVds.getCpuFlags());
}
@Override
public boolean isPowerManagementSupported() {
return true;
}
private boolean hostCompliesWithClusterEmulationMode(VDS vds, Cluster cluster) {
// the initial cluster emulated machine value is set by the first host that complies.
if (cluster.isDetectEmulatedMachine()) {
return hostEmulationModeMatchesTheConfigValues(vds);
} else {
// the cluster has the emulated machine flag set. match the host against it.
return vds.getSupportedEmulatedMachines() != null ? Arrays.asList(vds.getSupportedEmulatedMachines().split(",")).contains(
cluster.getEmulatedMachine()) : false;
}
}
private boolean hostEmulationModeMatchesTheConfigValues(VDS vds) {
// match this host against the config flags by order
String matchedEmulatedMachine =
Config.<List<String>> getValue(ConfigValues.ClusterEmulatedMachines,
vds.getClusterCompatibilityVersion().getValue())
.stream().filter(getSupportedEmulatedMachinesAsSet(vds)::contains).findFirst().orElse(null);
if (matchedEmulatedMachine != null && !matchedEmulatedMachine.isEmpty()) {
setClusterEmulatedMachine(vds, matchedEmulatedMachine);
return true;
}
return false;
}
private static Set<String> getSupportedEmulatedMachinesAsSet(VDS vds) {
return new HashSet<>(Arrays.asList(vds.getSupportedEmulatedMachines().split(",")));
}
private void setClusterEmulatedMachine(VDS vds, String matchedEmulatedMachine) {
// host matches and its value will set the cluster emulated machine
clusterDao.setEmulatedMachine(vds.getClusterId(), matchedEmulatedMachine, false);
}
}