/*
* Created on Oct 21, 2003 by mschilli
*/
package alma.acs.commandcenter.engine;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import alma.ACSErr.Completion;
import alma.acs.commandcenter.meta.Firestarter;
import alma.acs.commandcenter.meta.Firestarter.OrbInitException;
import alma.acs.commandcenter.trace.Flow;
import alma.acs.commandcenter.util.MiscUtils;
import alma.acs.commandcenter.util.PreparedString;
import alma.acs.commandcenter.util.StringRingBuffer;
import alma.acs.container.corba.AcsCorba;
import alma.acs.exceptions.AcsJCompletion;
import alma.acs.exceptions.AcsJException;
import alma.acs.util.AcsLocations;
import alma.acsdaemon.ContainerDaemon;
import alma.acsdaemon.ContainerDaemonHelper;
import alma.acsdaemon.DaemonSequenceCallback;
import alma.acsdaemon.DaemonSequenceCallbackHelper;
import alma.acsdaemon.DaemonSequenceCallbackPOA;
import alma.acsdaemon.ServicesDaemon;
import alma.acsdaemon.ServicesDaemonHelper;
import com.trilead.ssh2.ChannelCondition;
import com.trilead.ssh2.Connection;
import com.trilead.ssh2.Session;
/**
* @author mschilli
*/
public class Executor {
public static boolean disableRemote = false;
public static final String SYSPROP_COMMAND_NATIVE_SSH = "AcsCommandCenter.commandNativeSSH";
public static RemoteFlow remoteFlow = new RemoteFlow();
public static LocalInProcFlow localInProcFlow = new LocalInProcFlow();
public static LocalOutProcFlow localOutProcFlow = new LocalOutProcFlow();
public static SingleStepFlow singleStepFlow = new SingleStepFlow();
public static RemoteServicesDaemonFlow remoteServicesDaemonFlow = new RemoteServicesDaemonFlow();
public static RemoteContainerDaemonFlow remoteContainerDaemonFlow = new RemoteContainerDaemonFlow();
private static Logger log = MiscUtils.getPackageLogger(Executor.class);
// ==========================================================================
/**
* @return false - if this failed gracefully
* @throws IOException - if this failed severely
*/
static public boolean remote(boolean nativeSSH, String username, String password, String command, String endMark, NativeCommand.Listener listener, String host) throws Throwable {
if (nativeSSH)
return remoteNative(username, password, command, endMark, listener, host);
else
return remotePortable(username, password, command, endMark, listener, host);
}
static public void remoteDownAll() {
// msc 2009-04: CommandCenterLogic uses this on shutdown. this method would
// need a "nativeSSH" flag, too, but i would like to avoid that. it then
// occurred to me that it shouldn't hurt (maybe it's even a good idea) to
// try and bring down both types of remote processes in any case.
remoteDownAllNative();
remoteDownAllPortable();
}
////////////////////////////////////////////////////////////////////
// ------------------ Remote with SSH library ------------------- //
////////////////////////////////////////////////////////////////////
static protected Vector<Connection> connections = new Vector<Connection>();
static protected Vector<Session> sessions = new Vector<Session>();
/**
* @return false - if this failed gracefully
* @throws IOException - if this failed severely
*/
static private boolean remotePortable(String username, String password, String command, String endMark, NativeCommand.Listener listener, String host) throws IOException {
if (listener == null) {
listener = new NativeCommand.ListenerAdapter(); // normalization: use a do-nothing implementation
}
try {
remoteFlow.reset(null);
// connect
// --------
Connection conn = new Connection(host);
connections.add(conn);
conn.connect();
remoteFlow.success(RemoteFlow.CONNECT);
// login
// ------
boolean isAuthenticated = conn.authenticateWithPassword(username, password);
if (isAuthenticated == false)
throw new IOException("Authentication failed");
remoteFlow.success(RemoteFlow.LOG_IN);
// send command
// -------------
Session sess = conn.openSession();
sessions.add(sess);
/* msc 2008-11:
* We're passing the command as an argument to ssh, just like typing
* > ssh 127.0.0.1 "env"
* on the commandline. This opens a non-login shell on the remote host
* which (thank you bash) won't parse the .bash_profile but only the .bashrc!
* Now unfortunately the usual setup for accounts on Alma machines is to
* have the ACS settings defined in .bash_profile. The optimal way around
* this would be to run a real login shell here by allocating a terminal,
* and then deal with all the input output/stuff ourselves. I'm trying here
* to get away with something cheaper: Explicitly source the .bash_profile
* before running the command.
*/
command = ". ~/.bash_profile ; " + command;
log.info("Now sending: '" + command + "'");
sess.execCommand(command);
remoteFlow.success(RemoteFlow.SEND_COMMAND);
// read output, scan for endmark
// ----------------------------
SearchBuffer searchStdout = null;
SearchBuffer searchStderr = null;
if (endMark != null) {
searchStdout = new SearchBuffer(endMark);
searchStderr = new SearchBuffer(endMark);
}
InputStream stdout = sess.getStdout();
InputStream stderr = sess.getStderr();
byte[] buffer = new byte[8192];
while (true) {
if (stdout.available() == 0 && stderr.available() == 0) {
/* Even though currently there is no data available, it may be that new data arrives
* and the session's underlying channel is closed before we call waitForCondition().
* This means that EOF and STDOUT_DATA (or STDERR_DATA, or both) may be set together. */
int conditions = sess.waitForCondition(
ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA | ChannelCondition.EOF, //
10*1000); // allow several seconds
if ((conditions & ChannelCondition.TIMEOUT) != 0) {
throw new IOException("Timeout while waiting for data from peer");
}
if ((conditions & ChannelCondition.EOF) != 0) {
/* The remote side won't send us further data ... */
if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) == 0) {
/* ... and we have consumed all data in the local arrival window. */
break;
}
}
/* At this point, either STDOUT_DATA or STDERR_DATA, (or both) is set. */
}
/* If you below use "if" instead of "while", then the way the output appears on the local
* stdout and stder streams is more "balanced". Addtionally reducing the buffer size
* will also improve the interleaving, but performance will slightly suffer.
* OKOK, that all matters only if you get HUGE amounts of stdout and stderr data =)
*/
if (stdout.available() > 0) {
int len = stdout.read(buffer);
listener.stdoutWritten(null, new String(buffer, 0, len));
if (searchStdout != null)
if (searchStdout.add(buffer, 0, len)) {
remoteFlow.success(RemoteFlow.COMPLETE);
break;
}
}
if (stderr.available() > 0) {
int len = stderr.read(buffer);
// msc 2008-11: porting to a different ssh library. this should of course
// call stderrWritten() but i don't want to change the original behavior.
listener.stdoutWritten(null, new String(buffer, 0, len));
if (searchStderr != null)
if (searchStderr.add(buffer, 0, len)) {
remoteFlow.success(RemoteFlow.COMPLETE);
break;
}
}
}
return true;
} catch (IOException exc) {
remoteFlow.failure(exc);
// we kind of expect IOExceptions and it should be enough
// to show them in the flow dialog. so we don't rethrow them.
/* throw exc; */
return false;
}
}
/**
* Shuts down all ssh-sessions and -connections that may still be active.
*/
static private void remoteDownAllPortable () {
for (Session sess : sessions) {
try {
sess.close();
log.fine("closed " + sess);
} catch (Exception exc) {
log.fine("could not close " + sess);
}
}
for (Connection conn : connections) {
try {
conn.close();
log.fine("closed " + conn);
} catch (Exception exc) {
log.fine("could not close " + conn);
}
}
}
public static class RemoteFlow extends Flow {
static final String CONNECT = "Connect";
static final String LOG_IN = "Log In";
static final String SEND_COMMAND = "Send Command";
static final String COMPLETE = "Command Completion";
{
consistsOf(null, new String[] { CONNECT, LOG_IN, SEND_COMMAND, COMPLETE });
}
}
////////////////////////////////////////////////////////////////////
// ---------------- Remote through native SSH ------------------- //
////////////////////////////////////////////////////////////////////
static private Vector<NativeCommand> remoteNativeTasks = new Vector<NativeCommand>();
/**
* @return false - if this failed gracefully
* @throws Throwable - if this failed severely
*/
static private boolean remoteNative(String username, final String password, final String command, String endMark, NativeCommand.Listener listener, String host) throws Throwable {
localOutProcFlow.reset(null);
if (listener == null) {
listener = new NativeCommand.ListenerAdapter(); // normalization: use a do-nothing implementation
}
// --- set up task
// ssh-command can be changed at any time through a system property if necessary
// msc (2006-04-28): added "-X" for X-forwarding, as done for OMC in Socorro in Jan'06
String sshPatternDefault = "ssh -X -t -l ? ? /bin/bash --login -c \"?\"";
String sshPattern = System.getProperty(SYSPROP_COMMAND_NATIVE_SSH, sshPatternDefault);
PreparedString prep = new PreparedString(sshPattern);
String sshCommand = prep.toString(new String[]{username, host, command});
/*String sshCommand = "ssh -t -l "+username+" "+host+" /bin/bash --login -c \""+command+"\"";*/
boolean foreground = true;
long maxExecutionTime = -1;
endMark = ".*" + endMark + ".*"; // make a regular expression
NativeCommand task = new NativeCommand(sshCommand, foreground, maxExecutionTime, endMark);
remoteNativeTasks.add(task);
task.addListener(listener);
task.addListener(new NativeCommand.ListenerAdapter() {
@Override
public void statusChanged(NativeCommand task, String oldStatus) {
if (task.getStatus() == NativeCommand.RUNNING) {
localOutProcFlow.success(LocalOutProcFlow.RUN);
}
}
});
// --- run task
// the current thread will block if "foreground" is true
task.run();
// --- examine results
Throwable latestExc = task.getLatestException();
// if we get here and the process-status is still RUNNING,
// it simply means the expected output has occurred but the
// process itself is still running
log.info("Process invoked. Process is now: " + task.getStatus() + ". Exitcode: " +
( task.getExitValue()!=null? task.getExitValue().toString() : "none (yet)" ) + ". Command was '" + command + "'"
);
if (latestExc != null) {
log.fine("During process invocation (at least) one error occurred: " + latestExc);
remoteFlow.failure(latestExc);
throw latestExc;
} else {
localOutProcFlow.success(LocalOutProcFlow.COMPLETE);
return true;
}
}
static private void remoteDownAllNative() {
for (Enumeration<NativeCommand> en = remoteNativeTasks.elements(); en.hasMoreElements();) {
NativeCommand t = null;
try {
t = (NativeCommand) en.nextElement();
t.process.destroy();
} catch (Exception e) {
log.finer("Failed to destroy native-ssh task " + t);
}
}
}
////////////////////////////////////////////////////////////////////
// --------------------- Local In-Process ----------------------- //
////////////////////////////////////////////////////////////////////
// static protected Vector threads = new Vector();
/**
* @param properties the properties to insert (and override) into the system properties
* @param pexpect as soon as the process writes this to stdout, this method returns
* @param listener a proprietary listener if the caller wants to hear about the process' output
* @param runMain a callback that will be invoked by the newly spawned thread
*/
static public void localInProc(Properties properties, String pexpect, NativeCommand.Listener listener, final RunMain runMain) {
if (listener == null) {
listener = new NativeCommand.ListenerAdapter(); // normalization: use a do-nothing implementation
}
localInProcFlow.reset(null);
// insert given properties into system properties
System.getProperties().putAll(properties);
//CommandCenter.dbg_printProps(System.getProperties(), "sysprops");
// --- use our own search-enabled stream as stdout
LocalInProcStream outstream = new LocalInProcStream();
outstream.setListener(listener);
outstream.setConsumer(Thread.currentThread(), pexpect);
System.setOut(new PrintStream(outstream));
Thread t = new Thread() {
@Override
public void run() {
//localInProcFlow.trying(LocalInProcFlow.ALIVE);
try {
runMain.runMain();
} catch (Throwable exc) {
localInProcFlow.failure(exc);
}
}
};
t.start();
localInProcFlow.success(LocalInProcFlow.START);
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException exc) {}
// it is safe to leave subthread t alone now
// it can now do what it wants, the main thread goes on
localInProcFlow.success(LocalInProcFlow.ALIVE);
//CommandCenter.dbg_printProps(System.getProperties(), "sysprops");
}
static protected class LocalInProcStream extends OutputStream {
String search;
Thread consumer;
StringRingBuffer searchBuff;
NativeCommand.Listener listener;
void setListener(NativeCommand.Listener listener) {
this.listener = listener;
}
void setConsumer(Thread consumer, String search) {
this.consumer = consumer;
this.search = search;
if (search != null) {
this.searchBuff = new StringRingBuffer(search.length());
}
}
StringBuffer lineBuffer = new StringBuffer(512);
@Override
public void write(int b) throws IOException {
char c = (char) b;
// risk of infinite loop:
// if above we do System.setOut(), we must not do System.out.println() here
// System.err.print(c);
// deal with Task.Listener if it exists
if (listener != null) {
lineBuffer.append(c);
if (c == '\n') {
listener.stdoutWritten(null, lineBuffer.toString());
lineBuffer.setLength(0);
}
}
// deal with search expression if it exists
if (search != null && searchBuff != null) {
searchBuff.add(c);
if (searchBuff.equals(search)) {
// System.err.println("\n" + search + " occured\n");
consumer.interrupt();
}
}
}
}
/**
* The only sense of this is to have a flow for normal java instructions
* that may take a while.
* @param runMain the java instructions to perform
*/
static public void local(final RunMain runMain) {
singleStepFlow.reset(null);
try {
runMain.runMain();
} catch (Throwable exc) {
singleStepFlow.failure(exc);
}
singleStepFlow.success(SingleStepFlow.DONE);
}
static public interface RunMain {
public void runMain() throws Throwable;
}
public static class LocalInProcFlow extends Flow {
static final String START = "Thread Start";
static final String ALIVE = "Delegate Up";
{
consistsOf(null, new String[] { START, ALIVE });
}
}
public static class SingleStepFlow extends Flow {
static final String DONE = "Task completion";
{
consistsOf(null, new String[] { DONE });
}
}
////////////////////////////////////////////////////////////////////
// ------------------- Local Out-of-Process --------------------- //
////////////////////////////////////////////////////////////////////
/**
*
*/
static public void localOutProc(String command, boolean foreground, long maxExecutionTime, String endMark, NativeCommand.Listener listener) throws Throwable {
if (listener == null) {
listener = new NativeCommand.ListenerAdapter(); // normalization: use a do-nothing implementation
}
localOutProcFlow.reset(null);
log.fine("Now executing: '" + command + "'");
// --- set up task
// we make the endMark a regular expression by enclosing it with ".*"
NativeCommand task = new NativeCommand(command, foreground, maxExecutionTime, ".*" + endMark + ".*");
task.addListener(listener);
task.addListener(new NativeCommand.ListenerAdapter() {
@Override
public void statusChanged(NativeCommand task, String oldStatus) {
if (task.getStatus() == NativeCommand.RUNNING)
localOutProcFlow.success(LocalOutProcFlow.RUN);
}
});
// --- run task
// the current thread will
// block if "foreground" is true
task.run();
// --- examine results
Throwable latestExc = task.getLatestException();
// if we get here and the process-status is still RUNNING,
// it simply means the expected output has occurred but the
// process itself is still running
log.info("Process invoked. Process is now: " + task.getStatus() + ". Exitcode: " +
( task.getExitValue()!=null? task.getExitValue().toString() : "none (yet)" ) + ". Command was '" + command + "'"
);
if (latestExc != null) {
log.fine("During process invocation (at least) one error occurred: " + latestExc);
localOutProcFlow.failure(latestExc);
throw latestExc;
} else {
localOutProcFlow.success(LocalOutProcFlow.COMPLETE);
}
}
public static class LocalOutProcFlow extends Flow {
static final String RUN = "Process Launch";
static final String COMPLETE = "Process Completion";
{
consistsOf(null, new String[] { RUN, COMPLETE });
}
}
////////////////////////////////////////////////////////////////////
// ------------------- Remote using Daemons --------------------- //
////////////////////////////////////////////////////////////////////
static private Firestarter firestarter;
static public void remoteDaemonEnable(Firestarter fs) {
firestarter = fs;
}
/**
* Starts or stops ACS via the ACS services daemon.
* This call returns only when the action has completed.
* Exceptions will be returned instead of thrown.
* @return any exception that occurs underways
*/
/* msc 2009-12: this method has never thrown exceptions, instead they can be detected through
* Flow listening, and as of today also by looking at the return value. Starting to throw exceptions
* would be too big a change that I don't want to risk. I have no time to verify it doesn't harm. */
static public Exception remoteDaemonForServices(String host, int instance, boolean startStop, String cmdFlags, NativeCommand.Listener listener) {
if (listener != null) {
listener.stdoutWritten(null, "\nIn daemon mode, output cannot be displayed.\n"
+ "See logs in <daemon-owner>/.acs/commandcenter on host " + host + "\n");
}
String info = ((startStop) ? "Starting" : "Stopping") + " Acs Suite on host '" + host + "' (instance "+ instance + ")";
remoteServicesDaemonFlow.reset(info);
ServicesDaemon daemon = null;
String step = ""; // msc 2014-11 ICT-3753: finer-grained logging
try {
org.omg.CORBA.ORB orb;
AcsCorba acsCorba = null;
remoteServicesDaemonFlow.trying(RemoteServicesDaemonFlow.INIT_CORBA);
step = "access acs-corba object";
acsCorba = firestarter.giveAcsCorba(); // throws OrbInitException
step = "access orb";
orb = acsCorba.getORB();
remoteServicesDaemonFlow.success(RemoteServicesDaemonFlow.INIT_CORBA);
remoteServicesDaemonFlow.trying(RemoteServicesDaemonFlow.CONNECT_DAEMON);
step = "convert host name to daemon address";
String daemonLoc = AcsLocations.convertToServicesDaemonLocation(host);
step = "convert daemon address to corba reference";
org.omg.CORBA.Object object = orb.string_to_object(daemonLoc);
step = "narrow corba reference to daemon object";
daemon = ServicesDaemonHelper.narrow(object);
step = "sanity check daemon object";
if (daemon == null)
throw new NullPointerException("received null trying to retrieve acsdaemon "+daemonLoc);
try {
if (daemon._non_existent()) // this may be superfluous with daemons but shouldn't hurt either
log.log (Level.INFO, "acsdaemon '"+daemonLoc+"' reported as non_existent, trying to use it nonetheless.");
} catch (Exception exc) {log.log (Level.INFO, "problem verifying acsdaemon "+daemonLoc+" exists, trying to use it anyhow.", exc);}
remoteServicesDaemonFlow.success(RemoteServicesDaemonFlow.CONNECT_DAEMON);
remoteServicesDaemonFlow.trying(RemoteServicesDaemonFlow.SEND_COMMAND);
final BlockingQueue<Completion> sync = new ArrayBlockingQueue<Completion>(1);
DaemonSequenceCallbackPOA daemonCallbackImpl = new DaemonSequenceCallbackPOA() {
public void done(Completion comp) {sync.add(comp);}
public void working(String service, String host, short instance_number, Completion comp) {}
};
step = "create daemon callback";
DaemonSequenceCallback daemonCallback = DaemonSequenceCallbackHelper.narrow(acsCorba.activateOffShoot(daemonCallbackImpl, acsCorba.getRootPOA()) );
step = "send request to daemon";
if (startStop == true)
daemon.start_acs(daemonCallback, (short)instance, cmdFlags);
else
daemon.stop_acs(daemonCallback, (short)instance, cmdFlags);
remoteServicesDaemonFlow.success(RemoteServicesDaemonFlow.SEND_COMMAND);
remoteServicesDaemonFlow.trying(RemoteServicesDaemonFlow.AWAIT_RESPONSE);
// The services daemon's start/stop methods are implemented asynchronously,
// which means we need to wait for the callback notification.
// @TODO: Perhaps a 10 minute timeout is too much though?
step = "poll on reply queue";
long timeout = 10; TimeUnit timeoutUnit = TimeUnit.MINUTES;
Completion daemonReplyRaw = sync.poll(timeout, timeoutUnit);
if (daemonReplyRaw == null)
throw new RuntimeException("Timeout: Acs daemon did not "+(startStop?"start":"stop")+" Acs within "+timeout+" "+timeoutUnit);
step = "deserialize daemon response";
AcsJCompletion daemonReply = AcsJCompletion.fromCorbaCompletion(daemonReplyRaw);
if (daemonReply.isError()) {
AcsJException exc = daemonReply.getAcsJException();
throw new Exception("daemon responded with error "+exc.getMessage(), exc);
}
remoteServicesDaemonFlow.success(RemoteServicesDaemonFlow.AWAIT_RESPONSE);
return null;
} catch (Exception exc) {
remoteServicesDaemonFlow.failure(exc);
return new Exception(remoteServicesDaemonFlow.current()+":"+step+": "+exc.getMessage(), exc);
} finally {
// msc 2014-11 ICT-3753: omc-to-daemon connection can get stale. this apparently helps.
if (daemon != null) {
try {
daemon._release();
} catch (Exception exc) {
log.log (Level.INFO, "failure releasing internal resources for daemon, ignoring: "+exc.getMessage(), exc);
}
}
}
}
public static class RemoteServicesDaemonFlow extends Flow {
static final String INIT_CORBA = "Assert corba connectivity";
static final String CONNECT_DAEMON = "Connect to acsservicesdaemon";
static final String SEND_COMMAND = "Send command to daemon";
static final String AWAIT_RESPONSE = "Receive remote response";
{
consistsOf (null, new String[]{INIT_CORBA, CONNECT_DAEMON, SEND_COMMAND, AWAIT_RESPONSE});
}
}
/**
* @param startStop - if true, the daemon starts the container, otherwise stops it. @todo rethink how useful this overloading is.
* @param contType - only needed for starting (i.e. startStop==true)
* @param contTypeMods - only needed for starting (i.e. startStop==true)
* @see http://www.eso.org/projects/alma/develop/acs/OnlineDocs/ACS_docs/schemas/urn_schemas-cosylab-com_Container_1.0/complexType/DeployInfo.html
*/
/* msc 2010-02: this method has never thrown exceptions, instead they can be detected through
* Flow listening, and as of today also by looking at the return value. Starting to throw exceptions
* would be too big a change that I don't want to risk. I have no time to verify it doesn't harm. */
static public Exception remoteDaemonForContainers (String host, int instance, boolean startStop, String contName, String contType, String[] contTypeMods, String cmdFlags, NativeCommand.Listener listener) {
if (listener != null) {
listener.stdoutWritten(null, "\nIn daemon mode, output cannot be displayed.\n" +
"See logs in <daemon-owner>/.acs/commandcenter on host "+host+"\n");
}
String info = ((startStop)? "Starting" : "Stopping") + " container "+contName+" on host '"+host+"' (instance "+instance+")";
remoteContainerDaemonFlow.reset(info);
ContainerDaemon daemon = null;
String step = ""; // msc 2014-11 ICT-3753: finer-grained logging
try {
org.omg.CORBA.ORB orb = null;
remoteContainerDaemonFlow.trying(RemoteContainerDaemonFlow.INIT_CORBA);
step = "access orb";
orb = firestarter.giveOrb();
if (orb == null)
throw new NullPointerException("received null when trying to access local orb");
remoteContainerDaemonFlow.success(RemoteContainerDaemonFlow.INIT_CORBA);
remoteContainerDaemonFlow.trying(RemoteContainerDaemonFlow.CONNECT_DAEMON);
step = "convert host name to daemon address";
String daemonLoc = AcsLocations.convertToContainerDaemonLocation(host);
step = "convert daemon address to corba reference";
org.omg.CORBA.Object object = orb.string_to_object(daemonLoc);
step = "narrow corba reference to daemon object";
daemon = ContainerDaemonHelper.narrow(object);
step = "sanity check daemon object";
if (daemon == null)
throw new NullPointerException("received null trying to retrieve acsdaemon "+daemonLoc);
try {
if (daemon._non_existent()) // this may be superfluous with daemons but shouldn't hurt either
log.log (Level.INFO, "acsdaemon '"+daemonLoc+"' reported as non_existent, trying to use it nonetheless.");
} catch (Exception exc) {log.log (Level.INFO, "problem verifying acsdaemon "+daemonLoc+" exists, trying to use it anyhow.", exc);}
remoteContainerDaemonFlow.success(RemoteContainerDaemonFlow.CONNECT_DAEMON);
remoteContainerDaemonFlow.trying(RemoteContainerDaemonFlow.SEND_COMMAND);
step = "send request to daemon";
if (startStop == true) {
daemon.start_container(contType, contName, (short)instance, contTypeMods, cmdFlags);
} else {
daemon.stop_container(contName, (short)instance, cmdFlags);
}
step = "wait for completion";
// i'm adding this option of sleeping a little, simply because it gives the user a feeling of
// "something is happening". this can be reconfigured, however, if desired (omc will do so).
try {Thread.sleep(remoteDaemonForContainersCompletionDelay);} catch (InterruptedException exc) {}
remoteContainerDaemonFlow.success(RemoteContainerDaemonFlow.SEND_COMMAND);
return null;
} catch (Exception exc) {
remoteContainerDaemonFlow.failure(exc);
return new Exception(remoteContainerDaemonFlow.current()+":"+step+": "+exc.getMessage(), exc);
} finally {
// msc 2014-11 ICT-3753: omc-to-daemon connection can get stale. this apparently helps.
if (daemon != null) {
try {
daemon._release();
} catch (Exception exc) {
log.log (Level.INFO, "failure releasing internal resources for daemon, ignoring: "+exc.getMessage(), exc);
}
}
}
}
static public int remoteDaemonForContainersCompletionDelay = 2500;
public static class RemoteContainerDaemonFlow extends Flow {
static final String INIT_CORBA = "Assert corba connectivity";
static final String CONNECT_DAEMON = "Connect to acscontainerdaemon";
static final String SEND_COMMAND = "Send command to daemon";
{
consistsOf (null, new String[]{INIT_CORBA, CONNECT_DAEMON, SEND_COMMAND});
}
}
/**
* Helper class for stream parsing, this doesn't bear the overhead
* of util class StringRingBuffer.
*/
static class SearchBuffer {
byte[] search;
byte[] data;
int next = 0;
boolean isFillingUp = true;
SearchBuffer (String searched) {
search = searched.getBytes();
data = new byte[search.length];
}
/**
* Convenience method for feeding bytes into this buffer.
* @return whether the search-expression was found
*/
boolean add (byte[] bytes, final int off, final int len) {
for (int i=off; i<off+len; i++) {
if (add (bytes[i]))
return true;
}
return false;
}
/**
* @param b - a newly incoming byte
* @return whether the byte made the search-expression complete
*/
boolean add (byte b) {
// fill new character into this
// -----------------------------
data[next] = b;
next += 1;
if (next == data.length)
next = 0;
if (isFillingUp && next == 0)
isFillingUp = false;
// compare this with searched
// ---------------------------
if (isFillingUp)
return false;
int idx;
int otherIdx = 0;
idx = next;
while (idx < data.length) {
if (data[idx] != search[otherIdx])
return false;
idx++;
otherIdx++;
}
idx = 0;
while (idx < next) {
if (data[idx] != search[otherIdx])
return false;
idx++;
otherIdx++;
}
return true;
}
}
}