// $Id: PoolMonitorV5.java,v 1.32 2007-08-01 20:00:45 tigran Exp $
package diskCacheV111.poolManager;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.primitives.Ints;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import diskCacheV111.poolManager.PoolSelectionUnit.DirectionType;
import diskCacheV111.pools.PoolCostInfo;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.CostException;
import diskCacheV111.util.FileLocality;
import diskCacheV111.util.FileNotInCacheException;
import diskCacheV111.util.FileNotOnlineCacheException;
import diskCacheV111.util.PermissionDeniedCacheException;
import diskCacheV111.vehicles.IpProtocolInfo;
import diskCacheV111.vehicles.ProtocolInfo;
import org.dcache.namespace.FileAttribute;
import org.dcache.namespace.FileType;
import org.dcache.poolmanager.Partition;
import org.dcache.poolmanager.PartitionManager;
import org.dcache.poolmanager.PoolInfo;
import org.dcache.poolmanager.PoolSelector;
import org.dcache.poolmanager.SelectedPool;
import org.dcache.poolmanager.SerializablePoolMonitor;
import org.dcache.vehicles.FileAttributes;
import static com.google.common.base.Strings.nullToEmpty;
import static java.util.stream.Collectors.toList;
import static org.dcache.namespace.FileAttribute.*;
public class PoolMonitorV5
extends SerializablePoolMonitor
{
private static final Logger _log =
LoggerFactory.getLogger(PoolMonitorV5.class);
private static final long serialVersionUID = -2400834413958127412L;
private PoolSelectionUnit _selectionUnit ;
private CostModule _costModule ;
private PartitionManager _partitionManager ;
@Override
public PoolSelectionUnit getPoolSelectionUnit()
{
return _selectionUnit;
}
public void setPoolSelectionUnit(PoolSelectionUnit selectionUnit)
{
_selectionUnit = selectionUnit;
}
@Override
public CostModule getCostModule()
{
return _costModule;
}
public void setCostModule(CostModule costModule)
{
_costModule = costModule;
}
@Override
public PartitionManager getPartitionManager()
{
return _partitionManager;
}
public void setPartitionManager(PartitionManager partitionManager)
{
_partitionManager = partitionManager;
}
@Override
public PoolSelector getPoolSelector(FileAttributes fileAttributes,
ProtocolInfo protocolInfo,
String linkGroup)
{
return new PnfsFileLocation(fileAttributes, protocolInfo, linkGroup);
}
public class PnfsFileLocation implements PoolSelector
{
private Partition _partition;
private final FileAttributes _fileAttributes;
private final ProtocolInfo _protocolInfo;
private final String _linkGroup;
public PnfsFileLocation(FileAttributes fileAttributes,
ProtocolInfo protocolInfo,
String linkGroup)
{
_fileAttributes = fileAttributes;
_protocolInfo = protocolInfo;
_linkGroup = linkGroup;
}
@Override
public Partition getCurrentPartition()
{
return _partition;
}
/**
* Returns the result of a PSU match for this PnfsFileLocation.
*/
private PoolPreferenceLevel[] match(DirectionType direction)
{
String hostName = getHostName();
String protocol = getProtocol();
return _selectionUnit.match(direction,
hostName,
protocol,
_fileAttributes,
_linkGroup);
}
@Override
public List<List<PoolInfo>> getReadPools()
{
Map<String,PoolInfo> onlineLocations = _costModule.getPoolInfoAsMap(_fileAttributes.getLocations());
return Stream
.of(match(DirectionType.READ))
.map(level -> level.getPoolList().stream().map(onlineLocations::get).filter(Objects::nonNull).collect(toList()))
.filter(levels -> !levels.isEmpty())
.collect(toList());
}
@Override
public SelectedPool selectWritePool(long preallocated)
throws CacheException
{
String hostName = getHostName();
String protocol = getProtocol();
PoolPreferenceLevel[] levels = _selectionUnit.match(DirectionType.WRITE,
hostName,
protocol,
_fileAttributes,
_linkGroup);
if (levels.length == 0) {
throw new CacheException(CacheException.NO_POOL_CONFIGURED,
"No write links configured for [" +
"net=" + hostName +
",protocol=" + protocol +
",store=" + _fileAttributes.getStorageClass() + "@" + _fileAttributes.getHsm() +
",cache=" + nullToEmpty(_fileAttributes.getCacheClass()) +
",linkgroup=" + nullToEmpty(_linkGroup) + "]");
}
CostException fallback = null;
for (PoolPreferenceLevel level: levels) {
List<PoolInfo> pools =
level.getPoolList().stream()
.map(_costModule::getPoolInfo)
.filter(Objects::nonNull)
.collect(toList());
if (!pools.isEmpty()) {
Partition partition = _partitionManager.getPartition(level.getTag());
try {
return partition.selectWritePool(_costModule, pools, _fileAttributes, preallocated);
} catch (CostException e) {
if (!e.shouldFallBack()) {
throw e;
}
fallback = e;
}
}
}
/* We were asked to fall back, but all available links were
* exhausted. Let the caller deal with it.
*/
if (fallback != null) {
throw fallback;
}
throw new CacheException(CacheException.NO_POOL_ONLINE,
"No write pools online for [" +
"net=" + hostName +
",protocol=" + protocol +
",store=" + _fileAttributes.getStorageClass() + "@" + _fileAttributes.getHsm() +
",cache=" + nullToEmpty(_fileAttributes.getCacheClass()) +
",linkgroup=" + nullToEmpty(_linkGroup) + "]");
}
@Override
public SelectedPool selectReadPool()
throws CacheException
{
Collection<String> locations = _fileAttributes.getLocations();
_log.debug("[read] Expected from pnfs: {}", locations);
Map<String,PoolInfo> onlinePools =
_costModule.getPoolInfoAsMap(locations);
_log.debug("[read] Online pools: {}", onlinePools);
/* Is the file in any of the online pools?
*/
if (onlinePools.isEmpty()){
throw new FileNotInCacheException("File not in any pool");
}
/* Get the prioritized list of allowed pools for this
* request.
*/
String hostName = getHostName();
String protocol = getProtocol();
PoolPreferenceLevel[] level = _selectionUnit.match(DirectionType.READ,
hostName,
protocol,
_fileAttributes,
_linkGroup);
/* An empty array indicates that no links were found that
* could serve the request. No reason to try any further;
* not even a stage or P2P would help.
*/
if (level.length == 0) {
throw new CacheException(CacheException.NO_POOL_CONFIGURED, "No read links configured [" +
"net=" + hostName +
",protocol=" + protocol +
",store=" + _fileAttributes.getStorageClass() + "@" + _fileAttributes.getHsm() +
",cache=" + nullToEmpty(_fileAttributes.getCacheClass()) +
",linkgroup=" + nullToEmpty(_linkGroup) + "]");
}
CostException fallback = null;
for (int prio = 0; prio < level.length; prio++) {
List<String> poolNames = level[prio].getPoolList();
_log.debug("[read] Allowed pools at level {}: {}",
prio, poolNames);
/* Reduce the set to the pools that are supposed to
* contain the file and which are online.
*/
List<PoolInfo> pools = new ArrayList<>(poolNames.size());
for (String poolName: poolNames) {
PoolInfo info = onlinePools.get(poolName);
if (info != null) {
pools.add(info);
}
}
_log.debug("[read] Available pools at level {}: {}",
prio, pools);
/* The caller may want to know which partition we used
* to select a pool.
*/
_partition =
_partitionManager.getPartition(level[prio].getTag());
/* Fallback to next link if current link doesn't point
* to any available pools.
*/
if (pools.isEmpty()) {
continue;
}
/* The actual pool selection is delegated to the
* Partition.
*/
try {
return _partition.selectReadPool(_costModule, pools,
_fileAttributes);
} catch (CostException e) {
if (!e.shouldFallBack()) {
throw e;
}
fallback = e;
}
}
/* We were asked to fall back, but all available links were
* exhausted. Let the caller deal with it.
*/
if (fallback != null) {
throw fallback;
}
/* None of the pools we were allowed to read from were
* online or had the file.
*/
throw new PermissionDeniedCacheException("File is online, but not in read-allowed pool");
}
private String getHostName() {
return (_protocolInfo instanceof IpProtocolInfo)
? ((IpProtocolInfo) _protocolInfo).getSocketAddress().getAddress().getHostAddress()
: null;
}
private String getProtocol() {
return _protocolInfo.getProtocol() + "/" +
_protocolInfo.getMajorVersion();
}
@Override
public Partition.P2pPair selectPool2Pool(boolean force)
throws CacheException
{
Collection<String> locations = _fileAttributes.getLocations();
_log.debug("[p2p] Expected source from pnfs: {}", locations);
Map<String,PoolInfo> sources =
_costModule.getPoolInfoAsMap(locations);
_log.debug("[p2p] Online source pools: {}", sources.values());
if (sources.isEmpty()) {
throw new CacheException("P2P denied: No source pools available");
}
PoolPreferenceLevel[] levels = match(DirectionType.P2P);
for (PoolPreferenceLevel level: levels) {
List<PoolInfo> pools =
level.getPoolList().stream()
.filter(pool -> !sources.containsKey(pool))
.map(_costModule::getPoolInfo)
.filter(Objects::nonNull)
.collect(toList());
if (!pools.isEmpty()) {
_log.debug("[p2p] Online destination candidates: {}", pools);
Partition partition =
_partitionManager.getPartition(level.getTag());
return partition.selectPool2Pool(_costModule,
Lists.newArrayList(sources.values()),
pools,
_fileAttributes,
force);
}
}
throw new PermissionDeniedCacheException("P2P denied: No pool candidates available/configured/left for p2p or file already everywhere");
}
@Override
public SelectedPool selectStagePool(String previousPool,
String previousHost)
throws CacheException
{
Collection<String> locations = _fileAttributes.getLocations();
_log.debug("[stage] Existing locations of the file: {}", locations);
CostException fallback = null;
for (PoolPreferenceLevel level: match(DirectionType.CACHE)) {
try {
List<PoolInfo> pools =
level.getPoolList().stream()
.filter(pool -> !locations.contains(pool))
.map(_costModule::getPoolInfo)
.filter(Objects::nonNull)
.collect(toList());
if (!pools.isEmpty()) {
_log.debug("[stage] Online stage candidates: {}", pools);
Partition partition =
_partitionManager.getPartition(level.getTag());
return partition.selectStagePool(_costModule, pools,
previousPool,
previousHost,
_fileAttributes);
}
} catch (CostException e) {
if (!e.shouldFallBack()) {
throw e;
}
fallback = e;
}
}
/* We were asked to fall back, but all available links were
* exhausted. Let the caller deal with it.
*/
if (fallback != null) {
throw fallback;
}
throw new CacheException(149, "No pool candidates available/configured/left for stage");
}
// FIXME: There is a fair amount of overlap between this method
// and getFileLocality.
@Override
public SelectedPool selectPinPool()
throws CacheException
{
/* This is the same arbitrary but deterministic ordering we
* use for min cost cut handling.
*/
Ordering<String> ordering =
new Ordering<String>()
{
final String id = _fileAttributes.getPnfsId().toString();
@Override
public int compare(String pool1, String pool2)
{
String s1 = id + pool1;
String s2 = id + pool2;
return Ints.compare(s1.hashCode(), s2.hashCode());
}
};
Collection<String> locations = _fileAttributes.getLocations();
_log.debug("[pin] Expected from pnfs: {}", locations);
Map<String,PoolInfo> onlinePools =
_costModule.getPoolInfoAsMap(locations);
_log.debug("[pin] Online pools: {}", onlinePools.values());
boolean isRequestSatisfiable = false;
String hostName = getHostName();
String protocol = getProtocol();
PoolPreferenceLevel[] levels = _selectionUnit.match(DirectionType.READ,
hostName,
protocol,
_fileAttributes,
_linkGroup);
for (PoolPreferenceLevel level: levels) {
List<String> pools = level.getPoolList();
if (!pools.isEmpty()) {
/* Now we know that the file could be pinned/read if
* any of these pools have the file.
*/
isRequestSatisfiable = true;
Optional<PoolInfo> pool =
pools.stream().filter(onlinePools::containsKey).min(ordering).map(onlinePools::get);
if (pool.isPresent()) {
return new SelectedPool(pool.get());
}
}
}
if (levels.length == 0) {
throw new CacheException(CacheException.NO_POOL_CONFIGURED, "No read links configured [" +
"net=" + hostName +
",protocol=" + protocol +
",store=" + _fileAttributes.getStorageClass() + "@" + _fileAttributes.getHsm() +
",cache=" + nullToEmpty(_fileAttributes.getCacheClass()) +
",linkgroup=" + nullToEmpty(_linkGroup) + "]");
} else if (!isRequestSatisfiable) {
throw new CacheException(CacheException.NO_POOL_ONLINE,
"No read pools online for [" +
"net=" + hostName +
",protocol=" + protocol +
",store=" + _fileAttributes.getStorageClass() + "@" + _fileAttributes.getHsm() +
",cache=" + nullToEmpty(_fileAttributes.getCacheClass()) +
",linkgroup=" + nullToEmpty(_linkGroup) + "]");
} else if (onlinePools.isEmpty() && !_fileAttributes.getStorageInfo().isStored()) {
throw new FileNotInCacheException("File is unavailable.");
} else {
throw new FileNotOnlineCacheException("File is nearline.");
}
}
}
@Override
public Collection<PoolCostInfo> queryPoolsByLinkName(String linkName)
{
return _selectionUnit
.getLinkByName(linkName)
.getPools()
.stream()
.map(PoolSelectionUnit.SelectionEntity::getName)
.map(_costModule::getPoolCostInfo)
.filter(Objects::nonNull)
.collect(toList());
}
public static Set<FileAttribute> getRequiredAttributesForFileLocality()
{
return EnumSet.of(STORAGEINFO, SIZE, LOCATIONS);
}
@Override
public FileLocality
getFileLocality(FileAttributes attributes, String hostName)
{
if (attributes.getFileType() == FileType.DIR || !attributes.isDefined(SIZE)) {
return FileLocality.NONE;
}
PoolPreferenceLevel[] levels =
_selectionUnit.match(DirectionType.READ,
hostName,
"*/*",
attributes,
null);
Collection<String> locations = attributes.getLocations();
for (PoolPreferenceLevel level: levels) {
if (!Collections.disjoint(level.getPoolList(), locations)) {
return (attributes.getStorageInfo().isStored()
? FileLocality.ONLINE_AND_NEARLINE
: FileLocality.ONLINE);
}
}
if (attributes.getStorageInfo().isStored()) {
return FileLocality.NEARLINE;
}
for (String name: locations) {
PoolSelectionUnit.SelectionPool pool = _selectionUnit.getPool(name);
if (pool == null || !pool.canReadForP2P()) {
continue;
}
PoolCostInfo cost = _costModule.getPoolCostInfo(name);
if (cost == null) {
continue;
}
// REVISIT: This check should be integrated into
// SelectionPool.canReadForP2P
if (cost.getP2pQueue().getMaxActive() > 0){
return FileLocality.NEARLINE;
}
}
return FileLocality.UNAVAILABLE;
}
}