Skip to content

Commit

Permalink
pool, plugin: Add admin commands for plugins
Browse files Browse the repository at this point in the history
Motivation:

Sometimes it's helpful to be able to print some statistics
or interact with a plugin instance, for example nearline storage,
after it is created.

Modification:

Add possibility to dynamically add and remove prefixed commands
if a plugin implements CellDynamicCommandProvider.

Result:

If a plugin implements CellDynamicCommandProvider,
dCache scans for Command-Annotations and adds them to the
pool with the instance name as a prefix. If the hsm instance
is removed, the commands will be removed, too.

target: master
Requires-book: yes
Requires-note: yes
Acked-by: Paul Millar
GitHub: #6249
Patch: https://rb.dcache.org/r/13445/
  • Loading branch information
svemeyer committed Mar 2, 2022
1 parent 2f9b5d3 commit a88bade
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 14 deletions.
Expand Up @@ -195,6 +195,14 @@ public Serializable command(Args args) throws CommandException {
return _commandInterpreter.command(args);
}

/**
* Get {@link CommandInterpreter} used by this CellAdapter.
* @return
*/
protected CommandInterpreter getCommandInterpreter() {
return _commandInterpreter;
}

/**
* Called to execute admin shell commands. Subclasses may override this to intercept command
* execution. Implementations should call CommandExecutor#execute to execute the command.
Expand Down
@@ -0,0 +1,17 @@
package dmg.cells.nucleus;

import dmg.util.CommandInterpreter;

/**
* An implementation of this interface can dynamically provide new admin commands (typically by
* loading external classes).
*/

public interface CellDynamicCommandProvider {
/**
* Inject command interpreter into which commands can be dynamically added.
* @param commandInterpreter command interpreter to use.
*/

void setCommandInterpreter(CommandInterpreter commandInterpreter);
}
@@ -1,7 +1,6 @@
package org.dcache.util.cli;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSortedMap;
import dmg.util.CommandException;
import dmg.util.CommandExitException;
import dmg.util.CommandPanicException;
Expand All @@ -12,10 +11,9 @@
import dmg.util.command.HelpFormat;
import dmg.util.command.Option;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.Callable;

import org.dcache.util.Args;

/**
Expand Down Expand Up @@ -55,16 +53,72 @@ public final synchronized void addCommandListener(Object commandListener) {
_commandListeners.add(commandListener);
}

public final synchronized void addPrefixedCommandListener(Object commandListener, String prefix) {
for (CommandScanner scanner : _scanners) {
addPrefixedCommands(scanner.scan(commandListener), List.of(prefix.split(" ")));
}

_commandListeners.add(commandListener);
}

public final synchronized void removePrefixedCommandListener(Object commandListener, String prefix) {
for (CommandScanner scanner : _scanners) {
removePrefixedCommands(scanner.scan(commandListener), prefix);
}

_commandListeners.remove(commandListener);
}

private void addCommands(Map<List<String>, ? extends CommandExecutor> commands) {
addPrefixedCommands(commands, Collections.emptyList());
}

private void addPrefixedCommands(Map<List<String>, ? extends CommandExecutor> commands, List<String> prefix) {
List<String> commandElements = new ArrayList<>();
for (Map.Entry<List<String>, ? extends CommandExecutor> entry : commands.entrySet()) {
CommandEntry currentEntry = _rootEntry.getOrCreate(entry.getKey());
commandElements.addAll(prefix);
commandElements.addAll(entry.getKey());
CommandEntry currentEntry = _rootEntry.getOrCreate(commandElements);
if (currentEntry.hasCommand()) {
throw new IllegalArgumentException(
"Conflicting implementations of shell command '" +
Joiner.on(" ").join(entry.getKey()) + "': " +
currentEntry.getCommand() + " and " + entry.getValue());
}
currentEntry.setCommand(entry.getValue());
commandElements.clear();
}
}

private void removeCommand(List<String> command, CommandEntry commandEntry) {
if (command.size() == 0) {
return;
}
String firstCommandPart = command.get(0);
CommandEntry nestedEntry = commandEntry.get(firstCommandPart);
if (nestedEntry == null) {
return;
}

if (command.size() > 1) {
removeCommand(command.subList(1, command.size()), nestedEntry);
} else {
nestedEntry.removeExecutor();
}

if (nestedEntry.isEmpty()) {
commandEntry.remove(firstCommandPart);
}
}

private void removePrefixedCommands(Map<List<String>, ? extends CommandExecutor> commands, String prefix) {
List<String> commandElements = new ArrayList<>();
List<String> prefixElements = List.of(prefix.split(" "));
for (Map.Entry<List<String>, ? extends CommandExecutor> command : commands.entrySet()) {
commandElements.clear();
commandElements.addAll(prefixElements);
commandElements.addAll(command.getKey());
removeCommand(commandElements, _rootEntry);
}
}

Expand Down Expand Up @@ -139,8 +193,7 @@ protected Serializable doExecute(CommandEntry entry, Args args,
*/
protected static class CommandEntry {

private ImmutableSortedMap<String, CommandEntry> _suffixes =
ImmutableSortedMap.of();
private SortedMap<String, CommandEntry> _suffixes = new TreeMap<>();

private final String _name;
private CommandExecutor _commandExecutor;
Expand All @@ -154,10 +207,11 @@ public String getName() {
}

public void put(String str, CommandEntry e) {
_suffixes = ImmutableSortedMap.<String, CommandEntry>naturalOrder()
.putAll(_suffixes)
.put(str, e)
.build();
_suffixes.put(str, e);
}

public void remove(String key) {
_suffixes.remove(key);
}

public CommandEntry get(String str) {
Expand Down Expand Up @@ -231,6 +285,14 @@ public String toString() {
}
return sb.toString();
}

public boolean isEmpty() {
return _suffixes.isEmpty() && _commandExecutor == null;
}

public void removeExecutor() {
this._commandExecutor = null;
}
}

