package org.dcache.services.info.secondaryInfoProviders;
import com.google.common.base.Joiner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.dcache.services.info.base.StateComposite;
import org.dcache.services.info.base.StateExhibitor;
import org.dcache.services.info.base.StatePath;
import org.dcache.services.info.base.StateUpdate;
import org.dcache.services.info.stateInfo.PoolSpaceVisitor;
import org.dcache.services.info.stateInfo.SetMapVisitor;
import org.dcache.services.info.stateInfo.SpaceInfo;
/**
* The PoolgroupSpaceWatcher Class implements StateWatcher. It is responsible for maintaining
* the space information of poolgroups. It is triggered whenever pool space information or
* a pool's membership of a poolgroup changes.
*
* @author Paul Millar <paul.millar@desy.de>
*/
public class PoolgroupSpaceWatcher extends AbstractStateWatcher
{
private static final Logger LOGGER = LoggerFactory.getLogger(PoolgroupSpaceWatcher.class);
private static final String PREDICATE_PATHS[] = { "pools.*.space.*",
"poolgroups.*",
"poolgroups.*.pools.*"};
private static final StatePath POOLGROUPS_PATH = new StatePath("poolgroups");
private static final StatePath POOL_MEMBERSHIP_REL_PATH = new StatePath("pools");
@Override
protected String[] getPredicates() {
return PREDICATE_PATHS;
}
@Override
public void trigger(StateUpdate update, StateExhibitor currentState,
StateExhibitor futureState)
{
super.trigger(update, currentState, futureState);
Set<String> recalcPoolgroup = new HashSet<>();
LOGGER.info("Watcher {} triggered", getClass().getSimpleName());
LOGGER.trace("Gathering state:");
LOGGER.trace(" building current poolgroup membership.");
Map <String,Set<String>> currentPoolgroupMembership = SetMapVisitor.getDetails(currentState, POOLGROUPS_PATH, POOL_MEMBERSHIP_REL_PATH);
LOGGER.trace(" building future poolgroup membership.");
Map <String,Set<String>> futurePoolgroupMembership = SetMapVisitor.getDetails(futureState, POOLGROUPS_PATH, POOL_MEMBERSHIP_REL_PATH);
LOGGER.trace(" establishing current pool space mapping.");
Map<String, SpaceInfo> poolSpaceInfoPre = PoolSpaceVisitor.getDetails(currentState);
LOGGER.trace(" establishing future pool space mapping.");
Map<String, SpaceInfo> poolSpaceInfoPost = PoolSpaceVisitor.getDetails(futureState);
LOGGER.trace("Looking for changes in poolgroup membership.");
updateTodoBasedOnMembership(recalcPoolgroup, currentPoolgroupMembership, futurePoolgroupMembership);
LOGGER.trace("Looking for changes in pool space information.");
updateTodoBasedOnPoolSpace(recalcPoolgroup, futurePoolgroupMembership, poolSpaceInfoPre, poolSpaceInfoPost);
if (recalcPoolgroup.size() == 0) {
LOGGER.trace("No poolgroups need updating");
return;
}
for (String thisPoolgroup : recalcPoolgroup) {
LOGGER.trace("Updating poolgroup {}", thisPoolgroup);
StatePath thisPgPath = POOLGROUPS_PATH.newChild(thisPoolgroup);
buildNewMetrics(update, thisPgPath.newChild("space"), futurePoolgroupMembership.get(thisPoolgroup), poolSpaceInfoPost);
}
}
/**
* Create new metrics that update information about the named poolgroup.
* @param update the StateUpdate to which the new metric values will be appended.
* @param path the StatePath under which the space information will be added.
* @param pools the Set of pools this poolgroup has within its membership.
* @param poolsSpaceInfo the mapping between pools and their SpaceInfo.
*/
private void buildNewMetrics(StateUpdate update, StatePath path, Set<String> pools,
Map<String, SpaceInfo> poolsSpaceInfo)
{
SpaceInfo pgSpaceInfo = new SpaceInfo();
// Create an Ephemeral StateComposite for our data.
update.appendUpdate(path, new StateComposite());
if (pools != null) {
for (String thisPool : pools) {
SpaceInfo poolSpace = poolsSpaceInfo.get(thisPool);
if (poolSpace != null) {
pgSpaceInfo.add(poolSpace);
}
}
}
pgSpaceInfo.addMetrics(update, path, false);
LOGGER.trace(" new info: {}", pgSpaceInfo);
}
private String describePoolgroup(Set<String> poolgroup)
{
if (poolgroup == null) {
return "<unknown>";
} else if (poolgroup.isEmpty()) {
return "<empty>";
} else {
return "{" + Joiner.on(", ").join(poolgroup) + "}";
}
}
/**
* Add poolgroups to the to-be-recalculated Set if the pool membership of a poolgroup has
* changed, or the poolgroup is new.
* @param recalcPoolgroup
* @param transition
*/
private void updateTodoBasedOnMembership(Set<String> recalcPoolgroup,
Map <String,Set<String>> currentPoolgroupMembership,
Map <String,Set<String>> futurePoolgroupMembership)
{
// Scan (future) poolgroup membership..
for (Map.Entry<String, Set<String>> pgEntry : futurePoolgroupMembership.entrySet()) {
String thisPoolgroup = pgEntry.getKey();
Set<String> thisPoolgroupFuturePoolset = pgEntry.getValue();
LOGGER.trace(" examining poolgroup: {}", thisPoolgroup);
Set<String> thisPoolgroupCurrentPoolset = currentPoolgroupMembership.get(thisPoolgroup);
// If poolgroup is new or membership isn't the same as it was...
if ((thisPoolgroupCurrentPoolset == null) || !thisPoolgroupCurrentPoolset.equals(thisPoolgroupFuturePoolset)) {
recalcPoolgroup.add(thisPoolgroup);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace(" poolgroup {} is new or has altered membership {} -> {}",
thisPoolgroup, describePoolgroup(thisPoolgroupCurrentPoolset),
describePoolgroup(thisPoolgroupFuturePoolset));
}
}
}
}
/**
* Look for changes in the pool size. Update any poolgroup this pool is a member of.
* @param recalcPoolgroup
* @param transition
*/
private void updateTodoBasedOnPoolSpace(Set<String> recalcPoolgroup,
Map <String,Set<String>> futurePoolgroupMembership,
Map<String, SpaceInfo> currentPoolSpaceInfo, Map<String, SpaceInfo> futurePoolSpaceInfo)
{
Set<String> changedPools = new HashSet<>();
// 1. Build list of pools that have changed:
// 1. a) disappeared or changed value.
for (Map.Entry<String, SpaceInfo> preInfoEntry : currentPoolSpaceInfo.entrySet()) {
String thisPool = preInfoEntry.getKey();
SpaceInfo thisPoolPreInfo = preInfoEntry.getValue();
SpaceInfo thisPoolPostInfo = futurePoolSpaceInfo.get(thisPool);
LOGGER.trace(" examining pool: {}", thisPool);
// Pool has disappeared or size has changed
if (thisPoolPostInfo == null || !thisPoolPostInfo.equals(thisPoolPreInfo)) {
changedPools.add(thisPool);
LOGGER.trace(" pool {} has changed or disappeared", thisPool);
}
}
// 1. b) new pools have appeared.
for (String thisPool : futurePoolSpaceInfo.keySet()) {
if (!currentPoolSpaceInfo.containsKey(thisPool)) {
changedPools.add(thisPool);
LOGGER.trace(" pool {} has appeared", thisPool);
}
}
// 2. Nothing doing?, do nothing more.
if (changedPools.isEmpty()) {
LOGGER.trace(" no pools have changed");
return;
}
// 3. Build the (future) reverse map: pool to Set of Poolgroups.
Map<String,Set<String>> poolToPoolgroups = new HashMap<>();
for (Map.Entry<String, Set<String>> pgPoolEntry : futurePoolgroupMembership.entrySet()) {
String thisPoolgroup = pgPoolEntry.getKey();
for (String thisPool : pgPoolEntry.getValue()) {
Set<String> thisPoolPg = poolToPoolgroups.get(thisPool);
if (thisPoolPg == null) {
thisPoolPg = new HashSet<>();
poolToPoolgroups.put(thisPool, thisPoolPg);
}
thisPoolPg.add(thisPoolgroup);
}
}
// 4. Mark each Poolgroup (that has a changed pool as a member) as to-be-updated.
for (String pool : changedPools) {
Set<String> poolgroupSet = poolToPoolgroups.get(pool);
if (poolgroupSet == null) {
continue; // skip if pool is not a member of any poolgroup
}
for (String poolgroup : poolgroupSet) {
recalcPoolgroup.add(poolgroup);
if (changedPools.isEmpty()) {
LOGGER.trace(" poolgroup {} is marked as to be recalculated.",
poolgroup);
return;
}
}
}
}
}