blob: 4647f280fef13e3064b7b51b03b6da80551494b6 [file] [log] [blame]
/*
* 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 String 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(String 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 + " on port " + jmxPort);
JMXServiceURL url;
try {
url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://"
+ addr + ":" + 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 + "/" + 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 String addr;
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.addr = wrapAddress(bindAddress.getHostAddress());
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(
addr, 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);
}
}
/**
* Will wrap IPv6 address in '[]'
*/
static String wrapAddress(String address) {
if (address.contains(":")) {
return "[" + address + "]";
}
return address;
}
}