package org.dcache.services.info.gathers.routingmanager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; import java.util.Set; import dmg.cells.nucleus.UOID; import org.dcache.services.info.base.StateComposite; import org.dcache.services.info.base.StatePath; import org.dcache.services.info.base.StateUpdate; import org.dcache.services.info.base.StateUpdateManager; import org.dcache.services.info.gathers.CellMessageHandlerSkel; import org.dcache.services.info.gathers.MessageMetadataRepository; import org.dcache.services.info.gathers.cells.CellInfoMsgHandler; /** * This class handles incoming messages from the RoutingMgr cell of some specific domain. The messages are the * result of an "ls -x" admin command, which returns a dump of that RoutingMgr's current routing knowledge. * <p> * The message is an array of three items: the domain Name (that the RoutingMgr cell is running within), the * Set of locally registered cells and the knowledge of remote cells. The remote knowledge is in the form of a Map between * a domain's name and the Set of registered cells at that domain. * <p> * Given the set of locally registered and remote registered cells, a Map of well-known cells is derived, which maps the * well-known cell's name to its corresponding domain. This assumes (and enforces) that registered cell names are unique. * If they are not unique, then the locally registered cell is chosen preferentially. If two remote registered cells have * the same name then it is unspecified which one will be chosen. * <p> * The information is added under the RoutingMgr's domain, under the "routing" branch. There are three * sub-branches of "routing" : named-cells, local and remote. The following illustrates this: * <pre> * [domains] * | * +--[<domain name #1>] * | | * | +--[routing] * | | | * | | +--[named-cells] * | | | | * | | | +--[<cell name #1>] * | | | | | * | | | | +--[<domain name #1>] * | | | | * | | | +--[<cell name #2>] * | | | | | * | | | | +--[<domain name #2>] * | | | | * | | | +--[<cell name #3>] * | | | | * | | | +--[<domain name #2>] * | | | * | | +--[local] * | | | | * | | | +--[<cell name #1>] * | | | * | | +--[remote] * | | | | * | | | +--[<domain name #2>] * | | | | * | | | +--[<cell name #2>] * | | | | * | | | +--[<cell name #3>] * | | | * | | +--[cells] * | | | | * </pre> * * @author Paul Millar <paul.millar@desy.de> */ public class RoutingMgrMsgHandler extends CellMessageHandlerSkel { private static final Logger LOGGER = LoggerFactory.getLogger(CellInfoMsgHandler.class); private static final StatePath DOMAINS_PATH = new StatePath("domains"); public RoutingMgrMsgHandler(StateUpdateManager sum, MessageMetadataRepository<UOID> msgMetaRepo) { super(sum, msgMetaRepo); } @SuppressWarnings("unchecked") @Override public void process(Object msgPayload, long metricLifetime) { LOGGER.trace("received msg."); if (!msgPayload.getClass().isArray()) { LOGGER.error("received a reply message that isn't an array; type is {}", msgPayload.getClass().getName()); return; } Object result[] = (Object[]) msgPayload; // Extract the information from the msg payload. String domainName = (String) result[0]; Set<String> localExports = (Set<String>) result[1]; Map<String,Set<String>> domainHash = (Map<String,Set<String>>) result[2]; // Construct our well-known cells map. Map<String,String> wellKnownCells = new HashMap<>(); buildWellKnownCells(wellKnownCells, domainName, localExports, domainHash); if (wellKnownCells.isEmpty() && localExports.isEmpty() && domainHash.isEmpty()) { LOGGER.debug("Message from domain {} with no well-known cells", domainName); return; } // Build our new metrics StatePath routingPath = DOMAINS_PATH.newChild(domainName).newChild("routing"); StateUpdate update = new StateUpdate(); addWellKnownCells(update, routingPath, wellKnownCells, metricLifetime); addLocalCells(update, routingPath, localExports, metricLifetime); addRemoteCells(update, routingPath, domainHash, metricLifetime); applyUpdates(update); } /** * Build a well-known cells mapping. This maps from a cell name to the corresponding domain. * If there is a name-clash, with two registered cells having the same name, the local cell * is chosen in preference to a remote cell. If two remote cells have the same name, which * cell is chosen is not specified. That all said, one should never see a name clash. * @param wellKnownCells the Map of wellKnownCells we are to update with information * @param domainName the name of the domain the RoutingMgr cell is running within * @param localExports the Set of cell names of all locally registered cells * @param domainHash the Map between a (remote) domain and the registered cells the RoutingMgr * knows about. */ private void buildWellKnownCells(Map<String,String> wellKnownCells, String domainName, Set<String> localExports, Map<String,Set<String>> domainHash) { for (Map.Entry<String, Set<String>> entry : domainHash.entrySet()) { String thisDomainName = entry.getKey(); for (String thisCellName : entry.getValue()) { wellKnownCells.put(thisCellName, thisDomainName); } } for (String cellName : localExports) { wellKnownCells.put(cellName, domainName); } } /** * Add the new metrics for the well-known cells. This maps all well-known cell name to the * corresponding domain under the branch: * <pre> * domains.<this domain name>.routing.well-known.<cell name>.<domain name> * </pre> * @param update the StateUpdate to append metrics to * @param routingPath the StatePath to the appropriate "routing" branch. * @param wellKnownCells the Map between cell name and domain name * @param metricLifetime how long, in seconds, these metrics should last. */ private void addWellKnownCells(StateUpdate update, StatePath routingPath, Map<String,String> wellKnownCells, long metricLifetime) { StatePath namedCellsPath = routingPath.newChild("named-cells"); for (Map.Entry<String, String> entry : wellKnownCells.entrySet()) { String thisCellName = entry.getKey(); String thisDomainName = entry.getValue(); StatePath thisEntryPath = namedCellsPath.newChild(thisCellName).newChild(thisDomainName); update.appendUpdate(thisEntryPath, new StateComposite(metricLifetime)); } } /** * Add new metrics for the local registered cells. These are recorded as metrics like: * <pre> * domains.<this domain name>.routing.local.<cell name> * </pre> * @param update the StateUpdate to append metrics to * @param routingPath the StatePath to the appropriate routing branch. * @param localCells the Set of cell names * @param metricLifetime how long, in seconds, the metrics should last. */ private void addLocalCells(StateUpdate update, StatePath routingPath, Set<String> localCells, long metricLifetime) { StatePath localCellsPath = routingPath.newChild("local"); for (String cellName : localCells) { StatePath thisCellPath = localCellsPath.newChild(cellName); update.appendUpdate(thisCellPath, new StateComposite(metricLifetime)); } } /** * Add new metrics for the remote registered cells. These are recorded as metrics like: * <pre> * domains.<this domain name>.routing.remote.<domain name>.<cell name> * </pre> * @param update the StateUpdate to append metrics to * @param routingPath the StatePath to the appropriate routing branch. * @param domainHash the Map between a domain name and the Set of cell names * @param metricLifetime how long, in seconds, the metrics should last. */ private void addRemoteCells(StateUpdate update, StatePath routingPath, Map<String,Set<String>> domainHash, long metricLifetime) { StatePath remoteCellsPath = routingPath.newChild("remote"); for (Map.Entry< String, Set<String>> entry : domainHash.entrySet()) { StatePath domainPath = remoteCellsPath.newChild(entry.getKey()); for (String cellName : entry.getValue()) { StatePath cellPath = domainPath.newChild(cellName); update.appendUpdate(cellPath, new StateComposite(metricLifetime)); } } } }