/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.utils.cluster.internal;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import de.rcenvironment.core.utils.cluster.ClusterJobInformation;
import de.rcenvironment.core.utils.cluster.ClusterJobInformation.ClusterJobState;
import de.rcenvironment.core.utils.cluster.ClusterJobSourceService;
import de.rcenvironment.core.utils.cluster.ClusterJobStateChangeListener;
import de.rcenvironment.core.utils.cluster.ClusterService;
import de.rcenvironment.core.utils.common.ServiceUtils;
import de.rcenvironment.core.utils.ssh.jsch.JschSessionFactory;
import de.rcenvironment.core.utils.ssh.jsch.SshParameterException;
import de.rcenvironment.core.utils.ssh.jsch.SshSessionConfiguration;
import de.rcenvironment.core.utils.ssh.jsch.executor.JSchCommandLineExecutor;
/**
* Abstract implementation of {@link ClusterService} with common functionality.
* @author Doreen Seider
*/
public abstract class AbstractClusterService implements ClusterService {
protected static final String REMOTE_WORK_DIR = "~";
protected static ClusterJobSourceService informationService
= ServiceUtils.createFailingServiceProxy(ClusterJobSourceService.class);
protected SshSessionConfiguration sshConfiguration;
protected Map<String, String> pathsToQueuingSystemCommands;
protected Session jschSession;
protected volatile long latestFetch = 0;
protected Map<String, ClusterJobInformation> latestFetchedJobInformation;
protected Map<String, ClusterJobState> lastClusterJobStates = new HashMap<String, ClusterJobInformation.ClusterJobState>();
protected final Map<String, ClusterJobStateChangeListener> listeners = new HashMap<String, ClusterJobStateChangeListener>();
protected Timer fetchInformationTimer;
public AbstractClusterService() {}
public AbstractClusterService(SshSessionConfiguration sshConfiguration, Map<String, String> pathsToQueuingSystemCommands) {
this.sshConfiguration = sshConfiguration;
this.pathsToQueuingSystemCommands = pathsToQueuingSystemCommands;
}
/**
* OSGi bind method.
* @param newService instance of {@link ClusterJobSourceService}
*/
public void bindClusterJobSourceService(ClusterJobSourceService newService) {
informationService = newService;
}
protected void unbindDistributedClusterJobSourceInformationService(ClusterJobSourceService oldService) {}
public void setPathsToQueuingSystemCommands(Map<String, String> pathsToQueuingSystemCommands) {
this.pathsToQueuingSystemCommands = pathsToQueuingSystemCommands;
}
@Override
public Set<ClusterJobInformation> fetchClusterJobInformation() throws IOException {
synchronized (this) {
if (jschSession == null || !jschSession.isConnected()) {
try {
jschSession = JschSessionFactory.setupSession(sshConfiguration.getDestinationHost(),
sshConfiguration.getPort(), sshConfiguration.getSshAuthUser(), null,
sshConfiguration.getSshAuthPhrase(), null);
} catch (JSchException e) {
throw new IOException("Establishing connection to cluster failed: " + ExceptionUtils.getRootCauseMessage(e), e);
} catch (SshParameterException e) {
throw new IOException("Establishing connection to cluster failed" + ExceptionUtils.getRootCauseMessage(e), e);
}
}
}
return fetchAndParseClusterJobInformation();
}
protected abstract Set<ClusterJobInformation> fetchAndParseClusterJobInformation() throws IOException;
protected String buildMainCommand(String command) {
// with Java 8 this can be improved by Map.getOrDefault()
if (pathsToQueuingSystemCommands.get(command) != null) {
command = pathsToQueuingSystemCommands.get(command) + command;
}
return command;
}
@Override
public void addClusterJobStateChangeListener(String jobId, final ClusterJobStateChangeListener listener) {
synchronized (listeners) {
listeners.put(jobId, listener);
if (fetchInformationTimer == null) {
fetchInformationTimer = new Timer("Fetch Cluster Job Information Timer", true);
TimerTask fetchInformationTimerTask = new TimerTask() {
@Override
public void run() {
final int oneSecond = 1000;
if (latestFetchedJobInformation == null || new Date().getTime() - latestFetch > FETCH_INTERVAL + oneSecond) {
try {
fetchClusterJobInformation();
} catch (IOException e) {
fetchInformationTimer.cancel();
fetchInformationTimer = null;
jschSession = null;
notifyClusterJobStateChangeListenerAboutFetchingFailure();
throw new RuntimeException("Fetching cluster job information failed", e);
}
}
notifyClusterJobStateChangeListener();
}
};
fetchInformationTimer.schedule(fetchInformationTimerTask, ClusterService.FETCH_INTERVAL,
ClusterService.FETCH_INTERVAL);
}
}
}
private void notifyClusterJobStateChangeListener() {
Set<String> listenersToRemove = new HashSet<String>();
synchronized (listeners) {
for (String jobId : listeners.keySet()) {
ClusterJobInformation.ClusterJobState lastState = lastClusterJobStates.get(jobId);
if (latestFetchedJobInformation.containsKey(jobId)) {
ClusterJobInformation.ClusterJobState latestState = latestFetchedJobInformation.get(jobId).getJobState();
if (lastState == null || !lastState.equals(latestState)) {
if (!listeners.get(jobId).onClusterJobStateChanged(latestState)) {
listenersToRemove.add(jobId);
}
}
lastState = latestState;
} else {
if (!listeners.get(jobId).onClusterJobStateChanged(ClusterJobInformation.ClusterJobState.Unknown)) {
listenersToRemove.add(jobId);
}
}
}
for (String jobId : listenersToRemove) {
listeners.remove(jobId);
}
if (listeners.isEmpty()) {
fetchInformationTimer.cancel();
fetchInformationTimer = null;
}
}
}
private void notifyClusterJobStateChangeListenerAboutFetchingFailure() {
synchronized (listeners) {
for (ClusterJobStateChangeListener listener : listeners.values()) {
listener.onClusterJobStateChanged((ClusterJobState) null);
}
}
}
protected String executesCommand(Session ajschSession, String command, String remoteWorkDir)
throws IOException {
JSchCommandLineExecutor commandLineExecutor = new JSchCommandLineExecutor(ajschSession, remoteWorkDir);
commandLineExecutor.start(command);
try (InputStream stdoutStream = commandLineExecutor.getStdout(); InputStream stderrStream = commandLineExecutor.getStderr();) {
try {
commandLineExecutor.waitForTermination();
} catch (InterruptedException e) {
throw new IOException(e);
}
String stderr = IOUtils.toString(stderrStream);
if (stderr != null && !stderr.isEmpty()) {
throw new IOException(stderr);
}
String stdout = IOUtils.toString(stdoutStream);
return stdout;
}
}
}