package diskCacheV111.services;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Transaction;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.PnfsId;
import diskCacheV111.vehicles.DoorTransferFinishedMessage;
import diskCacheV111.vehicles.IpProtocolInfo;
import diskCacheV111.vehicles.transferManager.CancelTransferMessage;
import diskCacheV111.vehicles.transferManager.TransferManagerMessage;
import diskCacheV111.vehicles.transferManager.TransferStatusQueryMessage;
import dmg.cells.nucleus.AbstractCellComponent;
import dmg.cells.nucleus.CellAddressCore;
import dmg.cells.nucleus.CellCommandListener;
import dmg.cells.nucleus.CellInfoProvider;
import dmg.cells.nucleus.CellMessage;
import dmg.cells.nucleus.CellMessageReceiver;
import dmg.cells.nucleus.SerializationException;
import dmg.util.TimebasedCounter;
import org.dcache.cells.CellStub;
import org.dcache.poolmanager.PoolManagerStub;
import org.dcache.util.Args;
import org.dcache.util.CDCExecutorServiceDecorator;
import static java.util.stream.Collectors.joining;
/**
* Base class for services that transfer files on behalf of SRM. Used to
* implement server-side srmCopy.
*/
public abstract class TransferManager extends AbstractCellComponent
implements CellCommandListener,
CellMessageReceiver, CellInfoProvider
{
private static final Logger log = LoggerFactory.getLogger(TransferManager.class);
private final Map<Long, TransferManagerHandler> _activeTransfers =
new ConcurrentHashMap<>();
private int _maxTransfers;
private int _numTransfers;
private long _moverTimeout;
private TimeUnit _moverTimeoutUnit;
protected static long nextMessageID;
private String _tLogRoot;
private CellStub _pnfsManager;
private PoolManagerStub _poolManager;
private CellStub _poolStub;
private CellStub _billingStub;
private boolean _overwrite;
private int _maxNumberOfDeleteRetries;
// this is the timer which will timeout the
// transfer requests
private final Timer _moverTimeoutTimer = new Timer("Mover timeout timer", true);
private final Map<Long, TimerTask> _moverTimeoutTimerTasks =
new ConcurrentHashMap<>();
private String _ioQueueName; // multi io queue option
private TimebasedCounter idGenerator = new TimebasedCounter();
public final Set<PnfsId> justRequestedIDs = new HashSet<>();
private final ExecutorService executor =
new CDCExecutorServiceDecorator<>(Executors.newCachedThreadPool());
private PersistenceManagerFactory _pmf;
public void cleanUp()
{
executor.shutdown();
}
@Override
public void getInfo(PrintWriter pw)
{
pw.printf("DB logging : %b\n", doDbLogging());
pw.printf("Transfer ID generated : %s\n", idGenerator == null ? "locally" : "from DB");
pw.printf("Next Transfer ID : %d\n", nextMessageID);
pw.printf("Active transfers : %d\n", _numTransfers);
pw.printf("Max active transfers : %d\n", getMaxTransfers());
pw.printf("Pool manager : %s\n", _poolManager);
pw.printf("io-queue : %s\n", _ioQueueName);
pw.printf("Max delete retries : %d\n", _maxNumberOfDeleteRetries);
}
public String ac_set_maxNumberOfDeleteRetries_$_1(Args args)
{
_maxNumberOfDeleteRetries = Integer.parseInt(args.argv(0));
return "setting maxNumberOfDeleteRetries " + _maxNumberOfDeleteRetries;
}
public static final String hh_set_tlog = "<direcory for ftp logs or \"null\" for none>";
public String ac_set_tlog_$_1(Args args)
{
_tLogRoot = args.argv(0);
if (_tLogRoot.equals("null")) {
_tLogRoot = null;
return "remote ftp transaction logging is off";
}
return "remote ftp transactions will be logged to " + _tLogRoot;
}
public static final String hh_set_max_transfers_external = "<#max transfers>";
public String ac_set_max_transfers_external_$_1(Args args)
{
int max = Integer.parseInt(args.argv(0));
if (max <= 0) {
return "Error, max transfers number should be greater then 0 ";
}
setMaxTransfers(max);
return "set maximum number of active transfers to " + max;
}
public static final String hh_ls_external = "[-l] [<#transferId>]";
public String ac_ls_external_$_0_1(Args args)
{
boolean long_format = args.hasOption("l");
if (args.argc() > 0) {
long id = Long.parseLong(args.argv(0));
TransferManagerHandler handler = _activeTransfers.get(id);
if (handler == null) {
return "ID not found : " + id;
}
return handler.toString(long_format);
}
if (_activeTransfers.isEmpty()) {
return "No active transfers.";
}
return _activeTransfers.values().stream()
.map(h -> h.toString(long_format))
.collect(joining("\n", "", "\n"));
}
public static final String hh_kill = " id";
public String ac_kill_$_1(Args args)
{
long id = Long.parseLong(args.argv(0));
TransferManagerHandler handler = _activeTransfers.get(id);
if (handler == null) {
return "transfer not found: " + id;
}
handler.cancel("triggered by admin");
return "request sent to kill the mover on pool\n";
}
public static final String hh_killall = " [-p pool] pattern [pool] \n"
+ " for example killall .* ketchup will kill all transfers with movers on the ketchup pool";
public String ac_killall_$_1_2(Args args)
{
try {
Pattern p = Pattern.compile(args.argv(0));
String pool = null;
if (args.argc() > 1) {
pool = args.argv(1);
}
List<TransferManagerHandler> handlersToKill =
new ArrayList<>();
for (Map.Entry<Long, TransferManagerHandler> e : _activeTransfers.entrySet()) {
long id = e.getKey();
TransferManagerHandler handler = e.getValue();
Matcher m = p.matcher(String.valueOf(id));
if (m.matches()) {
log.debug("pattern: \"{}\" matches id=\"{}\"", args.argv(0), id);
if (pool != null && pool.equals(handler.getPool())) {
handlersToKill.add(handler);
} else if (pool == null) {
handlersToKill.add(handler);
}
} else {
log.debug("pattern: \"{}\" does not match id=\"{}\"", args.argv(0), id);
}
}
if (handlersToKill.isEmpty()) {
return "no active transfers match the pattern and the pool";
}
StringBuilder sb = new StringBuilder("Killing these transfers: \n");
for (TransferManagerHandler handler : handlersToKill) {
handler.cancel("triggered by admin");
sb.append(handler.toString(true)).append('\n');
}
return sb.toString();
} catch (Exception e) {
log.error(e.toString());
return e.toString();
}
}
public void messageArrived(DoorTransferFinishedMessage message)
{
long id = message.getId();
TransferManagerHandler h = getHandler(id);
if (h != null) {
h.poolDoorMessageArrived(message);
}
}
public CancelTransferMessage messageArrived(CancelTransferMessage message)
{
long id = message.getId();
TransferManagerHandler h = getHandler(id);
if (h != null) {
String explanation = message.getExplanation();
h.cancel(explanation != null ? explanation : "at the request of door");
} else {
// FIXME: shouldn't this throw an exception?
log.error("cannot find handler with id={} for CancelTransferMessage", id);
}
return message;
}
public TransferManagerMessage messageArrived(CellMessage envelope, TransferManagerMessage message)
throws CacheException
{
if (!newTransfer()) {
throw new CacheException(TransferManagerMessage.TOO_MANY_TRANSFERS, "too many transfers!");
}
new TransferManagerHandler(this, message, envelope.getSourcePath().revert(), executor).handle();
return message;
}
// TransferStatusQueryMessage is a subclass of
// TransferManagerMessage, so the code relies on the
// messageArrived dispatch invoking the method with the most
// specific signature. The unused "CellMessage envelope" argument
// of this method is required because two-argument methods are
// called preferentially.
public Object messageArrived(CellMessage envelope, TransferStatusQueryMessage message)
{
TransferManagerHandler handler = getHandler(message.getId());
if (handler == null) {
message.setState(TransferManagerHandler.UNKNOWN_ID);
return message;
}
return handler.appendInfo(message);
}
public int getMaxTransfers()
{
return _maxTransfers;
}
public void setMaxTransfers(int max_transfers)
{
_maxTransfers = max_transfers;
}
private synchronized boolean newTransfer()
{
log.debug("newTransfer() num_transfers = {} max_transfers={}",
_numTransfers, _maxTransfers);
if (_numTransfers == _maxTransfers) {
log.debug("newTransfer() returns false");
return false;
}
log.debug("newTransfer() INCREMENT and return true");
_numTransfers++;
return true;
}
synchronized void finishTransfer()
{
log.debug("finishTransfer() num_transfers = {} DECREMENT", _numTransfers);
_numTransfers--;
}
public synchronized long getNextMessageID()
{
if (idGenerator != null) {
try {
nextMessageID = idGenerator.next();
} catch (Exception e) {
log.error("Having trouble getting getNextMessageID from DB");
log.error(e.toString());
log.error("will nullify requestsPropertyStorage");
idGenerator = null;
getNextMessageID();
}
} else {
if (nextMessageID == Long.MAX_VALUE) {
nextMessageID = 0;
return Long.MAX_VALUE;
}
return nextMessageID++;
}
return nextMessageID;
}
protected abstract IpProtocolInfo getProtocolInfo(TransferManagerMessage transferRequest);
protected TransferManagerHandler getHandler(long handlerId)
{
return _activeTransfers.get(handlerId);
}
public void startTimer(final long id)
{
TimerTask task = new TimerTask()
{
@Override
public void run()
{
log.error("timer for handler " + id + " has expired, killing");
Object o = _moverTimeoutTimerTasks.remove(id);
if (o == null) {
log.error("TimerTask.run(): timer task for handler Id={} not found in moverTimoutTimerTasks hashtable", id);
return;
}
TransferManagerHandler handler = getHandler(id);
if (handler == null) {
log.error("TimerTask.run(): timer task for handler Id={} could not find handler !!!", id);
return;
}
handler.timeout();
}
};
_moverTimeoutTimerTasks.put(id, task);
// this is very approximate
// but we do not need hard real time
_moverTimeoutTimer.schedule(task, _moverTimeoutUnit.toMillis(_moverTimeout));
}
public void stopTimer(long id)
{
TimerTask tt = _moverTimeoutTimerTasks.remove(id);
if (tt == null) {
log.error("stopTimer(): timer not found for Id={}", id);
return;
}
log.debug("canceling the mover timer for handler id {}", id);
tt.cancel();
}
public void addActiveTransfer(long id, TransferManagerHandler handler) {
_activeTransfers.put(id, handler);
if (doDbLogging()) {
PersistenceManager pm = _pmf.getPersistenceManager();
try {
Transaction tx = pm.currentTransaction();
try {
tx.begin();
pm.makePersistent(handler);
tx.commit();
log.debug("Recording new handler into database.");
} catch (Exception e) {
log.error(e.toString());
} finally {
rollbackIfActive(tx);
}
} finally {
pm.close();
}
}
}
public void removeActiveTransfer(long id) {
TransferManagerHandler handler = _activeTransfers.remove(id);
if (doDbLogging()) {
PersistenceManager pm = _pmf.getPersistenceManager();
try {
Transaction tx = pm.currentTransaction();
TransferManagerHandlerBackup handlerBackup
= new TransferManagerHandlerBackup(handler);
try {
tx.begin();
pm.makePersistent(handler);
pm.deletePersistent(handler);
pm.makePersistent(handlerBackup);
tx.commit();
log.debug("handler removed from db");
} catch (Exception e) {
log.error(e.toString());
} finally {
rollbackIfActive(tx);
}
} finally {
pm.close();
}
}
}
public CellStub getPoolStub()
{
return _poolStub;
}
public String getLogRootName()
{
return _tLogRoot;
}
public boolean isOverwrite()
{
return _overwrite;
}
public PoolManagerStub getPoolManagerStub()
{
return _poolManager;
}
public CellStub getPnfsManagerStub()
{
return _pnfsManager;
}
public CellStub getBillingStub()
{
return _billingStub;
}
public String getIoQueueName()
{
return _ioQueueName;
}
public static void rollbackIfActive(Transaction tx)
{
if (tx != null && tx.isActive()) {
tx.rollback();
}
}
public boolean doDbLogging()
{
return _pmf != null;
}
public int getMaxNumberOfDeleteRetries()
{
return _maxNumberOfDeleteRetries;
}
public void persist(Object o) {
if (doDbLogging()) {
PersistenceManager pm = _pmf.getPersistenceManager();
try {
Transaction tx = pm.currentTransaction();
try {
tx.begin();
pm.makePersistent(o);
tx.commit();
log.debug("[{}]: Recording new state of handler into database.",
o);
} catch (Exception e) {
log.error("[{}]: failed to persist object: {}.",
o, e.getMessage());
} finally {
rollbackIfActive(tx);
}
} finally {
pm.close();
}
}
}
@Override
public CellAddressCore getCellAddress()
{
return super.getCellAddress();
}
public void sendMessage(CellMessage envelope) throws SerializationException
{
super.sendMessage(envelope);
}
public void setBilling(CellStub billingStub) {
_billingStub = billingStub;
}
public void setPoolManager(PoolManagerStub poolManager) {
_poolManager = poolManager;
}
public void setPnfsManager(CellStub pnfsManager) {
_pnfsManager = pnfsManager;
}
public void setPool(CellStub pool) {
_poolStub = pool;
}
public void setMoverTimeout(long moverTimeout) {
_moverTimeout = moverTimeout;
}
public void setMoverTimeoutUnit(TimeUnit moverTimeoutUnit) {
_moverTimeoutUnit = moverTimeoutUnit;
}
public void setIoQueueName(String ioQueueName) {
_ioQueueName = ioQueueName;
}
public void setMaxNumberOfDeleteRetries(int maxNumberOfDeleteRetries) {
_maxNumberOfDeleteRetries = maxNumberOfDeleteRetries;
}
public void setOverwrite(boolean overwrite) {
_overwrite = overwrite;
}
public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
_pmf = pmf;
}
public void setTLogRoot(String tLogRoot) {
_tLogRoot = tLogRoot;
}
}