提交 919afc52 编写于 作者: S sgehwolf

6425769: Allow specifying an address to bind JMX remote connector

Reviewed-by: jbachorik, dfuchs
上级 f9f1dd5a
......@@ -30,9 +30,12 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
......@@ -40,6 +43,7 @@ import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.RMISocketFactory;
import java.rmi.server.RemoteObject;
import java.rmi.server.UnicastRemoteObject;
import java.security.KeyStore;
......@@ -60,6 +64,8 @@ import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import javax.rmi.ssl.SslRMIServerSocketFactory;
......@@ -107,6 +113,8 @@ public final class ConnectorBootstrap {
public static final String PORT =
"com.sun.management.jmxremote.port";
public static final String HOST =
"com.sun.management.jmxremote.host";
public static final String RMI_PORT =
"com.sun.management.jmxremote.rmi.port";
public static final String CONFIG_FILE_NAME =
......@@ -424,10 +432,14 @@ public final class ConnectorBootstrap {
checkAccessFile(accessFileName);
}
final String bindAddress =
props.getProperty(PropertyNames.HOST);
if (log.debugOn()) {
log.debug("startRemoteConnectorServer",
Agent.getText("jmxremote.ConnectorBootstrap.starting") +
"\n\t" + PropertyNames.PORT + "=" + port +
(bindAddress == null ? "" : "\n\t" + PropertyNames.HOST + "=" + bindAddress) +
"\n\t" + PropertyNames.RMI_PORT + "=" + rmiPort +
"\n\t" + PropertyNames.USE_SSL + "=" + useSsl +
"\n\t" + PropertyNames.USE_REGISTRY_SSL + "=" + useRegistrySsl +
......@@ -458,7 +470,7 @@ public final class ConnectorBootstrap {
sslConfigFileName, enabledCipherSuitesList,
enabledProtocolsList, sslNeedClientAuth,
useAuthentication, loginConfigName,
passwordFileName, accessFileName);
passwordFileName, accessFileName, bindAddress);
cs = data.jmxConnectorServer;
url = data.jmxRemoteURL;
log.config("startRemoteConnectorServer",
......@@ -628,12 +640,13 @@ public final class ConnectorBootstrap {
String sslConfigFileName,
String[] enabledCipherSuites,
String[] enabledProtocols,
boolean sslNeedClientAuth) {
boolean sslNeedClientAuth,
String bindAddress) {
if (sslConfigFileName == null) {
return new SslRMIServerSocketFactory(
return new HostAwareSslSocketFactory(
enabledCipherSuites,
enabledProtocols,
sslNeedClientAuth);
sslNeedClientAuth, bindAddress);
} else {
checkRestrictedFile(sslConfigFileName);
try {
......@@ -687,11 +700,11 @@ public final class ConnectorBootstrap {
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return new SslRMIServerSocketFactory(
return new HostAwareSslSocketFactory(
ctx,
enabledCipherSuites,
enabledProtocols,
sslNeedClientAuth);
sslNeedClientAuth, bindAddress);
} catch (Exception e) {
throw new AgentConfigurationError(AGENT_EXCEPTION, e, e.toString());
}
......@@ -711,7 +724,8 @@ public final class ConnectorBootstrap {
boolean useAuthentication,
String loginConfigName,
String passwordFileName,
String accessFileName)
String accessFileName,
String bindAddress)
throws IOException, MalformedURLException {
/* Make sure we use non-guessable RMI object IDs. Otherwise
......@@ -719,7 +733,7 @@ public final class ConnectorBootstrap {
* IDs. */
System.setProperty("java.rmi.server.randomIDs", "true");
JMXServiceURL url = new JMXServiceURL("rmi", null, rmiPort);
JMXServiceURL url = new JMXServiceURL("rmi", bindAddress, rmiPort);
Map<String, Object> env = new HashMap<>();
......@@ -727,6 +741,8 @@ public final class ConnectorBootstrap {
env.put(RMIExporter.EXPORTER_ATTRIBUTE, exporter);
boolean useSocketFactory = bindAddress != null && !useSsl;
if (useAuthentication) {
if (loginConfigName != null) {
env.put("jmx.remote.x.login.config", loginConfigName);
......@@ -751,7 +767,7 @@ public final class ConnectorBootstrap {
csf = new SslRMIClientSocketFactory();
ssf = createSslRMIServerSocketFactory(
sslConfigFileName, enabledCipherSuites,
enabledProtocols, sslNeedClientAuth);
enabledProtocols, sslNeedClientAuth, bindAddress);
}
if (useSsl) {
......@@ -761,6 +777,12 @@ public final class ConnectorBootstrap {
ssf);
}
if (useSocketFactory) {
ssf = new HostAwareSocketFactory(bindAddress);
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE,
ssf);
}
JMXConnectorServer connServer = null;
try {
connServer =
......@@ -780,6 +802,10 @@ public final class ConnectorBootstrap {
registry =
new SingleEntryRegistry(port, csf, ssf,
"jmxrmi", exporter.firstExported);
} else if (useSocketFactory) {
registry =
new SingleEntryRegistry(port, csf, ssf,
"jmxrmi", exporter.firstExported);
} else {
registry =
new SingleEntryRegistry(port,
......@@ -813,4 +839,172 @@ public final class ConnectorBootstrap {
private static final ClassLogger log =
new ClassLogger(ConnectorBootstrap.class.getPackage().getName(),
"ConnectorBootstrap");
private static class HostAwareSocketFactory implements RMIServerSocketFactory {
private final String bindAddress;
private HostAwareSocketFactory(String bindAddress) {
this.bindAddress = bindAddress;
}
@Override
public ServerSocket createServerSocket(int port) throws IOException {
if (bindAddress == null) {
return new ServerSocket(port);
} else {
try {
InetAddress addr = InetAddress.getByName(bindAddress);
return new ServerSocket(port, 0, addr);
} catch (UnknownHostException e) {
return new ServerSocket(port);
}
}
}
}
private static class HostAwareSslSocketFactory extends SslRMIServerSocketFactory {
private final String bindAddress;
private final String[] enabledCipherSuites;
private final String[] enabledProtocols;
private final boolean needClientAuth;
private final SSLContext context;
private HostAwareSslSocketFactory(String[] enabledCipherSuites,
String[] enabledProtocols,
boolean sslNeedClientAuth,
String bindAddress) throws IllegalArgumentException {
this(null, enabledCipherSuites, enabledProtocols, sslNeedClientAuth, bindAddress);
}
private HostAwareSslSocketFactory(SSLContext ctx,
String[] enabledCipherSuites,
String[] enabledProtocols,
boolean sslNeedClientAuth,
String bindAddress) throws IllegalArgumentException {
this.context = ctx;
this.bindAddress = bindAddress;
this.enabledProtocols = enabledProtocols;
this.enabledCipherSuites = enabledCipherSuites;
this.needClientAuth = sslNeedClientAuth;
checkValues(ctx, enabledCipherSuites, enabledProtocols);
}
@Override
public ServerSocket createServerSocket(int port) throws IOException {
if (bindAddress != null) {
try {
InetAddress addr = InetAddress.getByName(bindAddress);
return new SslServerSocket(port, 0, addr, context,
enabledCipherSuites, enabledProtocols, needClientAuth);
} catch (UnknownHostException e) {
return new SslServerSocket(port, context,
enabledCipherSuites, enabledProtocols, needClientAuth);
}
} else {
return new SslServerSocket(port, context,
enabledCipherSuites, enabledProtocols, needClientAuth);
}
}
private static void checkValues(SSLContext context,
String[] enabledCipherSuites,
String[] enabledProtocols) throws IllegalArgumentException {
// Force the initialization of the default at construction time,
// rather than delaying it to the first time createServerSocket()
// is called.
//
final SSLSocketFactory sslSocketFactory =
context == null ?
(SSLSocketFactory)SSLSocketFactory.getDefault() : context.getSocketFactory();
SSLSocket sslSocket = null;
if (enabledCipherSuites != null || enabledProtocols != null) {
try {
sslSocket = (SSLSocket) sslSocketFactory.createSocket();
} catch (Exception e) {
final String msg = "Unable to check if the cipher suites " +
"and protocols to enable are supported";
throw (IllegalArgumentException)
new IllegalArgumentException(msg).initCause(e);
}
}
// Check if all the cipher suites and protocol versions to enable
// are supported by the underlying SSL/TLS implementation and if
// true create lists from arrays.
//
if (enabledCipherSuites != null) {
sslSocket.setEnabledCipherSuites(enabledCipherSuites);
}
if (enabledProtocols != null) {
sslSocket.setEnabledProtocols(enabledProtocols);
}
}
}
private static class SslServerSocket extends ServerSocket {
private static SSLSocketFactory defaultSSLSocketFactory;
private final String[] enabledCipherSuites;
private final String[] enabledProtocols;
private final boolean needClientAuth;
private final SSLContext context;
private SslServerSocket(int port,
SSLContext ctx,
String[] enabledCipherSuites,
String[] enabledProtocols,
boolean needClientAuth) throws IOException {
super(port);
this.enabledProtocols = enabledProtocols;
this.enabledCipherSuites = enabledCipherSuites;
this.needClientAuth = needClientAuth;
this.context = ctx;
}
private SslServerSocket(int port,
int backlog,
InetAddress bindAddr,
SSLContext ctx,
String[] enabledCipherSuites,
String[] enabledProtocols,
boolean needClientAuth) throws IOException {
super(port, backlog, bindAddr);
this.enabledProtocols = enabledProtocols;
this.enabledCipherSuites = enabledCipherSuites;
this.needClientAuth = needClientAuth;
this.context = ctx;
}
@Override
public Socket accept() throws IOException {
final SSLSocketFactory sslSocketFactory =
context == null ?
getDefaultSSLSocketFactory() : context.getSocketFactory();
Socket socket = super.accept();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
socket, socket.getInetAddress().getHostName(),
socket.getPort(), true);
sslSocket.setUseClientMode(false);
if (enabledCipherSuites != null) {
sslSocket.setEnabledCipherSuites(enabledCipherSuites);
}
if (enabledProtocols != null) {
sslSocket.setEnabledProtocols(enabledProtocols);
}
sslSocket.setNeedClientAuth(needClientAuth);
return sslSocket;
}
private static synchronized SSLSocketFactory getDefaultSSLSocketFactory() {
if (defaultSSLSocketFactory == null) {
defaultSSLSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
return defaultSSLSocketFactory;
} else {
return defaultSSLSocketFactory;
}
}
}
}
......@@ -316,3 +316,16 @@
# For a non-default password file location use the following line
# com.sun.management.jmxremote.access.file=filepath
#
# ################ Management agent listen interface #########################
#
# com.sun.management.jmxremote.host=<host-or-interface-name>
# Specifies the local interface on which the JMX RMI agent will bind.
# This is useful when running on machines which have several
# interfaces defined. It makes it possible to listen to a specific
# subnet accessible through that interface.
#
# The format of the value for that property is any string accepted
# by java.net.InetAddress.getByName(String).
#
/*
* Copyright (c) 2015, Red Hat Inc
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.rmi.ssl.SslRMIClientSocketFactory;
/**
* Tests client connections to the JDK's built-in JMX agent server on the given
* ports/interface combinations.
*
* @see JMXInterfaceBindingTest
*
* @author Severin Gehwolf <sgehwolf@redhat.com>
*
* Usage:
*
* SSL:
* java -Dcom.sun.management.jmxremote.ssl.need.client.auth=true \
* -Dcom.sun.management.jmxremote.host=127.0.0.1 \
* -Dcom.sun.management.jmxremote.port=9111 \
* -Dcom.sun.management.jmxremote.rmi.port=9112 \
* -Dcom.sun.management.jmxremote.authenticate=false \
* -Dcom.sun.management.jmxremote.ssl=true \
* -Dcom.sun.management.jmxremote.registry.ssl=true
* -Djavax.net.ssl.keyStore=... \
* -Djavax.net.ssl.keyStorePassword=... \
* JMXAgentInterfaceBinding 127.0.0.1 9111 9112 true
*
* Non-SSL:
* java -Dcom.sun.management.jmxremote.host=127.0.0.1 \
* -Dcom.sun.management.jmxremote.port=9111 \
* -Dcom.sun.management.jmxremote.rmi.port=9112 \
* -Dcom.sun.management.jmxremote.authenticate=false \
* -Dcom.sun.management.jmxremote.ssl=false \
* JMXAgentInterfaceBinding 127.0.0.1 9111 9112 false
*
*/
public class JMXAgentInterfaceBinding {
private final MainThread mainThread;
public JMXAgentInterfaceBinding(InetAddress bindAddress,
int jmxPort,
int rmiPort,
boolean useSSL) {
this.mainThread = new MainThread(bindAddress, jmxPort, rmiPort, useSSL);
}
public void startEndpoint() {
mainThread.start();
try {
mainThread.join();
} catch (InterruptedException e) {
throw new RuntimeException("Test failed", e);
}
if (mainThread.isFailed()) {
mainThread.rethrowException();
}
}
public static void main(String[] args) {
if (args.length != 4) {
throw new RuntimeException(
"Test failed. usage: java JMXInterfaceBindingTest <BIND_ADDRESS> <JMX_PORT> <RMI_PORT> {true|false}");
}
int jmxPort = parsePortFromString(args[1]);
int rmiPort = parsePortFromString(args[2]);
boolean useSSL = Boolean.parseBoolean(args[3]);
String strBindAddr = args[0];
System.out.println(
"DEBUG: Running test for triplet (hostname,jmxPort,rmiPort) = ("
+ strBindAddr + "," + jmxPort + "," + rmiPort + "), useSSL = " + useSSL);
InetAddress bindAddress;
try {
bindAddress = InetAddress.getByName(args[0]);
} catch (UnknownHostException e) {
throw new RuntimeException("Test failed. Unknown ip: " + args[0]);
}
JMXAgentInterfaceBinding test = new JMXAgentInterfaceBinding(bindAddress,
jmxPort, rmiPort, useSSL);
test.startEndpoint(); // Expect for main test to terminate process
}
private static int parsePortFromString(String port) {
try {
return Integer.parseInt(port);
} catch (NumberFormatException e) {
throw new RuntimeException(
"Invalid port specified. Not an integer! Value was: "
+ port);
}
}
private static class JMXConnectorThread extends Thread {
private final InetAddress addr;
private final int jmxPort;
private final int rmiPort;
private final boolean useSSL;
private final CountDownLatch latch;
private boolean failed;
private boolean jmxConnectWorked;
private boolean rmiConnectWorked;
private JMXConnectorThread(InetAddress addr,
int jmxPort,
int rmiPort,
boolean useSSL,
CountDownLatch latch) {
this.addr = addr;
this.jmxPort = jmxPort;
this.rmiPort = rmiPort;
this.latch = latch;
this.useSSL = useSSL;
}
@Override
public void run() {
try {
connect();
} catch (IOException e) {
failed = true;
}
}
private void connect() throws IOException {
System.out.println(
"JMXConnectorThread: Attempting JMX connection on: "
+ addr.getHostAddress() + " on port " + jmxPort);
JMXServiceURL url;
try {
url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://"
+ addr.getHostAddress() + ":" + jmxPort + "/jmxrmi");
} catch (MalformedURLException e) {
throw new RuntimeException("Test failed.", e);
}
Map<String, Object> env = new HashMap<>();
if (useSSL) {
SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
env.put("com.sun.jndi.rmi.factory.socket", csf);
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
}
// connect and immediately close
JMXConnector c = JMXConnectorFactory.connect(url, env);
c.close();
System.out.println("JMXConnectorThread: connection to JMX worked");
jmxConnectWorked = true;
checkRmiSocket();
latch.countDown(); // signal we are done.
}
private void checkRmiSocket() throws IOException {
Socket rmiConnection;
if (useSSL) {
rmiConnection = SSLSocketFactory.getDefault().createSocket();
} else {
rmiConnection = new Socket();
}
SocketAddress target = new InetSocketAddress(addr, rmiPort);
rmiConnection.connect(target);
if (useSSL) {
((SSLSocket)rmiConnection).startHandshake();
}
System.out.println(
"JMXConnectorThread: connection to rmi socket worked host/port = "
+ addr.getHostAddress() + "/" + rmiPort);
rmiConnectWorked = true;
// Closing the channel without sending any data will cause an
// java.io.EOFException on the server endpoint. We don't care about this
// though, since we only want to test if we can connect.
rmiConnection.close();
}
public boolean isFailed() {
return failed;
}
public boolean jmxConnectionWorked() {
return jmxConnectWorked;
}
public boolean rmiConnectionWorked() {
return rmiConnectWorked;
}
}
private static class MainThread extends Thread {
private static final int WAIT_FOR_JMX_AGENT_TIMEOUT_MS = 500;
private final InetAddress bindAddress;
private final int jmxPort;
private final int rmiPort;
private final boolean useSSL;
private boolean terminated = false;
private boolean jmxAgentStarted = false;
private Exception excptn;
private MainThread(InetAddress bindAddress, int jmxPort, int rmiPort, boolean useSSL) {
this.bindAddress = bindAddress;
this.jmxPort = jmxPort;
this.rmiPort = rmiPort;
this.useSSL = useSSL;
}
@Override
public void run() {
try {
waitUntilReadyForConnections();
// Do nothing, but wait for termination.
try {
while (!terminated) {
Thread.sleep(100);
}
} catch (InterruptedException e) { // ignore
}
System.out.println("MainThread: Thread stopped.");
} catch (Exception e) {
this.excptn = e;
}
}
private void waitUntilReadyForConnections() {
CountDownLatch latch = new CountDownLatch(1);
JMXConnectorThread connectionTester = new JMXConnectorThread(
bindAddress, jmxPort, rmiPort, useSSL, latch);
connectionTester.start();
boolean expired = false;
try {
expired = !latch.await(WAIT_FOR_JMX_AGENT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
System.out.println(
"MainThread: Finished waiting for JMX agent to become available: expired == "
+ expired);
jmxAgentStarted = !expired;
} catch (InterruptedException e) {
throw new RuntimeException("Test failed", e);
}
if (!jmxAgentStarted) {
throw new RuntimeException(
"Test failed. JMX server agents not becoming available.");
}
if (connectionTester.isFailed()
|| !connectionTester.jmxConnectionWorked()
|| !connectionTester.rmiConnectionWorked()) {
throw new RuntimeException(
"Test failed. JMX agent does not seem ready. See log output for details.");
}
// The main test expects this exact message being printed
System.out.println("MainThread: Ready for connections");
}
private boolean isFailed() {
return excptn != null;
}
private void rethrowException() throws RuntimeException {
throw new RuntimeException(excptn);
}
}
}
/*
* Copyright (c) 2015, Red Hat Inc
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.File;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import jdk.testlibrary.ProcessTools;
/**
* NOTE:
* This test requires at least a setup similar to the following in
* /etc/hosts file (or the windows equivalent). I.e. it expects it to
* be multi-homed and not both being the loop-back interface.
* For example:
* ----->8-------- /etc/hosts ----------->8---
* 127.0.0.1 localhost
* 192.168.0.1 localhost
* ----->8-------- /etc/hosts ----------->8---
*
* @test
* @bug 6425769
* @summary Test JMX agent host address binding. Same ports but different
* interfaces to bind to (using plain sockets and SSL sockets).
*
* @modules java.management/sun.management
* java.management/sun.management.jmxremote
* @library /lib/testlibrary
* @build jdk.testlibrary.* JMXAgentInterfaceBinding
* @run main/timeout=5 JMXInterfaceBindingTest
*/
public class JMXInterfaceBindingTest {
public static final int COMMUNICATION_ERROR_EXIT_VAL = 1;
public static final int STOP_PROCESS_EXIT_VAL = 137;
public static final int JMX_PORT = 9111;
public static final int RMI_PORT = 9112;
public static final String READY_MSG = "MainThread: Ready for connections";
public static final String TEST_CLASS = JMXAgentInterfaceBinding.class.getSimpleName();
public static final String KEYSTORE_LOC = System.getProperty("test.src", ".") +
File.separator +
"ssl" +
File.separator +
"keystore";
public static final String TRUSTSTORE_LOC = System.getProperty("test.src", ".") +
File.separator +
"ssl" +
File.separator +
"truststore";
public static final String TEST_CLASSPATH = System.getProperty("test.classes", ".");
public void run(InetAddress[] addrs) {
System.out.println("DEBUG: Running tests with plain sockets.");
runTests(addrs, false);
System.out.println("DEBUG: Running tests with SSL sockets.");
runTests(addrs, true);
}
private void runTests(InetAddress[] addrs, boolean useSSL) {
TestProcessThread[] jvms = new TestProcessThread[addrs.length];
for (int i = 0; i < addrs.length; i++) {
System.out.println();
String msg = String.format("DEBUG: Launching java tester for triplet (HOSTNAME,JMX_PORT,RMI_PORT) == (%s,%d,%d)",
addrs[i].getHostAddress(),
JMX_PORT,
RMI_PORT);
System.out.println(msg);
jvms[i] = runJMXBindingTest(addrs[i], useSSL);
jvms[i].start();
System.out.println("DEBUG: Started " + (i + 1) + " Process(es).");
}
int failedProcesses = 0;
for (TestProcessThread pt: jvms) {
try {
pt.stopProcess();
pt.join();
} catch (InterruptedException e) {
System.err.println("Failed to stop process: " + pt.getName());
throw new RuntimeException("Test failed", e);
}
int exitValue = pt.getExitValue();
// If there is a communication error (the case we care about)
// we get a exit code of 1
if (exitValue == COMMUNICATION_ERROR_EXIT_VAL) {
// Failure case since the java processes should still be
// running.
System.err.println("Test FAILURE on " + pt.getName());
failedProcesses++;
} else if (exitValue == STOP_PROCESS_EXIT_VAL) {
System.out.println("DEBUG: OK. Spawned java process terminated with expected exit code of " + STOP_PROCESS_EXIT_VAL);
} else {
System.err.println("Test FAILURE on " + pt.getName() + " reason: Unexpected exit code => " + exitValue);
failedProcesses++;
}
}
if (failedProcesses > 0) {
throw new RuntimeException("Test FAILED. " + failedProcesses + " out of " + addrs.length + " process(es) failed to start the JMX agent.");
}
}
private TestProcessThread runJMXBindingTest(InetAddress a, boolean useSSL) {
List<String> args = new ArrayList<>();
args.add("-classpath");
args.add(TEST_CLASSPATH);
args.add("-Dcom.sun.management.jmxremote.host=" + a.getHostAddress());
args.add("-Dcom.sun.management.jmxremote.port=" + JMX_PORT);
args.add("-Dcom.sun.management.jmxremote.rmi.port=" + RMI_PORT);
args.add("-Dcom.sun.management.jmxremote.authenticate=false");
args.add("-Dcom.sun.management.jmxremote.ssl=" + Boolean.toString(useSSL));
if (useSSL) {
args.add("-Dcom.sun.management.jmxremote.registry.ssl=true");
args.add("-Djavax.net.ssl.keyStore=" + KEYSTORE_LOC);
args.add("-Djavax.net.ssl.trustStore=" + TRUSTSTORE_LOC);
args.add("-Djavax.net.ssl.keyStorePassword=password");
args.add("-Djavax.net.ssl.trustStorePassword=trustword");
}
args.add(TEST_CLASS);
args.add(a.getHostAddress());
args.add(Integer.toString(JMX_PORT));
args.add(Integer.toString(RMI_PORT));
args.add(Boolean.toString(useSSL));
try {
ProcessBuilder builder = ProcessTools.createJavaProcessBuilder(args.toArray(new String[] {}));
System.out.println(ProcessTools.getCommandLine(builder));
TestProcessThread jvm = new TestProcessThread("JMX-Tester-" + a.getHostAddress(), JMXInterfaceBindingTest::isJMXAgentResponseAvailable, builder);
return jvm;
} catch (Exception e) {
throw new RuntimeException("Test failed", e);
}
}
private static boolean isJMXAgentResponseAvailable(String line) {
if (line.equals(READY_MSG)) {
System.out.println("DEBUG: Found expected READY_MSG.");
return true;
} else if (line.startsWith("Error:")) {
// Allow for a JVM process that exits with
// "Error: JMX connector server communication error: ..."
// to continue as well since we handle that case elsewhere.
// This has the effect that the test does not timeout and
// fails with an exception in the test.
System.err.println("PROBLEM: JMX agent of target JVM did not start as it should.");
return true;
} else {
return false;
}
}
public static void main(String[] args) {
InetAddress[] addrs = getAddressesForLocalHost();
if (addrs.length < 2) {
System.out.println("Ignoring manual test since no more than one IPs are configured for 'localhost'");
System.exit(0);
}
JMXInterfaceBindingTest test = new JMXInterfaceBindingTest();
test.run(addrs);
System.out.println("All tests PASSED.");
}
private static InetAddress[] getAddressesForLocalHost() {
InetAddress[] addrs;
try {
addrs = InetAddress.getAllByName("localhost");
} catch (UnknownHostException e) {
throw new RuntimeException("Test failed", e);
}
return addrs;
}
private static class TestProcessThread extends Thread {
private final Predicate<String> predicate;
private final ProcessBuilder pb;
private final CountDownLatch latch;
private Process process;
public TestProcessThread(String threadName, Predicate<String> predicate, ProcessBuilder pb) {
super(threadName);
this.predicate = predicate;
this.pb = pb;
this.latch = new CountDownLatch(1);
}
@Override
public void run() {
try {
process = ProcessTools.startProcess(getName(), pb, predicate, 10, TimeUnit.SECONDS);
latch.countDown();
process.waitFor();
} catch (Exception e) {
throw new RuntimeException("Test failed", e);
}
}
public void stopProcess() {
try {
latch.await();
} catch (InterruptedException e1) {
throw new RuntimeException("Test failed", e1);
}
if (process != null) {
process.destroyForcibly();
try {
process.waitFor();
} catch (InterruptedException e) {
throw new RuntimeException("Test failed", e);
}
}
}
public int getExitValue() {
return process.exitValue();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册