package org.dcache.poolmanager;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Formatter;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.Callable;
import dmg.cells.nucleus.CellCommandListener;
import dmg.cells.nucleus.CellLifeCycleAware;
import dmg.cells.nucleus.CellSetupProvider;
import dmg.util.CommandException;
import dmg.util.command.Argument;
import dmg.util.command.Command;
import dmg.util.command.CommandLine;
import dmg.util.command.Option;
import org.dcache.util.Args;
import static com.google.common.base.Predicates.*;
import static com.google.common.collect.Iterables.find;
import static com.google.common.collect.Maps.*;
/**
* Manages one or more pool manager partitions.
*
* A partition encapsulates configuration properties and pool
* selection logic. Pool manager maintains a default partition, but
* additional named partitions may be defined. Pool manager links
* identify a partition to use for transfers in that link.
*
* A set of shared properties is maintained by PartitionManager. These
* properties are inherited by all partitions, including the default
* partition.
*/
public class PartitionManager
implements Serializable, CellCommandListener, CellSetupProvider, CellLifeCycleAware
{
private static final long serialVersionUID = 3245564135066081407L;
private static final String DEFAULT = "default";
private static final ServiceLoader<PartitionFactory> _factories =
ServiceLoader.load(PartitionFactory.class);
/**
* Properties inherited by all partitions. Each partition may
* override any of the properties.
*/
private ImmutableMap<String,String> _inheritedProperties =
ImmutableMap.of();
/**
* When true the default partition's properties and the set of
* inherited properties are the same. Ie. the default partition
* cannot override any of the inherited properties.
*
* Once an explicit default partition is created, this field is set
* to false.
*
* This feature provides backwards compatibility with dCache
* 1.9.12: That version wrote PoolManager.conf files with the
* assumption that a property on the default partition would be
* inherited by other partitions. Once the PoolManager.conf file
* is regenerated with a newer version of dCache, an explicit
* default partition is created. Support for this feature can be
* removed in dCache 2.3.
*/
private volatile boolean _hasImplicitDefaultPartition;
/**
* Partition manager manages a set of named partitions.
*
* Like Partitions themself the map over partitions uses copy on
* write semantics. Any property update requires that the complete
* map and affected Partitions are recreated.
*/
private volatile ImmutableMap<String,Partition> _partitions =
ImmutableMap.of();
public PartitionManager()
{
clear();
}
public synchronized void clear()
{
_hasImplicitDefaultPartition = true;
_inheritedProperties = ImmutableMap.of();
_partitions =
ImmutableMap.of(DEFAULT, new WassPartition());
}
private PartitionFactory getFactory(String type)
throws NoSuchElementException
{
return find(_factories, compose(equalTo(type), PartitionFactory::getType));
}
public synchronized void setProperties(String name,
Map<String,String> properties)
throws IllegalArgumentException
{
if (name == null || (_hasImplicitDefaultPartition && name.equals(DEFAULT))) {
_inheritedProperties =
ImmutableMap.<String,String>builder()
.putAll(filterKeys(_inheritedProperties,
not(in(properties.keySet()))))
.putAll(filterValues(properties, notNull()))
.build();
_partitions =
ImmutableMap.copyOf(transformValues(_partitions,
partition -> partition.updateInherited(_inheritedProperties)));
} else if (_partitions.containsKey(name)) {
_partitions =
ImmutableMap.<String,Partition>builder()
.putAll(filterKeys(_partitions, not(equalTo(name))))
.put(name, _partitions.get(name).updateProperties(properties))
.build();
} else {
throw new IllegalArgumentException("No such partition: " + name);
}
}
public synchronized void
createPartition(PartitionFactory factory, String name)
throws IllegalArgumentException, NoSuchElementException
{
Partition partition = factory.createPartition(_inheritedProperties);
_partitions =
ImmutableMap.<String,Partition>builder()
.putAll(filterKeys(_partitions, not(equalTo(name))))
.put(name, partition)
.build();
if (name.equals(DEFAULT)) {
_hasImplicitDefaultPartition = false;
}
}
public synchronized void destroyPartition(String name)
throws IllegalArgumentException
{
if (name.equals(DEFAULT)) {
throw new IllegalArgumentException("Cannot destroy default partition");
}
if (!_partitions.containsKey(name)) {
throw new IllegalArgumentException("No such partition: " + name);
}
_partitions =
ImmutableMap.<String,Partition>builder()
.putAll(filterKeys(_partitions, not(equalTo(name))))
.build();
}
public Partition getDefaultPartition()
{
return _partitions.get(DEFAULT);
}
public Partition getPartition(String name)
{
if (name == null) {
return getDefaultPartition();
}
Partition partition = _partitions.get(name);
if (partition == null) {
return getDefaultPartition();
}
return partition;
}
@Command(name = "pmx get map",
hint = "get partition map",
description = "Internal command to query the internal representation of " +
"all partitions.")
public class PmxGetMapCommand implements Callable<ImmutableMap<String,Partition>>
{
@Override
public ImmutableMap<String,Partition> call()
{
return _partitions;
}
}
public ImmutableMap<String,Partition> getPartitions()
{
return _partitions;
}
@AffectsSetup
@Command(name = "pm set",
hint = "set partition parameters",
description = "Set one or more parameters on a partition. If no partition " +
"name is provided, then the set of inherited parameters is updated. " +
"These parameters are inherited by all partitions except for those " +
"that explicitly redefine the parameters.\n\n" +
"Setting a parameter to the value 'off' resets it back to inherited " +
"value or back to the default parameter value.")
public class PmSetCommand implements Callable<String>
{
@CommandLine(allowAnyOption = true,
usage = "Partition type specific options. Use 'pm ls -a' to discover available options.")
Args args;
@Argument(required = false,
usage = "The name of the partition to set its corresponding parameter and " +
"values.")
String partition;
@Option(name = "p2p-allowed", category = "Common options", values = {"yes", "no", "off"})
String p2pAllowed;
@Option(name = "p2p-oncost", category = "Common options", values = {"yes", "no", "off"})
String p2pOncost;
@Option(name = "p2p-fortransfer", category = "Common options", values = {"yes", "no", "off"})
String p2pFortransfer;
@Option(name = "stage-allowed", category = "Common options", values = {"yes", "no", "off"})
String stageAllowed;
@Option(name = "stage-oncost", category = "Common options", values = {"yes", "no", "off"})
String stageOncost;
@Override
public String call() throws IllegalArgumentException
{
setProperties(partition, scanProperties(args));
return "";
}
}
@Command(name = "pm types",
hint = "list available partition types",
description = "List partition types that can be used when creating new " +
"partitions. The pool selection algorithm, the configuration " +
"parameters, and the default values are defined by the partition " +
"type.\n\n" +
"Partition types are pluggable and new partition types can " +
"be added through a plugin mechanism.")
public class PmTypesCommand implements Callable<String>
{
@Override
public String call()
{
final String format = "%-16s %s\n";
Formatter s = new Formatter(new StringBuilder());
s.format(format, "Partition type", "Description");
s.format(format, "--------------", "-----------");
for (PartitionFactory factory: _factories) {
s.format(format, factory.getType(), factory.getDescription());
}
return s.toString();
}
}
@AffectsSetup
@Command(name = "pm create",
hint = "create a new partition",
description = "Creates a pool manager partition named <partition> of <type>. " +
"If no <type> is specified then 'wass' is used as a default type.\n\n" +
"A partition encapsulates configuration parameters and pool " +
"selection logic. Each pool manager link identifies a partition " +
"to use for transfers in that link.\n\n" +
"The partition type defines the pool selection logic and the " +
"available configuration parameters.\n\n" +
"A default partition named 'default' exists and is used for " +
"links that do not explicitly define which partition to use. " +
"Creating a partition reusing an existing name overwrites that " +
"partition. This allows the type of the default partition to be " +
"redefined. Any parameter values on the partition are lost.")
public class PmCreateCommand implements Callable<String>
{
@Argument(usage = "The name of the partition to be created.")
String partition;
@Option(name = "type",
usage = "The partition type to create. Use 'pm types' to see list of " +
"available partition types.")
String type = "wass";
@Override
public String call() throws CommandException
{
PartitionFactory factory;
try {
factory = getFactory(type);
} catch (NoSuchElementException e) {
throw new CommandException(1, "Unknown partition type \"" + type + "\"");
}
createPartition(factory, partition);
return "";
}
}
@AffectsSetup
@Command(name = "pm destroy",
hint = "remove a partition",
description = "Remove the specified pool manager partition. Links using the " +
"partition will fall back to the default partition. Any parameter " +
"values associated with the partition are lost.")
public class PmDestroyCommand implements Callable<String>
{
@Argument(usage = "The name of the partition to remove.")
String partition;
@Override
public String call() throws IllegalArgumentException
{
destroyPartition(partition);
return "";
}
}
@Command(name = "pm ls",
hint = "list all partitions",
description = "List information about the <partition>. If no <partition> is " +
"specified then information about all partitions is given.")
public class PmLsCommand implements Callable<String>
{
@Argument(required = false, metaVar = "",
usage = "The name of the partition to list the info.")
String name;
@Option(name = "a",
usage = "List all parameters. The default is not to list inherited " +
"and default parameters.")
boolean showAll;
@Option(name = "l", usage = "List parameters (default when <partition> is specified).")
boolean showMore;
@Override
public String call() throws IllegalArgumentException
{
StringBuilder sb = new StringBuilder();
if ( name != null ) {
Partition partition = _partitions.get(name);
if (partition == null) {
throw new IllegalArgumentException("Partition not found: " + name);
}
sb.append(name).append(" (").append(partition.getType()).append(")\n");
printProperties(sb, partition, showAll);
} else {
boolean showProperties = showMore || showAll;
if (showProperties) {
printInheritedProperties(sb);
}
for (Map.Entry<String,Partition> entry: _partitions.entrySet()) {
sb.append(entry.getKey())
.append(" (")
.append(entry.getValue().getType())
.append(")\n");
if (showProperties) {
printProperties(sb, entry.getValue(), showAll);
}
}
}
return sb.toString();
}
}
private void printInheritedProperties(StringBuilder sb)
{
Set<String> keys = _inheritedProperties.keySet();
sb.append("Inherited by all partitions\n");
for (String key: Ordering.<String>natural().sortedCopy(keys)) {
sb.append(" -").append(key)
.append("=").append(_inheritedProperties.get(key)).append("\n");
}
}
private void printProperties(StringBuilder sb, Partition partition,
boolean showAll)
{
Set<String> all = partition.getAllProperties().keySet();
Set<String> defined = partition.getProperties().keySet();
for (String key: Ordering.<String>natural().sortedCopy(all)) {
if (defined.contains(key) || showAll) {
sb.append(" -").append(key)
.append("=").append(partition.getProperty(key));
if (defined.contains(key)) {
sb.append("\n");
} else if (_inheritedProperties.containsKey(key)) {
sb.append(" [inherited]\n");
} else {
sb.append(" [default]\n");
}
}
}
}
private Map<String,String> scanProperties(Args args)
{
Map<String,String> map = newHashMap();
for (Map.Entry<String,String> entry: args.optionsAsMap().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (value.equals("off")) {
map.put(key, null);
} else {
map.put(key, value);
}
}
return map;
}
@Override
public void beforeSetup()
{
clear();
}
@Override
public synchronized void printSetup(PrintWriter pw)
{
dumpInfo(pw, "", _inheritedProperties);
for (Map.Entry<String,Partition> entry: _partitions.entrySet()) {
pw.format("pm create -type=%s %s\n",
entry.getValue().getType(), entry.getKey());
dumpInfo(pw, entry.getKey(), entry.getValue().getProperties());
}
}
private void dumpInfo(PrintWriter pw, String name,
Map<String,String> properties)
{
if (!properties.isEmpty()) {
pw.append("pm set");
if (!name.isEmpty()) {
pw.append(" ").append(name);
}
for (Map.Entry<String,String> entry: properties.entrySet()) {
pw.append(" -").append(entry.getKey()).append("=").append(entry.getValue());
}
pw.println();
}
}
private synchronized void writeObject(ObjectOutputStream stream) throws IOException
{
stream.defaultWriteObject();
}
}