package org.dcache.services.ssh2; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import org.apache.sshd.server.Command; import org.apache.sshd.server.Environment; import org.apache.sshd.server.ExitCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import diskCacheV111.admin.LegacyAdminShell; import diskCacheV111.services.space.LinkGroup; import diskCacheV111.services.space.Space; import diskCacheV111.services.space.message.GetLinkGroupsMessage; import diskCacheV111.services.space.message.GetSpaceTokensMessage; import diskCacheV111.util.CacheException; import diskCacheV111.util.TimeoutCacheException; import diskCacheV111.vehicles.IoJobInfo; import dmg.cells.applets.login.DomainObjectFrame; import dmg.cells.nucleus.CellEndpoint; import dmg.cells.nucleus.CellPath; import dmg.cells.nucleus.NoRouteToCellException; import dmg.cells.services.login.LoginBrokerInfo; import dmg.cells.services.login.LoginBrokerSource; import dmg.util.CommandException; import org.dcache.cells.CellStub; import org.dcache.util.Args; import org.dcache.util.TransferCollector; import org.dcache.util.TransferCollector.Transfer; public class PcellsCommand implements Command, Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(PcellsCommand.class); private final CellEndpoint _endpoint; private final CellStub _spaceManager; private final CellStub _poolManager; private final CellStub _pnfsManager; private final LoginBrokerSource _loginBrokerSource; private LegacyAdminShell _shell; private InputStream _in; private ExitCallback _exitCallback; private OutputStream _out; private Thread _adminShellThread; private final ExecutorService _executor = Executors.newCachedThreadPool(); private final ScheduledExecutorService _scheduler = Executors.newSingleThreadScheduledExecutor(); private volatile boolean _done = false; private final TransferCollector _collector; private volatile List<Transfer> _transfers = Collections.emptyList(); public PcellsCommand(CellEndpoint endpoint, CellStub spaceManager, CellStub poolManager, CellStub pnfsManager, LoginBrokerSource loginBrokerSource) { _endpoint = endpoint; _spaceManager = spaceManager; _poolManager = poolManager; _pnfsManager = pnfsManager; _loginBrokerSource = loginBrokerSource; _collector = new TransferCollector(new CellStub(endpoint), loginBrokerSource.doors()); } @Override public void setErrorStream(OutputStream err) { // we don't use the error stream } @Override public void setExitCallback(ExitCallback callback) { _exitCallback = callback; } @Override public void setInputStream(InputStream in) { _in = in; } @Override public void setOutputStream(OutputStream out) { _out = out; } @Override public void start(Environment env) throws IOException { String user = env.getEnv().get(Environment.ENV_USER); _shell = new LegacyAdminShell(user, _endpoint, ""); _adminShellThread = new Thread(this); _adminShellThread.start(); _scheduler.schedule(this::updateTransfers, 30, TimeUnit.SECONDS); } @Override public void destroy() { if (_adminShellThread != null) { _adminShellThread.interrupt(); } _executor.shutdownNow(); _scheduler.shutdownNow(); } @Override public void run() { try { final ObjectInputStream in = new ObjectInputStream(_in); final ObjectOutputStream out = new ObjectOutputStream(_out); out.flush(); Object obj; while (!_done && (obj = in.readObject()) != null) { if (!(obj instanceof DomainObjectFrame)) { LOGGER.error("Received unsupported request type: {}", obj.getClass()); continue; } final DomainObjectFrame frame = (DomainObjectFrame) obj; LOGGER.trace("Frame id {} received", frame.getId()); _executor.execute(() -> { Object result; try { if (frame.getDestination() == null) { result = _shell.executeCommand(frame.getPayload().toString()); } else { switch (frame.getDestination()) { case "PnfsManager": result = _shell.executeCommand(_pnfsManager.getDestinationPath(), frame.getPayload()); break; case "PoolManager": result = _shell.executeCommand(_poolManager.getDestinationPath(), frame.getPayload()); break; case "SrmSpaceManager": if (frame.getPayload().equals("ls -l")) { result = listSpaceReservations(); } else { result = _shell.executeCommand(_spaceManager.getDestinationPath(), frame.getPayload()); } break; case "TransferObserver": if (frame.getPayload().equals("ls iolist")) { result = listTransfers(_transfers); } else { result = _shell.executeCommand(new CellPath(frame.getDestination()), frame.getPayload()); } break; case "LoginBroker": String cmd = frame.getPayload().toString(); if (cmd.startsWith("ls") && cmd.contains("-binary")) { result = _loginBrokerSource.doors().stream().toArray(LoginBrokerInfo[]::new); } else { result = _shell.executeCommand(new CellPath(frame.getDestination()), frame.getPayload()); } break; default: result = _shell.executeCommand(new CellPath(frame.getDestination()), frame.getPayload()); break; } } } catch (CommandException e) { result = e; _done = true; try { in.close(); } catch (IOException ignored) { } } catch (NoRouteToCellException e) { result = e; } catch (TimeoutCacheException e) { result = null; } catch (Exception ae) { result = ae; } frame.setPayload(result); try { synchronized (out) { out.writeObject(frame); out.flush(); out.reset(); // prevents memory leaks... } } catch (IOException e) { LOGGER.error("Problem sending result : {}", e); } }); } } catch (IOException e) { LOGGER.warn("I/O failure in pcells connection: {}", e.toString()); } catch (ClassNotFoundException e) { LOGGER.error("Received unsupported request type: {}", e.getMessage()); } finally { _exitCallback.onExit(0); } } private String listSpaceReservations() throws CacheException, InterruptedException, NoRouteToCellException { /* Query information from space manager. */ Collection<Space> spaces = _spaceManager.sendAndWait(new GetSpaceTokensMessage()).getSpaceTokenSet(); Collection<LinkGroup> groups = _spaceManager.sendAndWait(new GetLinkGroupsMessage()).getLinkGroups(); /* Build pcells compatible list. */ StringBuilder out = new StringBuilder(); out.append("Reservations:\n"); for (Space space : spaces) { out.append(space).append('\n'); } out.append("total number of reservations: ").append(spaces.size()).append('\n'); out.append("total number of bytes reserved: ") .append(spaces.stream().mapToLong(Space::getSizeInBytes).sum()).append('\n'); out.append("\nLinkGroups:\n"); for (LinkGroup group : groups) { out.append(group).append('\n'); } out.append("total number of linkGroups: "). append(groups.size()).append('\n'); out.append("total number of bytes reservable: "). append(groups.stream().mapToLong(LinkGroup::getAvailableSpace).sum()).append('\n'); out.append("total number of bytes reserved : "). append(groups.stream().mapToLong(LinkGroup::getReservedSpace).sum()).append('\n'); return out.toString(); } private void updateTransfers() { Futures.addCallback(_collector.collectTransfers(), new FutureCallback<List<Transfer>>() { @Override public void onSuccess(List<Transfer> result) { result.sort(new TransferCollector.ByDoorAndSequence()); _transfers = result; _scheduler.schedule(PcellsCommand.this::updateTransfers, 2, TimeUnit.MINUTES); } @Override public void onFailure(Throwable t) { LOGGER.error("Possible bug detected. Please contact support@dcache.org.", t); _scheduler.schedule(PcellsCommand.this::updateTransfers, 30, TimeUnit.SECONDS); } }, _executor); } private String listTransfers(List<Transfer> transfers) { long now = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for (Transfer io : transfers) { List<String> args = new ArrayList<>(); args.add(io.door().getCellName()); args.add(io.door().getDomainName()); args.add(String.valueOf(io.session().getSerialId())); args.add(io.door().getProtocolFamily() + "-" + io.door().getProtocolVersion()); args.add(io.door().getOwner()); args.add(io.door().getProcess()); args.add(Objects.toString(io.session().getPnfsId(), "")); args.add(Objects.toString(io.session().getPool(), "")); args.add(io.session().getReplyHost()); args.add(Objects.toString(io.session().getStatus(), "")); args.add(String.valueOf(now - io.session().getWaitingSince())); IoJobInfo mover = io.mover(); if (mover == null) { args.add("No-mover()-Found"); } else { args.add(mover.getStatus()); if (mover.getStartTime() > 0L) { long transferTime = mover.getTransferTime(); long bytesTransferred = mover.getBytesTransferred(); args.add(String.valueOf(transferTime)); args.add(String.valueOf(bytesTransferred)); args.add(String.valueOf(transferTime > 0 ? ((double) bytesTransferred / (double) transferTime) : 0)); args.add(String.valueOf(now - mover.getStartTime())); } } sb.append(new Args(args)).append('\n'); } return sb.toString(); } }