package org.dcache.services.info;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import dmg.cells.nucleus.CellCommandListener;
import dmg.cells.nucleus.CellInfo;
import dmg.cells.nucleus.CellInfoProvider;
import dmg.cells.nucleus.CellMessageReceiver;
import org.dcache.services.info.base.BadStatePathException;
import org.dcache.services.info.base.State;
import org.dcache.services.info.base.StateObservatory;
import org.dcache.services.info.base.StatePath;
import org.dcache.services.info.conduits.Conduit;
import org.dcache.services.info.gathers.DataGatheringScheduler;
import org.dcache.services.info.gathers.MessageHandlerChain;
import org.dcache.services.info.serialisation.SimpleTextSerialiser;
import org.dcache.services.info.serialisation.StateSerialiser;
import org.dcache.util.Args;
import org.dcache.vehicles.InfoGetSerialisedDataMessage;
public class InfoProvider implements CellCommandListener, CellInfoProvider,
CellMessageReceiver
{
private static final Logger LOGGER = LoggerFactory.getLogger(InfoProvider.class);
private static final String ADMIN_INTERFACE_OK = "Done.";
private static final String ADMIN_INTERFACE_NONE = "(none)";
private static final String ADMIN_INTERFACE_LIST_PREFIX = " ";
private static final String TOPLEVEL_DIRECTORY_LABEL = "(top)";
private String _defaultSerialiser = SimpleTextSerialiser.NAME;
private Map<String,Conduit> _conduits;
private DataGatheringScheduler _scheduler;
private MessageHandlerChain _msgHandlerChain;
private StateSerialiser _currentSerialiser;
private Map<String,StateSerialiser> _availableSerialisers;
private StatePath _startSerialisingFrom;
private State _state;
private StateObservatory _observatory;
/**
* Provide information for the info command.
*/
@Override
public void getInfo(PrintWriter pw)
{
pw.println(" Overview of the info cell:\n");
pw.print(_conduits.size());
pw.print(" conduit"+ (_conduits.size()==1?"":"s") + " (");
int count=0;
for (Conduit c : _conduits.values()) {
count += c.isEnabled() ? 1 : 0;
}
pw.print(count);
pw.println(" enabled)");
pw.print(_scheduler.listActivity().size());
pw.println(" data-gathering activities.");
pw.print(_availableSerialisers.size());
pw.println(" available serialisers.");
_state.getInfo(pw);
}
@Override
public CellInfo getCellInfo(CellInfo info)
{
return info;
}
@Required
public void setState(State state)
{
_state = state;
}
@Required
public void setStateObservatory(StateObservatory observatory)
{
_observatory = observatory;
}
@Required
public void setDataGatheringScheduler(DataGatheringScheduler scheduler)
{
_scheduler = scheduler;
}
@Required
public void setSerialisers(Iterable<StateSerialiser> serialisers)
{
Map<String,StateSerialiser> available = new HashMap<>();
for (StateSerialiser serialiser : serialisers) {
available.put(serialiser.getName(), serialiser);
}
_availableSerialisers = available;
if (_defaultSerialiser != null) {
_currentSerialiser = _availableSerialisers.get(_defaultSerialiser);
}
}
@Required
public void setDefaultSerialiser(String name)
{
_defaultSerialiser = name;
if (_availableSerialisers != null) {
_currentSerialiser = _availableSerialisers.get(name);
}
}
@Required
public void setConduits(Iterable<Conduit> conduits)
{
_conduits = new HashMap<>();
for (Conduit conduit : conduits) {
_conduits.put(conduit.toString(), conduit);
}
}
/**
* Switch on "enable" a named conduit.
* @param name the name of the conduit to enable.
* @return null if there was no problem, or a description of the problem otherwise.
*/
private String enableConduit(String name)
{
Conduit con = _conduits.get(name);
if (con == null) {
return "Conduit " + name + " was not found.";
}
if (con.isEnabled()) {
return "Conduit " + name + " is already enabled.";
}
con.enable();
return null;
}
/**
* Attempt to disable a named conduit.
* @param name the name of the conduit to disable
* @return null if there was no problem, a description of the problem otherwise.
*/
private String disableConduit(String name) {
Conduit con = _conduits.get(name);
if (con == null) {
return "Conduit " + name + " was not found.";
}
if (!con.isEnabled()) {
return "Conduit " + name + " is not currently enabled.";
}
con.disable();
return null;
}
@Required
public void setMessageHandlerChain(MessageHandlerChain mhc)
{
_msgHandlerChain = mhc;
}
public synchronized InfoGetSerialisedDataMessage messageArrived(InfoGetSerialisedDataMessage message)
{
LOGGER.trace("Received InfoGetSerialisedDataMessage.");
StateSerialiser serialiser = _availableSerialisers.get(message.getSerialiser());
if (serialiser == null) {
LOGGER.error("Couldn't find serialiser {}", message.getSerialiser());
throw new IllegalArgumentException("no such serialiser");
}
String data;
if (message.isCompleteDump()) {
data = serialiser.serialise();
} else {
StatePath path = StatePath.buildFromList(message.getPathElements());
data = serialiser.serialise(path);
}
message.setData(data);
return message;
}
/**
* H A N D L E R A D M I N C O M M A N D S
*/
public static final String fh_handler_ls = "List all known Message handlers. These are responsible for updating dCache state.";
public static final String hh_handler_ls = "";
public String ac_handler_ls_$_0(Args args)
{
StringBuilder sb = new StringBuilder();
sb.append("Incoming Message Handlers:\n");
String msgHandlers[] = _msgHandlerChain.listMessageHandlers();
if (msgHandlers.length > 0) {
for (String msgHandler : msgHandlers) {
sb.append(ADMIN_INTERFACE_LIST_PREFIX);
sb.append(msgHandler);
sb.append("\n");
}
} else {
sb.append(ADMIN_INTERFACE_LIST_PREFIX);
sb.append(ADMIN_INTERFACE_NONE);
sb.append("\n");
}
return sb.toString();
}
/**
* C O N D U I T A D M I N C O M M A N D S
*/
public static final String fh_conduits_ls = "List all known conduits. Conduits provide read-only access to dCache current state.";
public String ac_conduits_ls_$_0(Args args)
{
StringBuilder sb = new StringBuilder();
sb.append("Conduits:\n");
if (_conduits.size() > 0) {
for (Conduit con : _conduits.values()) {
sb.append(ADMIN_INTERFACE_LIST_PREFIX);
sb.append(con.toString());
sb.append(" ");
sb.append(con.getInfo());
sb.append("\n");
}
} else {
sb.append(ADMIN_INTERFACE_LIST_PREFIX);
sb.append(ADMIN_INTERFACE_NONE);
sb.append("\n");
}
return sb.toString();
}
public static final String fh_conduits_enable = "Enabled the named conduit.";
public static final String hh_conduits_enable = "<conduit name>";
public String ac_conduits_enable_$_1(Args args)
{
String errMsg = enableConduit(args.argv(0));
return errMsg == null ? ADMIN_INTERFACE_OK : errMsg;
}
public static final String fh_conduits_disable = "Disable the named conduit.";
public static final String hh_conduits_disable = "<conduit name>";
public String ac_conduits_disable_$_1(Args args)
{
String errMsg = disableConduit(args.argv(0));
return errMsg == null ? ADMIN_INTERFACE_OK : errMsg;
}
/**
* D G A A D M I N C O M M A N D S
*/
public static final String fh_dga_ls = "list all known data-gathering activity, whether enabled or not.";
public String ac_dga_ls_$_0(Args args)
{
StringBuilder sb = new StringBuilder();
sb.append("Data-Gathering Activity:\n");
List<String> dgaList = _scheduler.listActivity();
if (dgaList.size() > 0) {
for (String activity : dgaList) {
sb.append(ADMIN_INTERFACE_LIST_PREFIX);
sb.append(activity);
sb.append("\n");
}
} else {
sb.append(ADMIN_INTERFACE_LIST_PREFIX);
sb.append(ADMIN_INTERFACE_NONE);
sb.append("\n");
}
return sb.toString();
}
public static final String hh_dga_disable = "<name>";
public static final String fh_dga_disable = "disable a data-gathering activity.";
public String ac_dga_disable_$_1(Args args)
{
String errMsg = _scheduler.disableActivity(args.argv(0));
return errMsg == null ? ADMIN_INTERFACE_OK : errMsg;
}
public static final String hh_dga_enable = "<name>";
public static final String fh_dga_enable = "enable a data-gathering activity. The next trigger time is randomly chosen.";
public String ac_dga_enable_$_1(Args args)
{
String errMsg = _scheduler.enableActivity(args.argv(0));
return errMsg == null ? ADMIN_INTERFACE_OK : errMsg;
}
public static final String hh_dga_trigger = "<name>";
public static final String fh_dga_trigger = "trigger data-gathering activity <name> now.";
public String ac_dga_trigger_$_1(Args args)
{
String errMsg = _scheduler.triggerActivity(args.argv(0));
return errMsg == null ? ADMIN_INTERFACE_OK : errMsg;
}
/**
* S T A T E A D M I N C O M M A N D S
*/
public static final String fh_state_ls = "List current status of dCache";
public static final String hh_state_ls = "[<path>]";
public String ac_state_ls_$_0_1(Args args)
{
StringBuilder sb = new StringBuilder();
StatePath start = _startSerialisingFrom;
if (args.argc() == 1) {
try {
start = processPath(_startSerialisingFrom, args.argv(0));
} catch (BadStatePathException e) {
return e.toString();
}
}
sb.append("\n");
if (start != null) {
sb.append(_currentSerialiser.serialise(start));
} else {
sb.append(_currentSerialiser.serialise());
}
if (sb.length() > 1) {
sb.append("\n");
}
return sb.toString();
}
public static final String fh_state_output = "view or change output format for the \"state ls\" command.";
public static final String hh_state_output = "[<format>]";
public String ac_state_output_$_0_1(Args args)
{
StringBuilder sb = new StringBuilder();
if (args.argc() == 0) {
sb.append("Current output: ");
sb.append(_currentSerialiser.getName());
sb.append("\n");
sb.append(list_valid_output());
} else {
StateSerialiser newSerialiser = _availableSerialisers.get(args.argv(0));
if (newSerialiser != null) {
_currentSerialiser = newSerialiser;
sb.append("Will use ");
sb.append(_currentSerialiser.getName());
sb.append(" formatting for future output.");
} else {
sb.append("Unknown output format \"");
sb.append(args.argv(0));
sb.append("\".\n");
sb.append(list_valid_output());
}
}
return sb.toString();
}
private String list_valid_output()
{
StringBuilder sb = new StringBuilder();
sb.append("Valid output format");
sb.append((_availableSerialisers.size() > 1) ? "s are" : " is");
sb.append(": ");
for (Iterator<String> itr = _availableSerialisers.keySet().iterator(); itr.hasNext();) {
sb.append(itr.next());
if (itr.hasNext()) {
sb.append(", ");
}
}
sb.append("\n");
return sb.toString();
}
public static final String fh_state_pwd = "List the current directory for state ls";
public String ac_state_pwd_$_0(Args args)
{
StringBuilder sb = new StringBuilder();
if (_startSerialisingFrom != null) {
sb.append(_startSerialisingFrom.toString());
} else {
sb.append(TOPLEVEL_DIRECTORY_LABEL);
}
return sb.toString();
}
public static final String fh_state_cd = "Change directory for state ls; path elements must be slash-separated";
public static final String hh_state_cd = "<path>";
public String ac_state_cd_$_1(Args args)
{
StatePath newPath;
try {
newPath = processPath(_startSerialisingFrom, args.argv(0));
} catch (BadStatePathException e) {
return e.toString();
}
_startSerialisingFrom = newPath;
StringBuilder sb = new StringBuilder();
sb.append("Path now: ");
sb.append(ac_state_pwd_$_0(null));
return sb.toString();
}
/**
* Create a new StatePath based on a current path and a description of the new path.
* The description of the path may be relative or absolute. The separator between path
* elements is a forward slash ("/"). Absolute paths start with '/', all other paths
* are relative.
* <p>
* Two special paths are also understood: "." and "..". The "." path is always the current
* element and ".." the parent element. This allows for a filesystem-like paths to be
* expressed, where sibling elements can be navigated to, using a combination of ".." and
* the sibling path-element name.
* <p>
* Paths are normally displayed as a dot-separated list of elements. This may lead to
* confusion as, to change directory to <tt>aaa.bbb.ccc</tt> one would specify (as an absolute
* path) <tt>/aaa/bbb/ccc</tt> or some path relative to the current location.
* <p>
* To allow users to "type what they see", a special case is introduced. If the path has
* no forward-slash and is neither of the two special elements ("." and ".."), then
* the path is treated as relative, with dot-separated list of elements.
* <p>
* One final refinement is the presence of quote marks around the element. If the first and last
* characters are double-quote marks, then the contents is treated as a single path element
* and no further special treatment is taken.
*
* @param cwd current path
* @param path description of new path
* @return a new path, or null if the path is root
* @throws BadPathException if the relative path is impossible.
*/
private StatePath processPath(StatePath cwd, String path) throws BadStatePathException
{
String[] pathElements;
StatePath currentPath = cwd;
boolean quoted = false;
/* Treat a quoted argument as a single entry--don't split */
if (path.startsWith("\"") && path.endsWith("\"")) {
pathElements = new String[1];
pathElements[1] = path.substring(1, path.length()-2);
quoted = true;
} else {
if (path.startsWith("/")) {
currentPath = null; // cd is with absolute path, so reset our path.
}
pathElements = path.split("/");
}
/**
* As a special case: no slashes, single element in list containing dots that
* isn't "." or "..". Treat this as a relative path, splitting on the dots.
*/
if (!quoted && pathElements.length == 1) {
String element = pathElements [0];
if (!element.contains("/") && element.contains(".") && !element.equals(".") && !element.equals("..")) {
if (currentPath != null) {
currentPath = currentPath.newChild(StatePath
.parsePath(element));
} else {
currentPath = StatePath.parsePath(element);
}
return currentPath;
}
}
for (int i = 0; i < pathElements.length; i++) {
switch (pathElements[i]) {
case "..":
// Ascend once in the hierarchy.
if (currentPath != null) {
currentPath = currentPath.parentPath();
} else {
throw (new BadStatePathException("You cannot cd upward from the top-most element."));
}
break;
case ".":
// Do nothing, just to emulate Unix FS semantics.
break;
default:
if (pathElements[i].length() > 0) {
if (currentPath == null) {
currentPath = new StatePath(pathElements[i]);
} else {
currentPath = currentPath
.newChild(pathElements[i]);
}
} else {
if (i == 0) {
currentPath = null; // ignore initial empty element from absolute paths
} else {
throw (new BadStatePathException("Path contains zero-length elements."));
}
}
break;
}
}
return currentPath;
}
/**
* W A T C H E R A D M I N C O M M A N D S
*/
public static final String fh_watchers_ls = "list all registered dCache state watchers";
public String ac_watchers_ls_$_0(Args args)
{
StringBuilder sb = new StringBuilder();
sb.append("State Watchers:\n");
String watcherNames[] = _observatory.listStateWatcher();
if (watcherNames.length > 0) {
for (String name : watcherNames) {
sb.append(ADMIN_INTERFACE_LIST_PREFIX);
sb.append(name);
sb.append("\n");
}
} else {
sb.append(ADMIN_INTERFACE_LIST_PREFIX);
sb.append(ADMIN_INTERFACE_NONE);
sb.append("\n");
}
return sb.toString();
}
public static final String fh_watchers_enable = "enable a registered dCache state watcher";
public String ac_watchers_enable_$_1(Args args)
{
int count;
count = _observatory.enableStateWatcher(args.argv(0));
switch (count) {
case 0:
return "No matching watcher: " + args.argv(0);
case 1:
return "Done.";
default:
return "Name matching multiple Watchers, all now enabled.";
}
}
public static final String fh_watchers_disable = "disable a registered dCache state watcher";
public String ac_watchers_disable_$_1(Args args)
{
int count;
count = _observatory.disableStateWatcher(args.argv(0));
switch (count) {
case 0:
return "No matching watcher: " + args.argv(0);
case 1:
return "Done.";
default:
return "Name matching multiple Watchers, all now disabled.";
}
}
}