package org.dcache.services.info.secondaryInfoProviders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.dcache.services.info.base.IntegerStateValue;
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.SimpleIntegerMapVisitor;
import org.dcache.services.info.stateInfo.SimpleStringMapVisitor;
/**
* The SpaceManager records some capacity information about a linkgroup; specifically,
* just the Free and Reserved spaces. The Total and Used sizes are not maintained, so
* must be calculated from other information.
* <p>
* Used space for a linkgroup is the sum of all used space within corresponding
* SRM space reservations. Total space is the sum of Free- and Used- space in the linkgroup.
* <p>
* This StateWatcher maintains a linkgroup's Total and Used space metrics by triggering
* when these values change.
*
* @author Paul Millar <paul.millar@desy.de>
*/
public class LinkgroupTotalSpaceMaintainer extends AbstractStateWatcher
{
private static final Logger LOGGER = LoggerFactory.getLogger(LinkgroupTotalSpaceMaintainer.class);
private static final StatePath RESERVATIONS = new StatePath("reservations");
private static final StatePath LINKGROUPS = new StatePath("linkgroups");
private static final StatePath LINKGROUPREF = new StatePath("linkgroupref");
private static final StatePath SPACE_USED = StatePath.parsePath("space.used");
private static final StatePath SPACE_FREE = StatePath.parsePath("space.free");
private static final String PREDICATE_PATHS[] = { "reservations.*.space.used",
"linkgroups.*.space.free",
"linkgroups.*.reservations.*"};
/**
* Provide a list of the paths we're interested in.
*/
@Override
protected String[] getPredicates()
{
return PREDICATE_PATHS;
}
@Override
public void trigger(StateUpdate update, StateExhibitor currentState,
StateExhibitor futureState)
{
super.trigger(update, currentState, futureState);
LOGGER.trace("Watcher {} triggered", getClass().getSimpleName());
// Build a mapping of how linkgroup-IDs map to the corresponding space.free metric
Map<String,Long> freeSpaceAfter = SimpleIntegerMapVisitor.buildMap(futureState, LINKGROUPS, SPACE_FREE);
// Build a mapping of how reservation-IDs map to space.used metric
Map<String,Long> usedSpaceAfter = SimpleIntegerMapVisitor.buildMap(futureState, RESERVATIONS, SPACE_USED);
// Build a mapping of reservation-IDs to linkgroup-IDs
Map<String,String> reservationToLinkgroup = SimpleStringMapVisitor.buildMap(futureState, RESERVATIONS, LINKGROUPREF);
Set<String> linkgroupsToUpdate = new HashSet<>();
// Update our list of linkgroups to update
addLinkgroupsWhereUsedSpaceChanged(currentState, linkgroupsToUpdate, usedSpaceAfter, reservationToLinkgroup);
addLinkgroupsWhereFreeChanged(currentState, linkgroupsToUpdate, freeSpaceAfter);
if (linkgroupsToUpdate.isEmpty()) {
// This should never happen!
LOGGER.warn("{} triggered, but apparently nothing needs doing.", getClass().getSimpleName());
return;
}
addLinkgroupChanges(update, linkgroupsToUpdate, freeSpaceAfter, usedSpaceAfter, reservationToLinkgroup);
}
/**
* Provide a the set of linkgroup-IDs where at least one reservation has changed its Used
* space metric.
*/
private void addLinkgroupsWhereUsedSpaceChanged(StateExhibitor currentState,
Set<String> linkgroupsToUpdate, Map<String,Long> usedSpaceAfter,
Map<String,String> reservationToLinkgroup)
{
// Build map of the current reservation-ID to space.used metric
Map<String,Long> usedSpaceNow = SimpleIntegerMapVisitor.buildMap(currentState, RESERVATIONS, SPACE_USED);
if (usedSpaceNow.equals(usedSpaceAfter)) {
LOGGER.trace("No update needed for reservation used space changing.");
return;
}
LOGGER.trace("Updates due to reservation used space changing:");
// Add the corresponding linkgroup for each space that has changed.
for (String reservationId : usedSpaceAfter.keySet()) {
if (!usedSpaceAfter.get(reservationId).equals(usedSpaceNow.get(reservationId))) {
String linkgroupId = reservationToLinkgroup.get(reservationId);
LOGGER.trace(" linkgroup: {}", linkgroupId);
linkgroupsToUpdate.add(linkgroupId);
}
}
}
/**
* Provide the Set of linkgroup-IDs where the linkgroup's Free space metric will change.
* @param transition the StateTransition under consideration.
* @return the Set of linkgroup-IDs where the Free space metric will change.
*/
private void addLinkgroupsWhereFreeChanged(StateExhibitor currentState,
Set<String> linkgroupsToUpdate, Map<String, Long> freeSpaceAfter)
{
// Build map from linkgroup-ID to used metric, both now and after.
Map<String,Long> freeSpaceNow = SimpleIntegerMapVisitor.buildMap(currentState, LINKGROUPS, SPACE_USED);
if (freeSpaceNow.equals(freeSpaceAfter)) {
LOGGER.trace("No update needed for linkgroup free space changing.");
return;
}
LOGGER.trace("Updates due to linkgroup free space changing:");
for (String linkgroupId : freeSpaceAfter.keySet()) {
if (!freeSpaceAfter.get(linkgroupId).equals(freeSpaceNow.get(linkgroupId))) {
LOGGER.trace(" linkgroup: {}", linkgroupId);
linkgroupsToUpdate.add(linkgroupId);
}
}
}
private void addLinkgroupChanges(StateUpdate update, Set<String> linkgroupsToUpdate,
Map<String,Long> freeStateAfter, Map<String,Long> usedSpaceAfter,
Map<String,String> reservationToLinkgroup)
{
for (String linkgroupId : linkgroupsToUpdate) {
LOGGER.trace("Building update for linkgroup {}", linkgroupId);
StatePath thisLinkgroupSpace = LINKGROUPS.newChild(linkgroupId).newChild("space");
/**
* Update the new space.used metric for this linkgroup.
*/
long used = 0;
for (Map.Entry<String, Long> entry : usedSpaceAfter.entrySet()) {
if (linkgroupId.equals(reservationToLinkgroup.get(entry.getKey()))) {
used += entry.getValue();
}
}
update.appendUpdate(thisLinkgroupSpace.newChild("used"), new IntegerStateValue(used));
/**
* Try to update the space.total metric for this linkgroup, if we don't yet know this
* linkgroup's free metric then this will not be possible.
*/
Long freeLong = freeStateAfter.get(linkgroupId);
if (freeLong != null) {
update.appendUpdate(thisLinkgroupSpace.newChild("total"),
new IntegerStateValue(used + freeLong));
} else {
LOGGER.trace("failed to find linkgroup {} in freeStateAfter", linkgroupId);
}
}
}
}