package org.dcache.services.info.secondaryInfoProviders;
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.IntegerStateValue;
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.base.StringStateValue;
import org.dcache.services.info.stateInfo.ReservationInfo;
import org.dcache.services.info.stateInfo.ReservationInfoVisitor;
/**
* The ReservationByDescMaintainer class implements StateWatcher. It
* maintains an aggregation of SRM reservations by (description, VO) ordered
* pairs: the space statics of SRM reservations that have the same
* reservation description are aggregated.
*/
public class ReservationByDescMaintainer extends AbstractStateWatcher
{
private static final Logger LOGGER =
LoggerFactory.getLogger(ReservationByDescMaintainer.class);
public static final String PATH_ELEMENT_BY_DESCRIPTION_BRANCH =
"by-description";
public static final String PATH_ELEMENT_VO_NAME_METRIC = "vo";
public static final String PATH_ELEMENT_SPACE_BRANCH = "space";
public static final String PATH_ELEMENT_TOTAL_METRIC = "total";
public static final String PATH_ELEMENT_ALLOCATED_METRIC = "allocated";
public static final String PATH_ELEMENT_USED_METRIC = "used";
public static final String PATH_ELEMENT_FREE_METRIC = "free";
public static final String PATH_ELEMENT_RESERVATIONS_BRANCH =
"reservations";
public static final StatePath RESERVATIONS_BASE_PATH =
StatePath.parsePath("summary.reservations.by-VO");
private static final String PREDICATE_PATHS[] =
{ "reservations.*",
"reservations.*.space.*",
"reservations.*.description",
"reservations.*.state",
"reservations.*.authorisation.group"};
/**
* Information aggregated over all reservations with the same description
* for the same VO.
*/
private class ReservationSummaryInfo
{
private long _total;
private long _free;
private long _allocated;
private long _used;
private final Set<String> _ids = new HashSet<>();
@Override
public int hashCode()
{
return (int) _total ^ (int) _free ^ (int) _allocated ^ (int) _used ^
_ids.hashCode();
}
@Override
public boolean equals(Object other)
{
if (this == other) {
return true;
}
if (!(other instanceof ReservationSummaryInfo)) {
return false;
}
ReservationSummaryInfo otherSummary =
(ReservationSummaryInfo) other;
if (otherSummary._total != _total || otherSummary._free != _free ||
otherSummary._allocated != _allocated ||
otherSummary._used != _used) {
return false;
}
return otherSummary._ids.equals(_ids);
}
/**
* Add a SRM reservation to this Reservation summary.
*
* @param reservationId the ID of this reservation.
* @param info the information about this reservation.
*/
public void addReservationInfo(String reservationId, ReservationInfo info)
{
if (info.hasTotal()) {
_total += info.getTotal();
}
if (info.hasUsed()) {
_used += info.getUsed();
}
if (info.hasFree()) {
_free += info.getFree();
}
if (info.hasAllocated()) {
_allocated += info.getAllocated();
}
_ids.add(reservationId);
}
public boolean hasId(String ID)
{
return _ids.contains(ID);
}
public long getTotal()
{
return _total;
}
public long getUsed()
{
return _used;
}
public long getFree()
{
return _free;
}
public long getAllocated()
{
return _allocated;
}
public boolean totalNeedsUpdating(ReservationSummaryInfo oldInfo)
{
return oldInfo == null || oldInfo.getTotal() != getTotal();
}
public boolean usedNeedsUpdating(ReservationSummaryInfo oldInfo)
{
return oldInfo == null || oldInfo.getUsed() != getUsed();
}
public boolean freeNeedsUpdating(ReservationSummaryInfo oldInfo)
{
return oldInfo == null || oldInfo.getFree() != getFree();
}
public boolean allocatedNeedsUpdating(ReservationSummaryInfo oldInfo)
{
return oldInfo == null || oldInfo.getAllocated() != getAllocated();
}
/**
* Purge all IDs that are not present in this ReservationSummaryInfo
* but are missing from a new version of ReservationSummaryInfo
*
* @param update the StateUpdate to add adjust.
* @param basePath the base path of the IDs
* @param newInfo the new version of this ReservationSummaryInfo
*/
public void purgeMissingIds(StateUpdate update, StatePath basePath,
ReservationSummaryInfo newInfo)
{
for (String id : _ids) {
if (!newInfo.hasId(id)) {
update.purgeUnder(basePath.newChild(id));
}
}
}
/**
* Update the provided StateUpdate object so that later processing
* this StateUpdate will alter dCache state such that it reflects the
* information held in this object.
*
* @param update the StateUpdate to use
* @param voName the name of the VO
* @param basePath the StatePath under which metrics will be added
* @param oldInfo the previous ReservationSummaryInfo or null if this
* description is new.
*/
public void updateMetrics(StateUpdate update, String voName,
StatePath basePath, ReservationSummaryInfo oldInfo)
{
StatePath spacePath = basePath.newChild(PATH_ELEMENT_SPACE_BRANCH);
if (totalNeedsUpdating(oldInfo)) {
update.appendUpdate(spacePath
.newChild(PATH_ELEMENT_TOTAL_METRIC), new IntegerStateValue(getTotal(), true));
}
if (freeNeedsUpdating(oldInfo)) {
update.appendUpdate(spacePath
.newChild(PATH_ELEMENT_FREE_METRIC), new IntegerStateValue(getFree(), true));
}
if (allocatedNeedsUpdating(oldInfo)) {
update.appendUpdate(spacePath
.newChild(PATH_ELEMENT_ALLOCATED_METRIC), new IntegerStateValue(getAllocated(), true));
}
if (usedNeedsUpdating(oldInfo)) {
update.appendUpdate(spacePath
.newChild(PATH_ELEMENT_USED_METRIC), new IntegerStateValue(getUsed(), true));
}
StatePath resvPath =
basePath.newChild(PATH_ELEMENT_RESERVATIONS_BRANCH);
/* Activity if there was an existing summary information */
if (oldInfo != null) {
oldInfo.purgeMissingIds(update, resvPath, this);
/* Add those entries that are new */
for (String newId : _ids) {
if (!oldInfo.hasId(newId)) {
update.appendUpdate(resvPath
.newChild(newId), new StateComposite(true));
}
}
} else {
/* Add the VO name (we need to this only once) */
update.appendUpdate(basePath.newChild(PATH_ELEMENT_VO_NAME_METRIC), new StringStateValue(voName, true));
/* Add all IDs */
for (String id : _ids) {
update.appendUpdate(resvPath
.newChild(id), new StateComposite(true));
}
}
}
}
@Override
protected String[] getPredicates()
{
return PREDICATE_PATHS;
}
@Override
public void trigger(StateUpdate update, StateExhibitor currentState, StateExhibitor futureState)
{
Map<String, ReservationInfo> currentResv =
ReservationInfoVisitor.getDetails(currentState);
Map<String, ReservationInfo> futureResv =
ReservationInfoVisitor.getDetails(futureState);
// build mapping from description to summary data
Map<String, Map<String, ReservationSummaryInfo>> currentSummary =
buildSummaryInfo(currentResv);
Map<String, Map<String, ReservationSummaryInfo>> futureSummary =
buildSummaryInfo(futureResv);
purgeMissingSummaries(update, currentSummary, futureSummary);
addMetrics(update, currentSummary, futureSummary);
}
/**
* Build a Map between VO name and a collection of reservation summary
* information. This collection of reservation summary information is
* itself a Map between the reservation description and a summary of all
* reservations with that same description. Those reservations without a
* description are ignored.
*
* @param reservations a Map between reservation ID and a corresponding
* ReservationInfo object describing that reservation.
* @return a Map of VO to reservation-description summaries.
*/
private Map<String, Map<String, ReservationSummaryInfo>> buildSummaryInfo(Map<String, ReservationInfo> reservations)
{
Map<String, Map<String, ReservationSummaryInfo>> summary = new HashMap<>();
for (Map.Entry<String, ReservationInfo> entry : reservations.entrySet()) {
String reservationId = entry.getKey();
ReservationInfo info = entry.getValue();
/*
* Ignore those reservations that don't have a state or that
* state is a final one
*/
if (!info.hasState() || info.getState().isFinalState()) {
LOGGER.trace("ignoring reservation {} as state is undefined or final",
reservationId);
continue;
}
/* Skip those reservations that don't have a description */
if (!info.hasDescription() || info.getDescription().isEmpty()) {
LOGGER.trace("ignoring reservation {} as description is undefined or empty",
reservationId);
continue;
}
/* Skip all those reservations that don't have a well-defined VO */
if (!info.hasVo() || info.getVo().isEmpty()) {
LOGGER.trace("ignoring reservation {} as VO is undefined or empty",
reservationId);
continue;
}
String voName = info.getVo();
ReservationSummaryInfo thisSummary;
Map<String, ReservationSummaryInfo> thisVoSummary;
thisVoSummary = summary.get(voName);
if (thisVoSummary == null) {
thisVoSummary = new HashMap<>();
summary.put(voName, thisVoSummary);
}
thisSummary = thisVoSummary.get(info.getDescription());
if (thisSummary == null) {
thisSummary = new ReservationSummaryInfo();
thisVoSummary.put(info.getDescription(), thisSummary);
}
// update summary with this reservation.
thisSummary.addReservationInfo(reservationId, info);
}
return summary;
}
/**
* Adjust the StateUpdate object so that those descriptions that have
* disappeared are purged.
*
* @param update
* @param currentDescriptions
* @param futureDescriptions
*/
private void purgeMissingSummaries(StateUpdate update,
Map<String, Map<String, ReservationSummaryInfo>> currentVoInfo,
Map<String, Map<String, ReservationSummaryInfo>> futureVoInfo)
{
for (Map.Entry<String, Map<String, ReservationSummaryInfo>> voEntry : currentVoInfo.entrySet()) {
String voName = voEntry.getKey();
Set<String> currentDescriptions = voEntry.getValue().keySet();
StatePath voBasePath = buildVoPath(voName);
Map<String, ReservationSummaryInfo> futureDescriptions =
futureVoInfo.get(voName);
// If this VO is gone completely, purge everything and move on.
if (futureDescriptions == null) {
update.purgeUnder(voBasePath);
continue;
}
// Otherwise, purge those descriptions that have gone.
for (String thisDescription : currentDescriptions) {
if (!futureDescriptions.containsKey(thisDescription)) {
update.purgeUnder(buildDescriptionPath(voBasePath, thisDescription));
}
}
}
}
/**
* Add metrics that update dCache state to reflect the changes. We assume
* those elements that should be removed have been purged
*
* @param update
* @param currentSummary
* @param futureSummary
*/
private void addMetrics (StateUpdate update,
Map<String, Map<String, ReservationSummaryInfo>> currentSummary,
Map<String, Map<String, ReservationSummaryInfo>> futureSummary)
{
for (Map.Entry<String, Map<String, ReservationSummaryInfo>> voEntry : futureSummary.entrySet()) {
String voName = voEntry.getKey();
LOGGER.trace("Checking vo {}", voName);
Map<String, ReservationSummaryInfo> futureDescriptions =
voEntry.getValue();
Map<String, ReservationSummaryInfo> currentDescriptions =
currentSummary.get(voName);
StatePath voPath = buildVoPath(voName);
/* Scan through descriptions after transition is applied */
for (Map.Entry<String, ReservationSummaryInfo> entry : futureDescriptions.entrySet()) {
String description = entry.getKey();
ReservationSummaryInfo futureInfo = entry.getValue();
/*
* Try to establish the corresponding current
* ReservationSummaryInfo for this description
*/
ReservationSummaryInfo currentInfo =
currentDescriptions != null
? currentDescriptions.get(description) : null;
if (!futureInfo.equals(currentInfo)) {
futureInfo
.updateMetrics(update, voName, buildDescriptionPath(voPath, description), currentInfo);
}
}
}
}
/**
* Build a StatePath for a given VO to the summary of that VO's SRM
* reservations; for example,
* <code>summary.reservations.by-vo.atlas</code>
*
* @param voName The name of the VO
* @return a StatePath pointing to the "by-description" part of this VO's
* reservation summary.
*/
private static StatePath buildVoPath(String voName)
{
if (voName == null) {
throw new IllegalArgumentException("voName is null");
}
return RESERVATIONS_BASE_PATH.newChild(voName);
}
/**
* Build a path to a description's summary information based on a
* voBasePath (something like
* <code>summary.reservations.by-vo.atlas</code>. The returned path is
* something like:
* <code>summary.reservations.by-vo.atlas.by-description.MCDISK</code>
*
* @param voBasePath
* @param description
* @return
*/
private static StatePath buildDescriptionPath(StatePath voBasePath, String description)
{
return voBasePath.newChild(PATH_ELEMENT_BY_DESCRIPTION_BRANCH).newChild(description);
}
}