package org.dcache.poolmanager; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import diskCacheV111.poolManager.CostModule; import diskCacheV111.util.CacheException; import diskCacheV111.util.CostException; import diskCacheV111.util.DestinationCostException; import diskCacheV111.util.PermissionDeniedCacheException; import diskCacheV111.util.SourceCostException; import org.dcache.pool.assumption.Assumption; import org.dcache.pool.assumption.Assumptions; import org.dcache.pool.assumption.AvailableSpaceAssumption; import org.dcache.pool.assumption.PerformanceCostAssumption; import org.dcache.vehicles.FileAttributes; import static com.google.common.base.Preconditions.checkState; import static java.util.stream.Collectors.toList; /** * Partition that implements the probabilistic weighted available * space selection (WASS) algorithm. * * Experimental. Details will likely change. At the moment only * pools to which data is written are selected according to the WASS * algorithm. For reads the classic selection algorithm is used. */ public class WassPartition extends ClassicPartition { static final String TYPE = "wass"; private static final long serialVersionUID = -3587599095801229561L; private final WeightedAvailableSpaceSelection wass; public WassPartition() { this(NO_PROPERTIES); } public WassPartition(Map<String,String> inherited) { this(inherited, NO_PROPERTIES); } protected WassPartition(Map<String,String> inherited, Map<String,String> properties) { super(inherited, properties); wass = new WeightedAvailableSpaceSelection(_performanceCostFactor, _spaceCostFactor); } @Override protected Partition create(Map<String,String> inherited, Map<String,String> properties) { return new WassPartition(inherited, properties); } @Override public String getType() { return TYPE; } @Override public SelectedPool selectWritePool(CostModule cm, List<PoolInfo> pools, FileAttributes attributes, long preallocated) throws CacheException { PoolInfo pool = wass.selectByAvailableSpace(pools, preallocated, PoolInfo::getCostInfo); if (pool == null) { throw new CostException("All pools are full", null, _fallbackOnSpace, false); } return new SelectedPool(pool, new AvailableSpaceAssumption(preallocated)); } /* REVISIT: The current implementation is a mix of the read pool * selection from ClassicPartition and write pool selection using * WASS. Code can probably be shared with ClassicPartition, but * since I hope to refine read pool selection, any code * duplication should be temporary and it is not worth refactoring * ClassicPartition. */ @Override public P2pPair selectPool2Pool(CostModule cm, List<PoolInfo> src, List<PoolInfo> dst, FileAttributes attributes, boolean force) throws CacheException { checkState(!src.isEmpty()); checkState(!dst.isEmpty()); /* The maximum number of replicas can be limited. */ if (src.size() >= _maxPnfsFileCopies) { throw new PermissionDeniedCacheException("P2P denied: already too many copies (" + src.size() + ")"); } /* Randomise order of pools with equal cost. In particular * important when cost factors are 0. */ Collections.shuffle(src); /* Source pools are only selected by performance cost, because * we will only read from the pool */ List<PoolCost> sources = src.stream().map(WassPartition::toPoolCost).sorted(_byPerformanceCost).collect(toList()); if (!force && isAlertCostExceeded(sources.get(0).performanceCost)) { throw new SourceCostException("P2P denied: All source pools are too busy (performance cost > " + _alertCostCut + ")"); } /* The target pool must be below specified cost limits; * otherwise we wouldn't be able to read the file afterwards * without triggering another p2p. */ double maxTargetCost = (_slope > 0.01) ? _slope * sources.get(0).performanceCost : getCurrentCostCut(cm); if (!force && maxTargetCost > 0.0) { dst = dst.stream().filter(pool -> toPoolCost(pool).performanceCost < maxTargetCost).collect(toList()); } if (dst.isEmpty()) { throw new DestinationCostException("P2P denied: All destination pools are too busy (performance cost > " + maxTargetCost + ")"); } long filesize = attributes.getSize(); Assumption sourceAssumption = force ? Assumptions.none() : PerformanceCostAssumption.of(_error, _alertCostCut); Assumption destinationAssumption = force ? new AvailableSpaceAssumption(filesize) : new AvailableSpaceAssumption(filesize).and(PerformanceCostAssumption.of( _error, maxTargetCost)); if (_allowSameHostCopy != SameHost.NOTCHECKED) { /* Loop over all sources and find the most appropriate * destination such that same host constraints are * satisfied. */ for (PoolCost source: sources) { List<PoolInfo> destinations; if (source.host == null) { destinations = dst; } else { destinations = dst.stream().filter(d -> !d.getHostName().equals(source.host)).collect(toList()); } PoolInfo destination = wass.selectByAvailableSpace(destinations, filesize, PoolInfo::getCostInfo); if (destination != null) { return new P2pPair(new SelectedPool(source.pool, sourceAssumption), new SelectedPool(destination, destinationAssumption)); } } /* We could not find a pair on different hosts, what now? */ if (_allowSameHostCopy == SameHost.NEVER) { throw new PermissionDeniedCacheException("P2P denied: sameHostCopy is 'never' and no matching pool found"); } } PoolInfo destination = wass.selectByAvailableSpace(dst, filesize, PoolInfo::getCostInfo); if (destination == null) { throw new DestinationCostException("All pools are full"); } return new P2pPair(new SelectedPool(sources.get(0).pool, sourceAssumption), new SelectedPool(destination, destinationAssumption)); } private PoolInfo selectByPrevious(List<PoolInfo> pools, String previousPool, String previousHost, FileAttributes attributes) { if (previousHost != null && _allowSameHostRetry != SameHost.NOTCHECKED) { List<PoolInfo> filteredPools = pools.stream() .filter(p -> !Objects.equals(p.getHostName(), previousHost) && !Objects.equals(p.getName(), previousPool)) .collect(toList()); PoolInfo pool = wass.selectByAvailableSpace(filteredPools, attributes.getSize(), PoolInfo::getCostInfo); if (pool != null) { return pool; } if (_allowSameHostRetry == SameHost.NEVER) { return null; } } if (previousPool != null) { List<PoolInfo> filteredPools = pools.stream() .filter(p -> !Objects.equals(p.getName(), previousPool)) .collect(toList()); PoolInfo pool = wass.selectByAvailableSpace(filteredPools, attributes.getSize(), PoolInfo::getCostInfo); if (pool != null) { return pool; } } return wass.selectByAvailableSpace(pools, attributes.getSize(), PoolInfo::getCostInfo); } @Override public SelectedPool selectStagePool(CostModule cm, List<PoolInfo> pools, String previousPool, String previousHost, FileAttributes attributes) throws CacheException { if (_fallbackCostCut > 0.0) { /* Filter by fallback cost; ensures that the file does not * get staged to a pool from which cost prevents us from * reading it. */ List<PoolInfo> filtered = pools.stream().filter(pool -> toPoolCost(pool).performanceCost < _fallbackCostCut).collect(toList()); PoolInfo pool = selectByPrevious(filtered, previousPool, previousHost, attributes); if (pool != null) { return new SelectedPool(pool, PerformanceCostAssumption.of(_error, _fallbackCostCut)); } /* Didn't find a pool. Redo the selection from the full * set, but signal that the caller should fall back to * other links if possible. */ pool = selectByPrevious(pools, previousPool, previousHost, attributes); if (pool == null) { throw new CostException("All pools full", null, true, false); } else { throw new CostException("Fallback cost exceeded", new SelectedPool(pool), true, false); } } else { PoolInfo pool = selectByPrevious(pools, previousPool, previousHost, attributes); if (pool == null) { throw new CostException("All pools full", null, true, false); } return new SelectedPool(pool); } } }