public String getHelp(HelpFormat format, String... command) {
Expand Down
Expand Up @@ -16,6 +16,7 @@
import diskCacheV111.util.CacheException;
import dmg.cells.nucleus.CellArgsAware;
import dmg.cells.nucleus.CellCommandListener;
import dmg.cells.nucleus.CellDynamicCommandProvider;
import dmg.cells.nucleus.CellEventListener;
import dmg.cells.nucleus.CellIdentityAware;
import dmg.cells.nucleus.CellInfo;
Expand Down Expand Up @@ -1019,6 +1020,10 @@ public Object postProcessAfterInitialization(Object bean,
addCellEventListener((CellEventListener) bean);
}

if (bean instanceof CellDynamicCommandProvider) {
((CellDynamicCommandProvider) bean).setCommandInterpreter(this.getCommandInterpreter());
}

return bean;
}

Expand Down
16 changes: 13 additions & 3 deletions modules/dcache/src/main/java/org/dcache/pool/nearline/HsmSet.java
Expand Up @@ -17,17 +17,17 @@
*/
package org.dcache.pool.nearline;

import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static java.util.Collections.unmodifiableSet;

import com.google.common.collect.Maps;
import diskCacheV111.util.FileNotInCacheException;
import diskCacheV111.vehicles.StorageInfo;
import dmg.cells.nucleus.CellCommandListener;
import dmg.cells.nucleus.CellDynamicCommandProvider;
import dmg.cells.nucleus.CellLifeCycleAware;
import dmg.cells.nucleus.CellSetupProvider;
import dmg.util.CommandException;
import dmg.util.CommandInterpreter;
import dmg.util.Formats;
import dmg.util.command.Argument;
import dmg.util.command.Command;
Expand Down Expand Up @@ -56,6 +56,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import static java.util.Objects.requireNonNull;
import static org.dcache.util.Exceptions.messageOrClassName;

/**
Expand All @@ -73,7 +74,8 @@
* serve as an instance name.
*/
public class HsmSet
implements CellCommandListener, CellSetupProvider, CellLifeCycleAware {
implements CellCommandListener, CellSetupProvider, CellLifeCycleAware,
CellDynamicCommandProvider {

private static final Logger LOGGER = LoggerFactory.getLogger(HsmSet.class);
private static final ServiceLoader<NearlineStorageProvider> PROVIDERS =
Expand All @@ -86,6 +88,7 @@ public class HsmSet
= Maps.newConcurrentMap();
private boolean _isReadingSetup;
private boolean _isStarted;
private CommandInterpreter _commandInterpreter;

@Value("${pool.name}")
private String _poolName;
Expand Down Expand Up @@ -268,9 +271,11 @@ public NearlineStorage getNearlineStorage() {
*/
public void start() throws IOException {
_nearlineStorage.start();
_commandInterpreter.addPrefixedCommandListener(_nearlineStorage, _instance);
}

public void shutdown() {
_commandInterpreter.removePrefixedCommandListener(_nearlineStorage, _instance);
_nearlineStorage.shutdown();
}
}
Expand Down Expand Up @@ -655,4 +660,9 @@ private void printInfos(StringBuilder sb, String instance) {
}
}
}

@Override
public void setCommandInterpreter(CommandInterpreter commandInterpreter) {
_commandInterpreter = requireNonNull(commandInterpreter);
}
}

0 comments on commit a88bade

Please sign in to comment.