package water.deploy;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.*;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
import water.Boot;
import water.H2O;
import water.util.Log;
/**
* Executes code in a separate VM.
*/
public abstract class VM {
private final ArrayList<String> _args;
private Process _process;
private boolean _inherit;
private File _out, _err;
public VM(String[] java, String[] args) {
_args = new ArrayList<String>();
_args.add(System.getProperty("java.home") + "/bin/java");
// Iterate on URIs in case jar has been unpacked by Boot
_args.add("-cp");
String cp = "";
for( URL url : ((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs() ) {
try {
cp += new File(new URI(url.toString())) + File.pathSeparator;
} catch( URISyntaxException e ) {
throw Log.errRTExcept(e);
}
}
_args.add(cp);
_args.addAll(Arrays.asList(java));
if( args != null )
_args.addAll(Arrays.asList(args));
}
static String[] javaArgs(String main) {
RuntimeMXBean r = ManagementFactory.getRuntimeMXBean();
ArrayList<String> list = new ArrayList<String>();
for( String s : r.getInputArguments() )
if( !s.startsWith("-agentlib") )
if( !s.startsWith("-Xbootclasspath") )
list.add(s);
if( System.getProperty(H2O.DEBUG_ARG) != null )
if( list.indexOf("-D" + H2O.DEBUG_ARG) < 0 )
list.add("-D" + H2O.DEBUG_ARG);
list.add(main);
return list.toArray(new String[list.size()]);
}
public Process process() {
return _process;
}
public void inheritIO() {
_inherit = true;
}
public void persistIO(String out, String err) throws IOException {
_out = new File(out);
_err = new File(err);
}
public void start() {
ProcessBuilder builder = new ProcessBuilder(_args);
try {
assert !_inherit || (_out == null);
_process = builder.start();
if( _inherit )
inheritIO(_process, null);
if( _out != null )
persistIO(_process, _out, _err);
} catch( IOException e ) {
throw Log.errRTExcept(e);
}
}
public boolean isAlive() {
try {
_process.exitValue();
return false;
} catch( IllegalThreadStateException xe ) {
return true;
} catch( Exception e ) {
throw Log.errRTExcept(e);
}
}
public int waitFor() {
try {
return _process.waitFor();
} catch( InterruptedException e ) {
throw Log.errRTExcept(e);
}
}
public void kill() {
_process.destroy();
try {
_process.waitFor();
} catch( InterruptedException xe ) {
// Ignore
}
}
public static void exitWithParent() {
Thread thread = new Thread() {
@Override public void run() {
// Avoid on Windows as it exits immediately. Seems to work using Java7
// ProcessBuilder.redirectInput, but we need to run on Java 6 for now
if( !System.getProperty("os.name").toLowerCase().contains("win") ) {
for( ;; ) {
int b;
try {
b = System.in.read();
} catch( Exception e ) {
b = -1;
}
if( b < 0 ) {
Log.info("Assuming parent done, exit(0)");
H2O.exit(0);
}
}
}
}
};
thread.setDaemon(true);
thread.start();
}
public static void inheritIO(Process process, final String header) {
forward(process, header, process.getInputStream(), System.out);
forward(process, header, process.getErrorStream(), System.err);
}
public static void persistIO(Process process, File out, File err) throws IOException {
forward(process, null, process.getInputStream(), new PrintStream(out));
forward(process, null, process.getErrorStream(), new PrintStream(err));
}
private static void forward(Process process, final String header, InputStream source, final PrintStream target) {
final BufferedReader source_ = new BufferedReader(new InputStreamReader(source));
Thread thread = new Thread() {
@Override public void run() {
try {
for( ;; ) {
String line = source_.readLine();
if( line == null )
break;
String s = header == null ? line : header + line;
Log.unwrap(target, s);
}
} catch( IOException e ) {
// Ignore, process probably done
}
}
};
thread.start();
}
/**
* A VM whose only job is to wait for its parent to be gone, then kill its child process.
* Otherwise every killed test leaves a bunch of orphan ssh and java processes.
*/
public static class Watchdog extends VM {
public Watchdog(String[] java, String[] node) {
super(java, node);
}
protected static void exec(ArrayList<String> list) throws Exception {
ProcessBuilder builder = new ProcessBuilder(list);
final Process process = builder.start();
NodeVM.inheritIO(process, null);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override public void run() {
process.destroy();
}
});
process.waitFor();
}
}
static class Params implements Serializable {
String[] _host, _java, _node;
Params(Host host, String[] java, String[] node) {
_host = new String[] { host.address(), host.user(), host.key() };
_java = java;
_node = node;
}
}
public static File h2oFolder() {
File target;
if( Boot._init.fromJar() )
target = new File(Boot._init.jarPath());
else {
try {
URL url = Boot._init.getResource(H2O.class.getName().replace('.', '/') + ".class");
target = new File(url.toURI()).getParentFile().getParentFile().getParentFile();
} catch( URISyntaxException e ) {
throw new RuntimeException(e);
}
}
return target.getParentFile();
}
/**
* A remote JVM, launched over SSH.
*/
public static class SSH extends Watchdog {
Host _host;
Thread _thread;
public SSH(Host host, String[] java, String[] node) {
this(new String[] { SSH.class.getName() }, host, java, node);
}
public SSH(String[] localJava, Host host, String[] java, String[] node) {
super(localJava, new String[] { write(new Params(host, java, node)) });
_host = host;
}
public Host host() {
return _host;
}
final void startThread() {
_thread = new Thread() {
@Override public void run() {
try {
SSH.this.start();
SSH.this.waitFor();
} catch( Exception ex ) {
Log.err(ex);
}
}
};
_thread.setDaemon(true);
_thread.start();
}
public static void main(String[] args) throws Exception {
exitWithParent();
Params p = read(args[0]);
Host host = new Host(p._host[0], p._host[1], p._host[2]);
ArrayList<String> list = new ArrayList<String>();
list.addAll(Arrays.asList(host.sshWithArgs().split(" ")));
list.add(host.address());
list.add(command(p._java, p._node));
exec(list);
}
static String command(String[] javaArgs, String[] nodeArgs) {
String cp = "";
try {
String h2o = h2oFolder().getCanonicalPath();
for( String s : System.getProperty("java.class.path").split(File.pathSeparator) ) {
cp += cp.length() != 0 ? ":" : "";
String path = new File(s).getCanonicalPath();
if( path.startsWith(h2o) )
path = path.substring(h2o.length() + 1);
cp += path.replace('\\', '/').replace(" ", "\\ ");
}
} catch( IOException e ) {
throw Log.errRTExcept(e);
}
String java = Cloud.JRE != null ? new File(Cloud.JRE).getName() + "/bin/java" : "java";
String command = "cd " + Host.FOLDER + ";" + java + " -cp " + cp;
for( String s : javaArgs )
command += " " + s;
for( String s : nodeArgs )
command += " " + s;
return command.replace("$", "\\$");
}
}
static String write(Serializable s) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(s);
return StringUtils.newStringUtf8(Base64.encodeBase64(bos.toByteArray(), false));
} finally {
out.close();
bos.close();
}
} catch( Exception ex ) {
throw Log.errRTExcept(ex);
}
}
static <T> T read(String s) {
try {
ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(Base64.decodeBase64(s)));
try {
return (T) in.readObject();
} finally {
in.close();
}
} catch( Exception ex ) {
throw Log.errRTExcept(ex);
}
}
}