package org.ovirt.engine.core.bll.scheduling.policyunits;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.ovirt.engine.core.bll.Backend;
import org.ovirt.engine.core.bll.job.ExecutionHandler;
import org.ovirt.engine.core.bll.scheduling.PolicyUnitParameter;
import org.ovirt.engine.core.bll.scheduling.SchedulingUnit;
import org.ovirt.engine.core.bll.scheduling.external.BalanceResult;
import org.ovirt.engine.core.bll.scheduling.pending.PendingResourceManager;
import org.ovirt.engine.core.bll.scheduling.pending.PendingVM;
import org.ovirt.engine.core.bll.scheduling.utils.FindVmAndDestinations;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.action.FenceVdsActionParameters;
import org.ovirt.engine.core.common.action.MaintenanceNumberOfVdssParameters;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdsActionParameters;
import org.ovirt.engine.core.common.action.VdsPowerDownParameters;
import org.ovirt.engine.core.common.businessentities.Cluster;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VDSStatus;
import org.ovirt.engine.core.common.businessentities.VdsSpmStatus;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.scheduling.PolicyUnit;
import org.ovirt.engine.core.common.scheduling.PolicyUnitType;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogable;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SchedulingUnit(
guid = "736999d0-1023-46a4-9a75-1316ed50e151",
name = "OptimalForPowerSaving",
type = PolicyUnitType.LOAD_BALANCING,
description = "Load balancing VMs in cluster according to hosts CPU load, striving cluster's hosts CPU load to"
+ " be over 'LowUtilization' and under 'HighUtilization'",
parameters = {
PolicyUnitParameter.HIGH_UTILIZATION,
PolicyUnitParameter.LOW_MEMORY_LIMIT_FOR_OVER_UTILIZED,
PolicyUnitParameter.HIGH_MEMORY_LIMIT_FOR_UNDER_UTILIZED,
PolicyUnitParameter.CPU_OVERCOMMIT_DURATION_MINUTES,
PolicyUnitParameter.LOW_UTILIZATION,
PolicyUnitParameter.ENABLE_AUTOMATIC_HOST_POWER_MANAGEMENT,
PolicyUnitParameter.HOSTS_IN_RESERVE
}
)
public class PowerSavingBalancePolicyUnit extends CpuAndMemoryBalancingPolicyUnit {
private static final Logger log = LoggerFactory.getLogger(PowerSavingBalancePolicyUnit.class);
public PowerSavingBalancePolicyUnit(PolicyUnit policyUnit,
PendingResourceManager pendingResourceManager) {
super(policyUnit, pendingResourceManager);
}
@Override
public Optional<BalanceResult> balance(Cluster cluster,
List<VDS> hosts,
Map<String, String> parameters,
ArrayList<String> messages) {
final Optional<BalanceResult> migrationRule = super.balance(cluster, hosts, parameters, messages);
List<VDS> allHosts = getVdsDao().getAllForCluster(cluster.getId());
List<VDS> emptyHosts = new ArrayList<>();
List<VDS> maintenanceHosts = new ArrayList<>();
List<VDS> downHosts = new ArrayList<>();
getHostLists(allHosts, parameters, emptyHosts, maintenanceHosts, downHosts);
Pair<VDS, VDSStatus> action = evaluatePowerManagementSituation(
cluster,
downHosts,
maintenanceHosts,
emptyHosts,
parameters);
if (action != null) {
processPmAction(action);
}
return migrationRule;
}
private void logAction(VDS vds, AuditLogType type) {
AuditLogable loggable = new AuditLogableImpl();
loggable.addCustomValue("Host", vds.getName());
loggable.setVdsName(vds.getName());
loggable.setVdsId(vds.getId());
loggable.setClusterId(vds.getClusterId());
loggable.setClusterName(vds.getClusterName());
new AuditLogDirector().log(loggable, type);
}
private void processPmAction(Pair<VDS, VDSStatus> action) {
VDS vds = action.getFirst();
VDSStatus currentStatus = vds.getStatus();
VDSStatus targetStatus = action.getSecond();
if (targetStatus == VDSStatus.Maintenance && currentStatus == VDSStatus.Up) {
logAction(vds, AuditLogType.PM_POLICY_UP_TO_MAINTENANCE);
/* Up -> Maint */
Guid[] vdsList = new Guid[] {vds.getId()};
MaintenanceNumberOfVdssParameters parameters =
new MaintenanceNumberOfVdssParameters(Arrays.asList(vdsList), true, true);
Backend.getInstance().runInternalAction(VdcActionType.MaintenanceNumberOfVdss,
parameters,
ExecutionHandler.createInternalJobContext());
}
else if (targetStatus == VDSStatus.Down && currentStatus == VDSStatus.Maintenance) {
logAction(vds, AuditLogType.PM_POLICY_MAINTENANCE_TO_DOWN);
/* Maint -> Down */
VdsPowerDownParameters parameters = new VdsPowerDownParameters(vds.getId());
parameters.setKeepPolicyPMEnabled(true);
Backend.getInstance().runInternalAction(VdcActionType.VdsPowerDown,
parameters,
ExecutionHandler.createInternalJobContext());
}
else if (targetStatus == VDSStatus.Up && currentStatus == VDSStatus.Maintenance) {
logAction(vds, AuditLogType.PM_POLICY_TO_UP);
/* Maint -> Up */
VdsActionParameters parameters = new VdsActionParameters(vds.getId());
Backend.getInstance().runInternalAction(VdcActionType.ActivateVds,
parameters,
ExecutionHandler.createInternalJobContext());
}
else if (targetStatus == VDSStatus.Up && currentStatus == VDSStatus.Down) {
logAction(vds, AuditLogType.PM_POLICY_TO_UP);
/* Down -> Up */
FenceVdsActionParameters parameters = new FenceVdsActionParameters(vds.getId());
Backend.getInstance().runInternalAction(VdcActionType.StartVds,
parameters,
ExecutionHandler.createInternalJobContext());
}
else {
/* Should not ever happen... */
log.error("Unknown host power management transition '{}' -> '{}'",
currentStatus,
targetStatus);
}
}
/**
* This method will prepare the lists that are necessary for the power management part of this
* policy.
*
* @param allHosts All hosts in the cluster regardless of their status or PM configuration
* @param emptyHosts Pre-initialized list that will be filled by empty hosts
* @param maintenanceHosts Pre-initialized list that will be filled by hosts in maintenance
* that have automatic power management still enabled
* @param downHosts Pre-initialized list that will be filled by hosts that are down
* that have automatic power management still enabled
*/
protected void getHostLists(List<VDS> allHosts, Map<String, String> parameters,
List<VDS> emptyHosts, List<VDS> maintenanceHosts, List<VDS> downHosts) {
for (VDS vds: allHosts) {
if (vds.getStatus() == VDSStatus.Up
&& vds.getVmCount() == 0
&& vds.getVmMigrating() == 0
&& PendingVM.collectForHost(getPendingResourceManager(), vds.getId()).size() == 0) {
emptyHosts.add(vds);
}
else if (vds.isPowerManagementControlledByPolicy() && !vds.isDisablePowerManagementPolicy()) {
if (vds.getStatus() == VDSStatus.Maintenance) {
maintenanceHosts.add(vds);
}
else if (vds.getStatus() == VDSStatus.Down) {
downHosts.add(vds);
}
}
}
}
/**
* This method will investigate the current state of all hosts in cluster and return the advised
* action that should take place to make the cluster closer to the expected balancing state.
*
* @param pmDownHosts hosts that were previously powered down by the power management policy
* @param pmMaintenanceHosts hosts that were previously powered down by the power management policy
* @param emptyHosts hosts that are still up, but contain no Vms
* @return a pair of VDS to update and the desired VDSStatus to get it to
*/
protected Pair<VDS, VDSStatus> evaluatePowerManagementSituation(Cluster cluster, List<VDS> pmDownHosts,
List<VDS> pmMaintenanceHosts,
List<VDS> emptyHosts,
Map<String, String> parameters) {
final int requiredReserve = tryParseWithDefault(parameters.get(PolicyUnitParameter.HOSTS_IN_RESERVE.getDbName()),
Config.<Integer> getValue(ConfigValues.HostsInReserve));
String enableAutoPMParameter = parameters.get(
PolicyUnitParameter.ENABLE_AUTOMATIC_HOST_POWER_MANAGEMENT.getDbName());
Boolean enableAutoPM = enableAutoPMParameter == null ? null : Boolean.valueOf(enableAutoPMParameter);
if (enableAutoPM == null) {
enableAutoPM = Config.<Boolean> getValue(ConfigValues.EnableAutomaticHostPowerManagement);
}
/* Automatic power management is disabled */
if (!enableAutoPM.booleanValue()) {
log.info("Automatic power management is disabled for cluster '{}'.", cluster.getName());
return null;
}
/* We need more hosts but there are no available for us */
if (requiredReserve > emptyHosts.size()
&& pmDownHosts.isEmpty()
&& pmMaintenanceHosts.isEmpty()) {
log.info("Cluster '{}' does not have enough spare hosts, but no additional host is available.",
cluster.getName());
return null;
}
/* We have enough free hosts so shut some hosts in maintenance down
keep at least one spare in maintenance during the process.
*/
else if (requiredReserve < emptyHosts.size()
&& pmMaintenanceHosts.size() > 1) {
log.info("Cluster '{}' does have enough spare hosts, shutting one host down.", cluster.getName());
return new Pair<>(pmMaintenanceHosts.get(0), VDSStatus.Down);
}
/* We do have enough empty hosts to put something to maintenance */
else if (requiredReserve < emptyHosts.size()) {
/* Find hosts with automatic PM enabled that are not the current SPM */
Optional<VDS> hostsWithAutoPM = emptyHosts.stream()
.filter(vds -> !vds.isDisablePowerManagementPolicy()
&& vds.getSpmStatus() != VdsSpmStatus.SPM
&& vds.isPmEnabled()
).findFirst();
if (!hostsWithAutoPM.isPresent()) {
log.info("Cluster '{}' does have too many spare hosts, but none can be put to maintenance.",
cluster.getName());
return null;
} else {
return new Pair<>(hostsWithAutoPM.get(), VDSStatus.Maintenance);
}
}
/* We have the right amount of empty hosts to start shutting the
hosts that are resting in maintenance down.
*/
else if (requiredReserve == emptyHosts.size() && !pmMaintenanceHosts.isEmpty()) {
log.info("Cluster '{}' does have enough spare hosts, shutting one host down.", cluster.getName());
return new Pair<>(pmMaintenanceHosts.get(0), VDSStatus.Down);
}
/* We do not have enough free hosts, but we still have some hosts
in maintenance. We can easily activate those.
*/
else if (requiredReserve > emptyHosts.size() && !pmMaintenanceHosts.isEmpty()) {
log.info("Cluster '{}' does not have enough spare hosts, reactivating one.", cluster.getName());
return new Pair<>(pmMaintenanceHosts.get(0), VDSStatus.Up);
}
/* We do not have enough free hosts and no hosts in pm maintenance,
so we need to start some hosts up.
*/
else if (requiredReserve > emptyHosts.size()
&& pmMaintenanceHosts.isEmpty()) {
log.info("Cluster '{}' does not have enough spare hosts, trying to start one up.", cluster.getName());
return new Pair<>(pmDownHosts.get(0), VDSStatus.Up);
}
/* All power management constraints were satisfied, no need to do anything */
return null;
}
@Override
protected FindVmAndDestinations getFindVmAndDestinations(Cluster cluster, Map<String, String> parameters) {
final int highUtilization = tryParseWithDefault(parameters.get("HighUtilization"), Config
.<Integer>getValue(ConfigValues.HighUtilizationForPowerSave));
final long overUtilizedMemory =
parameters.containsKey(PolicyUnitParameter.LOW_MEMORY_LIMIT_FOR_OVER_UTILIZED.getDbName()) ?
Long.parseLong(parameters.get(PolicyUnitParameter.LOW_MEMORY_LIMIT_FOR_OVER_UTILIZED.getDbName())) :
0L;
return new FindVmAndDestinations(cluster, highUtilization, overUtilizedMemory);
}
@Override
protected List<VDS> getPrimarySources(Cluster cluster, List<VDS> candidateHosts, Map<String, String> parameters) {
int highUtilization = tryParseWithDefault(parameters.get(PolicyUnitParameter.HIGH_UTILIZATION.getDbName()),
Config.<Integer>getValue(ConfigValues.HighUtilizationForPowerSave));
final int lowUtilization = tryParseWithDefault(parameters.get(PolicyUnitParameter.LOW_UTILIZATION.getDbName()),
Config.<Integer>getValue(ConfigValues.LowUtilizationForPowerSave));
final int cpuOverCommitDurationMinutes =
tryParseWithDefault(parameters.get(PolicyUnitParameter.CPU_OVERCOMMIT_DURATION_MINUTES.getDbName()),
Config.<Integer>getValue(ConfigValues.CpuOverCommitDurationMinutes));
final int highVdsCount = Math
.min(Config.<Integer>getValue(ConfigValues.UtilizationThresholdInPercent)
* highUtilization / 100,
highUtilization
- Config.<Integer>getValue(ConfigValues.VcpuConsumptionPercentage));
List<VDS> result = new ArrayList<>();
result.addAll(getUnderUtilizedCPUHosts(candidateHosts, lowUtilization, 1, cpuOverCommitDurationMinutes));
result.addAll(getOverUtilizedCPUHosts(candidateHosts, highVdsCount, cpuOverCommitDurationMinutes));
return result;
}
@Override
protected List<VDS> getPrimaryDestinations(Cluster cluster,
List<VDS> candidateHosts,
Map<String, String> parameters) {
int highUtilization = tryParseWithDefault(parameters.get(PolicyUnitParameter.HIGH_UTILIZATION.getDbName()),
Config.<Integer>getValue(ConfigValues.HighUtilizationForPowerSave));
final int lowUtilization = tryParseWithDefault(parameters.get(PolicyUnitParameter.LOW_UTILIZATION.getDbName()),
Config.<Integer>getValue(ConfigValues.LowUtilizationForPowerSave));
final int cpuOverCommitDurationMinutes =
tryParseWithDefault(parameters.get(PolicyUnitParameter.CPU_OVERCOMMIT_DURATION_MINUTES.getDbName()),
Config.<Integer>getValue(ConfigValues.CpuOverCommitDurationMinutes));
final List<VDS> result = getNormallyUtilizedCPUHosts(cluster,
candidateHosts,
highUtilization,
cpuOverCommitDurationMinutes,
lowUtilization);
return result;
}
@Override
protected List<VDS> getSecondarySources(Cluster cluster,
List<VDS> candidateHosts,
Map<String, String> parameters) {
long lowMemoryLimit = parameters.containsKey(PolicyUnitParameter.LOW_MEMORY_LIMIT_FOR_OVER_UTILIZED.getDbName()) ?
Long.parseLong(parameters.get(PolicyUnitParameter.LOW_MEMORY_LIMIT_FOR_OVER_UTILIZED.getDbName())) : 0L;
long highMemoryLimit = parameters.containsKey(PolicyUnitParameter.HIGH_MEMORY_LIMIT_FOR_UNDER_UTILIZED.getDbName()) ?
Long.parseLong(parameters.get(PolicyUnitParameter.HIGH_MEMORY_LIMIT_FOR_UNDER_UTILIZED.getDbName()))
: Long.MAX_VALUE;
List<VDS> result = new ArrayList<>();
result.addAll(getUnderUtilizedMemoryHosts(candidateHosts, highMemoryLimit, 1));
result.addAll(getOverUtilizedMemoryHosts(candidateHosts, lowMemoryLimit));
return result;
}
@Override
protected List<VDS> getSecondaryDestinations(Cluster cluster, List<VDS> candidateHosts, Map<String, String> parameters) {
long notEnoughMemory = parameters.containsKey(PolicyUnitParameter.LOW_MEMORY_LIMIT_FOR_OVER_UTILIZED.getDbName()) ?
Long.parseLong(parameters.get(PolicyUnitParameter.LOW_MEMORY_LIMIT_FOR_OVER_UTILIZED.getDbName())) : 0L;
long tooMuchMemory = parameters.containsKey(PolicyUnitParameter.HIGH_MEMORY_LIMIT_FOR_UNDER_UTILIZED.getDbName()) ?
Long.parseLong(parameters.get(PolicyUnitParameter.HIGH_MEMORY_LIMIT_FOR_UNDER_UTILIZED.getDbName())) :
Long.MAX_VALUE;
return getNormallyUtilizedMemoryHosts(candidateHosts, notEnoughMemory, tooMuchMemory);
}
}