From ea0dac2882c7c844b2fdc4da7dd6d0e36509eb97 Mon Sep 17 00:00:00 2001 From: Fedor Bobin Date: Tue, 6 Jul 2021 11:52:33 +0300 Subject: [PATCH] Nodes should report exit code in console. --- .../CentOS_RemoteNodeFeatureTest.java | 6 + .../FreeBSD_RemoteNodeFeatureTest.java | 6 + .../nanocloud/LocalNodeFeatureTest.java | 6 + .../gridkit/nanocloud/ViNodeFeatureTest.java | 26 ++ .../ssh/TunnellerJvmReplicator.java | 277 +++++++++--------- .../telecontrol/ProcessSporeLauncher.java | 1 + 6 files changed, 186 insertions(+), 136 deletions(-) diff --git a/nanocloud/src/test/java/org/gridkit/nanocloud/CentOS_RemoteNodeFeatureTest.java b/nanocloud/src/test/java/org/gridkit/nanocloud/CentOS_RemoteNodeFeatureTest.java index 0c5a2e5..99dad92 100644 --- a/nanocloud/src/test/java/org/gridkit/nanocloud/CentOS_RemoteNodeFeatureTest.java +++ b/nanocloud/src/test/java/org/gridkit/nanocloud/CentOS_RemoteNodeFeatureTest.java @@ -195,4 +195,10 @@ public void verify_jvm_agent_with_options() throws Exception { public void verify_jvm_agent_multiple_agents() throws Exception { super.verify_jvm_agent_multiple_agents(); } + + @Test + @Override + public void verify_exit_code_is_printed_to_logs() throws Exception { + super.verify_exit_code_is_printed_to_logs(); + } } diff --git a/nanocloud/src/test/java/org/gridkit/nanocloud/FreeBSD_RemoteNodeFeatureTest.java b/nanocloud/src/test/java/org/gridkit/nanocloud/FreeBSD_RemoteNodeFeatureTest.java index 62bec1f..6f1ad4c 100644 --- a/nanocloud/src/test/java/org/gridkit/nanocloud/FreeBSD_RemoteNodeFeatureTest.java +++ b/nanocloud/src/test/java/org/gridkit/nanocloud/FreeBSD_RemoteNodeFeatureTest.java @@ -195,4 +195,10 @@ public void verify_jvm_agent_with_options() throws Exception { public void verify_jvm_agent_multiple_agents() throws Exception { super.verify_jvm_agent_multiple_agents(); } + + @Test + @Override + public void verify_exit_code_is_printed_to_logs() throws Exception { + super.verify_exit_code_is_printed_to_logs(); + } } diff --git a/nanocloud/src/test/java/org/gridkit/nanocloud/LocalNodeFeatureTest.java b/nanocloud/src/test/java/org/gridkit/nanocloud/LocalNodeFeatureTest.java index 384fe7b..ebd3af1 100644 --- a/nanocloud/src/test/java/org/gridkit/nanocloud/LocalNodeFeatureTest.java +++ b/nanocloud/src/test/java/org/gridkit/nanocloud/LocalNodeFeatureTest.java @@ -160,6 +160,12 @@ public void verify_slave_working_dir() throws IOException { super.verify_slave_working_dir(); } + @Test + @Override + public void verify_exit_code_is_printed_to_logs() throws Exception { + super.verify_exit_code_is_printed_to_logs(); + } + @Test public void verify_jvm_agent() throws Exception { super.verify_jvm_agent(); diff --git a/nanocloud/src/test/java/org/gridkit/nanocloud/ViNodeFeatureTest.java b/nanocloud/src/test/java/org/gridkit/nanocloud/ViNodeFeatureTest.java index 0ecec52..94c0272 100755 --- a/nanocloud/src/test/java/org/gridkit/nanocloud/ViNodeFeatureTest.java +++ b/nanocloud/src/test/java/org/gridkit/nanocloud/ViNodeFeatureTest.java @@ -26,6 +26,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.StringWriter; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -694,6 +695,31 @@ public Void call() throws Exception { } }); } + + public void verify_exit_code_is_printed_to_logs() throws Exception { + ViNode node = cloud.node(testName.getMethodName()); + StringWriter writer = new StringWriter(); + node.x(VX.CONSOLE).bindOut(writer); + node.exec(new Runnable() { + @Override + public void run() { + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.exit(42); + } + }).start(); + } + }); + + Thread.sleep(2000); + assertThat(writer.toString()).contains("Process exited with code 42"); + } private static String readMarkerFromResources() throws IOException { URL url = IsolateNodeFeatureTest.class.getResource("/marker.txt"); diff --git a/telecontrol-ssh/src/main/java/org/gridkit/vicluster/telecontrol/ssh/TunnellerJvmReplicator.java b/telecontrol-ssh/src/main/java/org/gridkit/vicluster/telecontrol/ssh/TunnellerJvmReplicator.java index 01c5024..916570d 100644 --- a/telecontrol-ssh/src/main/java/org/gridkit/vicluster/telecontrol/ssh/TunnellerJvmReplicator.java +++ b/telecontrol-ssh/src/main/java/org/gridkit/vicluster/telecontrol/ssh/TunnellerJvmReplicator.java @@ -29,78 +29,78 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - -import com.jcraft.jsch.ChannelExec; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; -import org.gridkit.util.concurrent.AdvancedExecutor; -import org.gridkit.util.concurrent.FutureBox; -import org.gridkit.util.concurrent.FutureEx; -import org.gridkit.vicluster.telecontrol.BackgroundStreamDumper; -import org.gridkit.vicluster.telecontrol.Classpath; -import org.gridkit.vicluster.telecontrol.ClasspathUtils; -import org.gridkit.vicluster.telecontrol.ExecCommand; -import org.gridkit.vicluster.telecontrol.FileBlob; -import org.gridkit.vicluster.telecontrol.JvmConfig; -import org.gridkit.vicluster.telecontrol.ManagedProcess; -import org.gridkit.vicluster.telecontrol.StreamCopyService; -import org.gridkit.vicluster.telecontrol.bootstraper.Bootstraper; -import org.gridkit.vicluster.telecontrol.bootstraper.Tunneller; -import org.gridkit.vicluster.telecontrol.bootstraper.TunnellerConnection; -import org.gridkit.vicluster.telecontrol.bootstraper.TunnellerConnection.ExecHandler; -import org.gridkit.vicluster.telecontrol.bootstraper.TunnellerConnection.SocketHandler; -import org.gridkit.zerormi.DuplexStream; -import org.gridkit.zerormi.NamedStreamPair; -import org.gridkit.zerormi.hub.LegacySpore; -import org.gridkit.zerormi.hub.MasterHub; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import org.gridkit.util.concurrent.AdvancedExecutor; +import org.gridkit.util.concurrent.FutureBox; +import org.gridkit.util.concurrent.FutureEx; +import org.gridkit.vicluster.telecontrol.BackgroundStreamDumper; +import org.gridkit.vicluster.telecontrol.Classpath; +import org.gridkit.vicluster.telecontrol.ClasspathUtils; +import org.gridkit.vicluster.telecontrol.ExecCommand; +import org.gridkit.vicluster.telecontrol.FileBlob; +import org.gridkit.vicluster.telecontrol.JvmConfig; +import org.gridkit.vicluster.telecontrol.ManagedProcess; +import org.gridkit.vicluster.telecontrol.StreamCopyService; +import org.gridkit.vicluster.telecontrol.bootstraper.Bootstraper; +import org.gridkit.vicluster.telecontrol.bootstraper.Tunneller; +import org.gridkit.vicluster.telecontrol.bootstraper.TunnellerConnection; +import org.gridkit.vicluster.telecontrol.bootstraper.TunnellerConnection.ExecHandler; +import org.gridkit.vicluster.telecontrol.bootstraper.TunnellerConnection.SocketHandler; +import org.gridkit.zerormi.DuplexStream; +import org.gridkit.zerormi.NamedStreamPair; +import org.gridkit.zerormi.hub.LegacySpore; +import org.gridkit.zerormi.hub.MasterHub; import org.gridkit.zerormi.hub.RemotingHub; import org.gridkit.zerormi.hub.RemotingHub.SessionEventListener; import org.gridkit.zerormi.zlog.LogLevel; -import org.gridkit.zerormi.zlog.ZLogFactory; -import org.gridkit.zerormi.zlog.ZLogger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TunnellerJvmReplicator implements RemoteJmvReplicator { - - private static final long DEFAULT_CONN_TIMEOUT = 5000; - - private final StreamCopyService streamCopyService; - - private SshRemotingConfig rconfig = new SshRemotingConfig(); - private boolean initialized; - private boolean destroyed; - - private Session session; - private RemotingHub hub; - private TunnellerConnection control; - - private RemoteFileCache jarCache; - private String tunnellerJarPath; - - private String tunnelHost; - private int tunnelPort; - private long connectTimeoutMS = DEFAULT_CONN_TIMEOUT; - - private ZLogger logger; - - public TunnellerJvmReplicator(StreamCopyService streamCopyService) { - this.streamCopyService = streamCopyService; - } - - public TunnellerJvmReplicator(StreamCopyService streamCopyService, ZLogger logger) { - this(streamCopyService); - this.logger = logger; - } - - @Override - public synchronized void configure(Map nodeConfig) { - rconfig.configure(nodeConfig); - rconfig.validate(); - } - +import org.gridkit.zerormi.zlog.ZLogFactory; +import org.gridkit.zerormi.zlog.ZLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TunnellerJvmReplicator implements RemoteJmvReplicator { + + private static final long DEFAULT_CONN_TIMEOUT = 5000; + + private final StreamCopyService streamCopyService; + + private SshRemotingConfig rconfig = new SshRemotingConfig(); + private boolean initialized; + private boolean destroyed; + + private Session session; + private RemotingHub hub; + private TunnellerConnection control; + + private RemoteFileCache jarCache; + private String tunnellerJarPath; + + private String tunnelHost; + private int tunnelPort; + private long connectTimeoutMS = DEFAULT_CONN_TIMEOUT; + + private ZLogger logger; + + public TunnellerJvmReplicator(StreamCopyService streamCopyService) { + this.streamCopyService = streamCopyService; + } + + public TunnellerJvmReplicator(StreamCopyService streamCopyService, ZLogger logger) { + this(streamCopyService); + this.logger = logger; + } + + @Override + public synchronized void configure(Map nodeConfig) { + rconfig.configure(nodeConfig); + rconfig.validate(); + } + @Override public synchronized String getFingerPrint() { return rconfig.getFingerPrint(); @@ -300,25 +300,25 @@ private void startTunneler() throws JSchException, IOException { verifyJavaVersion(); ChannelExec exec = (ChannelExec) session.openChannel("exec"); - - String cmd = rconfig.getJavaExec() + " -Xms32m -Xmx32m -jar " + tunnellerJarPath; - exec.setCommand(cmd); - - // use std out for binary communication - InputStream cin = exec.getInputStream(); - OutputStream cout = exec.getOutputStream(); - // use std err for diagnostic output - OutputStream tunnel = new LoggerPrintStream(logger.get("console", LogLevel.WARN)); - streamCopyService.link(exec.getExtInputStream(), tunnel, false); - - // unfortunately Pty will merge out and err, so it should be disabled - exec.setPty(false); - exec.connect(); - - PrintStream diagLog = new LoggerPrintStream(logger.get("console", LogLevel.WARN)); - - try { - control = new TunnellerConnection(rconfig.getHost(), cin, cout, diagLog, connectTimeoutMS, TimeUnit.MILLISECONDS); + + String cmd = rconfig.getJavaExec() + " -Xms32m -Xmx32m -jar " + tunnellerJarPath; + exec.setCommand(cmd); + + // use std out for binary communication + InputStream cin = exec.getInputStream(); + OutputStream cout = exec.getOutputStream(); + // use std err for diagnostic output + OutputStream tunnel = new LoggerPrintStream(logger.get("console", LogLevel.WARN)); + streamCopyService.link(exec.getExtInputStream(), tunnel, false); + + // unfortunately Pty will merge out and err, so it should be disabled + exec.setPty(false); + exec.connect(); + + PrintStream diagLog = new LoggerPrintStream(logger.get("console", LogLevel.WARN)); + + try { + control = new TunnellerConnection(rconfig.getHost(), cin, cout, diagLog, connectTimeoutMS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { killAndDrop(exec); throw new IOException("Connection aborted due to thread interrupt"); @@ -500,54 +500,54 @@ public void consoleFlush() { // do nothing } - @Override - public FutureEx getExitCodeFuture() { - // FIXME getExitCodeFuture for remote process - return new FutureBox(); - } - - @Override - public void bindStdIn(InputStream is) { - if (is != null) { - streamCopyService.link(is, getOutputStream()); - } - else { - try { - getOutputStream().close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - @Override - public void bindStdOut(OutputStream os) { - if (os != null) { - streamCopyService.link(getInputStream(), os); - } - else { - try { - getInputStream().close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - } - - @Override - public void bindStdErr(OutputStream os) { - if (os != null) { - streamCopyService.link(getErrorStream(), os); - } - else { - try { - getErrorStream().close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } + @Override + public FutureEx getExitCodeFuture() { + // FIXME getExitCodeFuture for remote process + return new FutureBox(); + } + + @Override + public void bindStdIn(InputStream is) { + if (is != null) { + streamCopyService.link(is, getOutputStream()); + } + else { + try { + getOutputStream().close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void bindStdOut(OutputStream os) { + if (os != null) { + streamCopyService.link(getInputStream(), os); + } + else { + try { + getInputStream().close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + } + + @Override + public void bindStdErr(OutputStream os) { + if (os != null) { + streamCopyService.link(getErrorStream(), os); + } + else { + try { + getErrorStream().close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } @Override public void closed() { @@ -595,6 +595,11 @@ public void started(OutputStream stdIn, InputStream stdOut, InputStream stdErr) @Override public void finished(int exitCode) { this.exitCode.setData(exitCode); + try { + stdIn.write(("\nProcess exited with code "+exitCode+"\n").getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } } @Override diff --git a/vicluster-core/src/main/java/org/gridkit/nanocloud/telecontrol/ProcessSporeLauncher.java b/vicluster-core/src/main/java/org/gridkit/nanocloud/telecontrol/ProcessSporeLauncher.java index 2fa2483..fd25f2d 100644 --- a/vicluster-core/src/main/java/org/gridkit/nanocloud/telecontrol/ProcessSporeLauncher.java +++ b/vicluster-core/src/main/java/org/gridkit/nanocloud/telecontrol/ProcessSporeLauncher.java @@ -470,6 +470,7 @@ public void finished(int exitCode) { ProcessStreams ps = fget(procStreams); if (ps != null) { try { + ps.stdOut.write(("\nProcess exited with code "+exitCode+"\n").getBytes()); ps.eofOut.flushAndClose(); ps.eofErr.flushAndClose(); if (ps.stdOut.getOutput() == null) {