package org.dcache.poolmanager;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import diskCacheV111.poolManager.CostModule;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.CostException;
import diskCacheV111.util.PnfsId;
import org.dcache.pool.assumption.PerformanceCostAssumption;
import org.dcache.vehicles.FileAttributes;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Math.max;
import static java.util.stream.Collectors.*;
/**
* Legacy partition that provided the classic dCache pool selection semantics. Now
* it only serves as a base class for WassPartition containing old code that is
* expected to be replaced in the future.
*/
public abstract class ClassicPartition extends Partition
{
private static final long serialVersionUID = 8239030345609342048L;
public enum SameHost { NEVER, BESTEFFORT, NOTCHECKED }
/**
* COSTFACTORS
*
* spacecostfactor double
* cpucostfactor double
*
* COSTCUTS
*
* idle double
* p2p double
* alert double
* halt double
* slope double
* fallback double
*
* OTHER
* max-copies int
* fallback-onspace boolean
*
* Options | Description
* ----------------------------------------------------------------------------
* idle | below 'idle' : 'reduce duplicate' mode
* p2p | above : start pool to pool mode
* | If p2p value is a percent then p2p is dynamically
* | assigned that percentile value of pool performance costs
* alert | stop pool 2 pool mode, start stage only mode
* halt | suspend system
* fallback | Allow fallback in Permission matrix on high load
* fallback-onspace | Allow fallback on write if out of free space
* error | How much the performance cost of a pool may exceed
* | limits before it rejects a request
*/
private static final Map<String,String> DEFAULTS =
ImmutableMap.<String,String>builder()
.put("max-copies", "3")
.put("p2p", "0.0")
.put("alert", "0.0")
.put("halt", "0.0")
.put("fallback", "0.0")
.put("fallback-onspace", "no")
.put("spacecostfactor", "1.0")
.put("cpucostfactor", "1.0")
.put("sameHostCopy", "besteffort")
.put("sameHostRetry", "besteffort")
.put("slope", "0.0")
.put("idle", "0.0")
.put("error", "0.2")
.build();
protected final SameHost _allowSameHostCopy;
protected final SameHost _allowSameHostRetry;
protected final long _maxPnfsFileCopies;
protected final double _costCut;
protected final boolean _costCutIsPercentile;
protected final double _alertCostCut;
protected final double _panicCostCut;
protected final double _fallbackCostCut;
protected final boolean _fallbackOnSpace;
protected final double _spaceCostFactor;
protected final double _performanceCostFactor;
protected final double _error;
protected final double _slope;
protected final double _minCostCut;
/**
* Order by performance cost.
*/
protected transient Ordering<PoolCost> _byPerformanceCost;
protected ClassicPartition(Map<String,String> inherited,
Map<String,String> properties)
{
this(DEFAULTS, inherited, properties);
}
protected ClassicPartition(Map<String,String> defaults,
Map<String,String> inherited,
Map<String,String> properties)
{
super(defaults, inherited, properties);
initTransientFields();
_allowSameHostCopy =
SameHost.valueOf(getProperty("sameHostCopy").toUpperCase());
_allowSameHostRetry =
SameHost.valueOf(getProperty("sameHostRetry").toUpperCase());
_maxPnfsFileCopies = getLong("max-copies");
_alertCostCut = getDouble("alert");
_panicCostCut = getDouble("halt");
_fallbackCostCut = getDouble("fallback");
_spaceCostFactor = getDouble("spacecostfactor");
_performanceCostFactor = getDouble("cpucostfactor");
_error = getDouble("error");
_slope = getDouble("slope");
_minCostCut = getDouble("idle");
_fallbackOnSpace = getBoolean("fallback-onspace");
String costCut = getProperty("p2p");
if (costCut.endsWith("%")) {
String numberPart = costCut.substring(0, costCut.length() - 1);
_costCut = Double.parseDouble(numberPart) / 100;
_costCutIsPercentile = true;
if (_costCut <= 0) {
throw new IllegalArgumentException("Number " + _costCut + " is too small; must be > 0%");
}
if (_costCut >= 1) {
throw new IllegalArgumentException("Number " + _costCut + " is too large; must be < 100%");
}
} else {
_costCut = Double.parseDouble(costCut);
_costCutIsPercentile = false;
}
}
@Override
public SelectedPool selectReadPool(CostModule cm,
List<PoolInfo> pools,
FileAttributes attributes)
throws CacheException
{
checkState(!pools.isEmpty());
/* Randomise order of pools with equal cost. In particular
* important when performance cost factor is 0.
*/
Collections.shuffle(pools);
/* Find best pool, taking min cost cut into account.
*/
PnfsId pnfsId = attributes.getPnfsId();
double pcf = _performanceCostFactor;
double mcc = _minCostCut;
Comparator<PoolInfo> byPerformanceCost =
(a, b) -> (Math.max(a.getPerformanceCost(), b.getPerformanceCost()) < mcc)
? Integer.compare(minCostCutPosition(pnfsId, a), minCostCutPosition(pnfsId, b))
: Double.compare(a.getPerformanceCost() * pcf, b.getPerformanceCost() * pcf);
List<PoolInfo> best = pools.stream()
.sorted(byPerformanceCost)
.limit(2)
.collect(toList());
PoolInfo bestPool = best.get(0);
/* Check cost cuts.
*/
double cost = bestPool.getCostInfo().getPerformanceCost();
boolean isPanicCostExceeded = isPanicCostExceeded(cost);
boolean isFallbackCostExceeded = isFallbackCostExceeded(cost);
boolean isCostCutExceeded = isCostCutExceeded(cm, cost);
if (isPanicCostExceeded) {
throw new CostException("Cost limit exceeded", null,
isFallbackCostExceeded, isCostCutExceeded);
}
if (isFallbackCostExceeded || isCostCutExceeded) {
throw new CostException("Cost limit exceeded",
new SelectedPool(bestPool, PerformanceCostAssumption.of(_error, _panicCostCut)),
isFallbackCostExceeded, isCostCutExceeded);
}
/* Add an assumption of the load being lower than the second best pool while still
* taking the min cost cut into account.
*/
double nextBest =
(best.size() > 1) ? Math.max(best.get(1).getPerformanceCost(), mcc) : Double.POSITIVE_INFINITY;
return new SelectedPool(bestPool, PerformanceCostAssumption.of(_error, _panicCostCut, _fallbackCostCut, _costCut, nextBest));
}
/**
* Returns a hash of pnfsId and pool.
*/
protected int minCostCutPosition(PnfsId pnfsId, PoolInfo pool)
{
return (pnfsId.toString() + pool.getName()).hashCode();
}
/**
* Establish the costCut at the moment for the given set of parameters.
* If the costCut was assigned a value from a string ending with a '%' then
* that percentile cost is used.
* @param cm current CostModule
* @return the costCut, taking into account possible relative costCut.
*/
protected double getCurrentCostCut(CostModule cm)
{
return _costCutIsPercentile
? cm.getPoolsPercentilePerformanceCost(_costCut)
: _costCut;
}
protected boolean isPanicCostExceeded(double cost)
{
return (_panicCostCut > 0.0 && cost > _panicCostCut);
}
protected boolean isFallbackCostExceeded(double cost)
{
return (_fallbackCostCut > 0.0 && cost > _fallbackCostCut);
}
protected boolean isCostCutExceeded(CostModule cm, double cost)
{
return (_costCut > 0.0 && cost >= getCurrentCostCut(cm));
}
protected boolean isAlertCostExceeded(double cost)
{
return (_alertCostCut > 0.0 && cost > _alertCostCut);
}
/**
* Internal immutable helper class to hold precomputed performance
* and space cost.
*/
protected static class PoolCost
{
final PoolInfo pool;
final double performanceCost;
final String host;
public PoolCost(PoolInfo pool)
{
this(pool, pool.getCostInfo().getPerformanceCost());
}
public PoolCost(PoolInfo pool, double performanceCost)
{
this.pool = pool;
this.performanceCost = performanceCost;
this.host = pool.getHostName();
}
}
protected double getWeightedPerformanceCost(PoolCost cost)
{
return Math.abs(cost.performanceCost) * _performanceCostFactor;
}
protected static PoolCost toPoolCost(PoolInfo pool)
{
return new PoolCost(pool);
}
private void initTransientFields()
{
_byPerformanceCost =
new Ordering<PoolCost>()
{
@Override
public int compare(PoolCost cost1, PoolCost cost2) {
return Double.compare(getWeightedPerformanceCost(cost1),
getWeightedPerformanceCost(cost2));
}
};
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException
{
stream.defaultReadObject();
initTransientFields();
}
}