/* dCache - http://www.dcache.org/ * * Copyright (C) 2014 Deutsches Elektronen-Synchrotron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.dcache.pool.nearline; import com.google.common.base.Throwables; import com.google.common.collect.Maps; import javax.annotation.PreDestroy; import java.io.PrintWriter; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import diskCacheV111.util.FileNotInCacheException; import diskCacheV111.vehicles.StorageInfo; import dmg.cells.nucleus.CellCommandListener; import dmg.cells.nucleus.CellLifeCycleAware; import dmg.cells.nucleus.CellSetupProvider; import dmg.util.Formats; import dmg.util.command.Argument; import dmg.util.command.Command; import dmg.util.command.CommandLine; import org.dcache.pool.nearline.spi.NearlineStorage; import org.dcache.pool.nearline.spi.NearlineStorageProvider; import org.dcache.util.Args; import org.dcache.util.ColumnWriter; import org.dcache.vehicles.FileAttributes; import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; import static java.util.Collections.unmodifiableSet; /** * An HsmSet encapsulates information about attached HSMs. The HsmSet * also acts as a cell command interpreter, allowing the user to * add, remove or alter the information. * * Each HSM has a case sensitive instance name which uniquely * identifies this particular tape system throughout dCache. Notice * that multiple pools can be attached to the same HSM. In that case * the instance name must be the same at each pool. * * An HSM also has a type, e.g. OSM or Enstore. The type is not case * sensitive. Traditionally, the type was called the HSM name. It is * important not to confuse the type with the instance name. * * Earlier versions of dCache did not specify an instance name. For * compatibility, the type may serve as an instance name. */ public class HsmSet implements CellCommandListener, CellSetupProvider, CellLifeCycleAware { private static final ServiceLoader<NearlineStorageProvider> PROVIDERS = ServiceLoader.load(NearlineStorageProvider.class); private static final String DEFAULT_PROVIDER = "script"; private final ConcurrentMap<String, HsmInfo> _newConfig = Maps.newConcurrentMap(); private final ConcurrentMap<String, HsmInfo> _hsm = Maps.newConcurrentMap(); private boolean _isReadingSetup; private NearlineStorageProvider findProvider(String name) { for (NearlineStorageProvider provider : PROVIDERS) { if (provider.getName().equals(name)) { return provider; } } throw new IllegalArgumentException("No such nearline storage provider: " + name); } /** * Information about a particular HSM instance. */ public class HsmInfo { private final String _type; private final String _instance; private final Map<String,String> _currentAttributes = new LinkedHashMap<>(); private final Map<String,String> _newAttributes = new LinkedHashMap<>(); private final NearlineStorageProvider _provider; private final NearlineStorage _nearlineStorage; /** * Constructs an HsmInfo object. * * @param instance A unique instance name. * @param type The HSM type, e.g. OSM or enstore. */ public HsmInfo(String instance, String type, String provider) { _instance = instance; _type = type.toLowerCase(); _provider = findProvider(provider); _nearlineStorage = _provider.createNearlineStorage(_type, _instance); } /** * Returns the instance name. */ public String getInstance() { return _instance; } /** * Returns the HSM type. */ public String getType() { return _type; } /** * Returns the HSM provider name. */ public String getProvider() { return _provider.getName(); } /** * Returns the value of an attribute. Returns null if the * attribute has not been defined. * * @param attribute An attribute name */ public synchronized String getAttribute(String attribute) { return _currentAttributes.get(attribute); } /** * Removes an attribute. * * @param attribute An attribute name */ public synchronized void unsetAttribute(String attribute) { _newAttributes.remove(attribute); } /** * Sets an attribute to a value. * * @param attribute An attribute name * @param value A value string */ public synchronized void setAttribute(String attribute, String value) { _newAttributes.put(attribute, value); } /** * Returns the set of attributes. */ public synchronized Iterable<Map.Entry<String, String>> attributes() { return new ArrayList<>(_currentAttributes.entrySet()); } /** * Applies the current configuration to the nearline storage. Roles back * to the previous configuration if the new configuration is rejected. */ public synchronized void refresh() { try { _nearlineStorage.configure(_newAttributes); _currentAttributes.clear(); _currentAttributes.putAll(_newAttributes); } catch (Exception e) { _newAttributes.clear(); _newAttributes.putAll(_currentAttributes); throw e; } } /** * Scans an argument set for options and applies those as * attributes to an HsmInfo object. */ public synchronized void scanOptions(Args args) { for (Map.Entry<String,String> e: args.options().entries()) { String optName = e.getKey(); String optValue = e.getValue(); setAttribute(optName, optValue == null ? "" : optValue); } if (!_isReadingSetup) { refresh(); } } /** * Scans an argument set for options and removes and unsets those * attributes in the given HsmInfo object. */ public synchronized void scanOptionsUnset(Args args) { for (String optName: args.options().keySet()) { unsetAttribute(optName); } if (!_isReadingSetup) { refresh(); } } public NearlineStorage getNearlineStorage() { return _nearlineStorage; } public void shutdown() { _nearlineStorage.shutdown(); } } /** * Returns an unmodifiable view of the HSM instance names. * * Notice that the set is not Serializable. */ public Set<String> getHsmInstances() { return unmodifiableSet(_hsm.keySet()); } public NearlineStorage getNearlineStorageByName(String name) { HsmInfo info = _hsm.get(name); return (info != null) ? info.getNearlineStorage() : null; } public NearlineStorage getNearlineStorageByType(String type) { Collection<HsmInfo> infos = _hsm.values(); return infos.stream() .filter(hsm -> hsm.getType().equals(type)) .map(HsmInfo::getNearlineStorage) .findFirst().orElse(null); } /** * Returns the name of an HSM accessible for this pool and which * contains the given file. */ public String getInstanceName(FileAttributes fileAttributes) throws FileNotInCacheException { StorageInfo file = fileAttributes.getStorageInfo(); if (file.locations().isEmpty() && _hsm.containsKey(fileAttributes.getHsm())) { // This is for backwards compatibility. return fileAttributes.getHsm(); } for (URI location : file.locations()) { if (_hsm.containsKey(location.getAuthority())) { return location.getAuthority(); } } throw new FileNotInCacheException("Pool does not have access to any of the HSM locations " + file.locations()); } @AffectsSetup @Command(name = "hsm create", hint = "create nearline storage", description = "Creates a nearline storage. A nearline storage is dCache's interface to external " + "storage providers such as tape systems. Files are copied to (flush) and from (stage) " + "nearline storages.") public class CreateCommand implements Callable<String> { @Argument(index = 0, usage = "The nearline storage type is usually determined by the storage-info-extractor " + "used by the pnfs manager. This matches the string after the @ in a storage unit and " + "is typically either 'osm' or 'enstore'.") String type; @Argument(index = 1, required = false, usage = "Uniquely identifies the nearline storage. Defaults to the HSM type, " + "but should be set if interfaces with multiple nearline storages. If " + "multiple pools interact with the same nearline storage, these should " + "use the same instance name.") String instance; @Argument(index = 2, required = false, usage = "Nearline storage providers are pluggable drivers for the nearline storage interface. " + "The provider handles flushing to and staging from the nearline storage as well " + "deleting files.") String provider = DEFAULT_PROVIDER; @CommandLine(allowAnyOption = true, usage = "Provider specific options.") Args options; @Override public String call() throws Exception { String instance = (this.instance == null) ? type : this.instance; if (_isReadingSetup) { if (_newConfig.containsKey(instance)) { throw new IllegalArgumentException("Nearline storage already exists: " + instance); } HsmInfo info = _hsm.get(instance); if (info == null) { info = new HsmInfo(instance, type, provider); } info.scanOptions(options); _newConfig.put(instance, info); } else { if (_hsm.containsKey(instance)) { throw new IllegalArgumentException("Nearline storage already exists: " + instance); } HsmInfo info = new HsmInfo(instance, type, provider); info.scanOptions(options); _hsm.put(instance, info); } return ""; } } @AffectsSetup @Command(name = "hsm set", hint = "set nearline storage options", description = "Sets options of a nearline storage. See the nearline storage provider " + "documentation for information on supported options.") public class SetCommand implements Callable<String> { @Argument(usage = "Nearline storage instance name.") String instance; @CommandLine(allowAnyOption = true, usage = "Provider specific options.") Args options; @Override public String call() throws Exception { HsmInfo info = _isReadingSetup ? _newConfig.get(instance) : _hsm.get(instance); if (info == null) { throw new IllegalArgumentException(instance + ": No such nearline storage. You may need to run 'hsm create'."); } info.scanOptions(options); return ""; } } @AffectsSetup @Command(name = "hsm unset", hint = "unset nearline storage options", description = "Unsets options of a nearline storage.") public class UnsetCommand implements Callable<String> { @Argument(usage = "Nearline storage instance name.") String instance; @CommandLine(allowAnyOption = true, valueSpec = "-KEY ...", usage = "Provider specific options.") Args options; @Override public String call() throws Exception { HsmInfo info = _isReadingSetup ? _newConfig.get(instance) : _hsm.get(instance); if (info == null) { throw new IllegalArgumentException(instance + ": No such nearline storage. You may need to run 'hsm create'."); } info.scanOptionsUnset(options); return ""; } } @Command(name = "hsm ls", hint = "list nearline storages", description = "Lists all nearline storages defined on this pool.") public class LsCommand implements Callable<String> { @Argument(usage = "Limit output to these instances.", required = false) String[] instances; @Override public String call() throws Exception { StringBuilder sb = new StringBuilder(); if (instances != null && instances.length > 0) { for (String instance : instances) { printInfos(sb, instance); } } else { for (String name : _hsm.keySet()) { printInfos(sb, name); } } return sb.toString(); } } @AffectsSetup @Command(name = "hsm remove", hint = "remove nearlinestorage definition", description = "Deletes the nearline storage definition from this pool.") public class RemoveCommand implements Callable<String> { @Argument(usage = "Nearline storage instance name.") String instance; @Override public String call() throws Exception { HsmInfo info = (_isReadingSetup ? _newConfig : _hsm).remove(instance); if (info != null) { info.shutdown(); } return ""; } } @Command(name = "hsm show providers", hint = "list available providers", description = "Nearline storage providers are pluggable. Third party plugins may " + "use the nearline storage SPI to implement custom nearline storage " + "drivers.") public class ShowProvidersCommand implements Callable<String> { @Override public String call() throws Exception { ColumnWriter writer = new ColumnWriter(); writer.header("PROVIDER").left("provider").space(); writer.header("DESCRIPTION").left("description"); for (NearlineStorageProvider provider : PROVIDERS) { writer.row() .value("provider", provider.getName()) .value("description", provider.getDescription()); } return writer.toString(); } } @Override public void printSetup(PrintWriter pw) { if (!_hsm.isEmpty()) { pw.println("#\n# Nearline storage\n#"); for (HsmInfo info : _hsm.values()) { pw.print("hsm create "); pw.print(info.getType()); pw.print(" "); pw.print(info.getInstance()); pw.print(" "); pw.print(info.getProvider()); for (Map.Entry<String, String> entry : info.attributes()) { pw.print(" -"); pw.print(entry.getKey()); if (entry.getValue() != null) { pw.print("="); pw.print(entry.getValue()); } } pw.println(); } } } @Override public synchronized void beforeSetup() { _isReadingSetup = true; } @Override public synchronized void afterSetup() { _isReadingSetup = false; /* Remove the stores that are not in the new configuration. */ Iterator<HsmInfo> iterator = Maps.filterKeys(_hsm, not(in(_newConfig.keySet()))).values().iterator(); while (iterator.hasNext()) { iterator.next().shutdown(); iterator.remove(); } /* Apply configuration changes */ for (HsmInfo hsm : _newConfig.values()) { hsm.refresh(); } _hsm.putAll(_newConfig); _newConfig.clear(); } @PreDestroy public void shutdown() { for (HsmInfo info : _hsm.values()) { info.shutdown(); } } private void printInfos(StringBuilder sb, String instance) { assert instance != null; HsmInfo info = _hsm.get(instance); if (info == null) { sb.append(instance).append(" not found\n"); } else { sb.append(instance).append("(").append(info.getType()) .append("):").append(info.getProvider()).append('\n'); for (Map.Entry<String,String> entry : info.attributes()) { String attrName = entry.getKey(); String attrValue = entry.getValue(); sb.append(" "). append(Formats.field(attrName,20,Formats.LEFT)). append(attrValue == null ? "<set>" : attrValue). append('\n'); } } } }