| /* |
| * 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; |
| } |
| } |