1 /* 2 * Copyright (c) 2015, Red Hat Inc 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.IOException; 25 import java.net.InetAddress; 26 import java.net.InetSocketAddress; 27 import java.net.MalformedURLException; 28 import java.net.Socket; 29 import java.net.SocketAddress; 30 import java.net.UnknownHostException; 31 import java.util.HashMap; 32 import java.util.Map; 33 import java.util.concurrent.CountDownLatch; 34 import java.util.concurrent.TimeUnit; 35 36 import javax.management.remote.JMXConnector; 37 import javax.management.remote.JMXConnectorFactory; 38 import javax.management.remote.JMXServiceURL; 39 import javax.management.remote.rmi.RMIConnectorServer; 40 import javax.net.ssl.SSLSocket; 41 import javax.net.ssl.SSLSocketFactory; 42 import javax.rmi.ssl.SslRMIClientSocketFactory; 43 44 /** 45 * Tests client connections to the JDK's built-in JMX agent server on the given 46 * ports/interface combinations. 47 * 48 * @see JMXInterfaceBindingTest 49 * 50 * @author Severin Gehwolf <sgehwolf@redhat.com> 51 * 52 * Usage: 53 * 54 * SSL: 55 * java -Dcom.sun.management.jmxremote.ssl.need.client.auth=true \ 56 * -Dcom.sun.management.jmxremote.host=127.0.0.1 \ 57 * -Dcom.sun.management.jmxremote.port=9111 \ 58 * -Dcom.sun.management.jmxremote.rmi.port=9112 \ 59 * -Dcom.sun.management.jmxremote.authenticate=false \ 60 * -Dcom.sun.management.jmxremote.ssl=true \ 61 * -Dcom.sun.management.jmxremote.registry.ssl=true 62 * -Djavax.net.ssl.keyStore=... \ 63 * -Djavax.net.ssl.keyStorePassword=... \ 64 * JMXAgentInterfaceBinding 127.0.0.1 9111 9112 true 65 * 66 * Non-SSL: 67 * java -Dcom.sun.management.jmxremote.host=127.0.0.1 \ 68 * -Dcom.sun.management.jmxremote.port=9111 \ 69 * -Dcom.sun.management.jmxremote.rmi.port=9112 \ 70 * -Dcom.sun.management.jmxremote.authenticate=false \ 71 * -Dcom.sun.management.jmxremote.ssl=false \ 72 * JMXAgentInterfaceBinding 127.0.0.1 9111 9112 false 73 * 74 */ 75 public class JMXAgentInterfaceBinding { 76 77 private final MainThread mainThread; 78 79 public JMXAgentInterfaceBinding(InetAddress bindAddress, 80 int jmxPort, 81 int rmiPort, 82 boolean useSSL) { 83 this.mainThread = new MainThread(bindAddress, jmxPort, rmiPort, useSSL); 84 } 85 86 public void startEndpoint() { 87 mainThread.start(); 88 try { 89 mainThread.join(); 90 } catch (InterruptedException e) { 91 throw new RuntimeException("Test failed", e); 92 } 93 if (mainThread.isFailed()) { 94 mainThread.rethrowException(); 95 } 96 } 97 98 public static void main(String[] args) { 99 if (args.length != 4) { 100 throw new RuntimeException( 101 "Test failed. usage: java JMXInterfaceBindingTest <BIND_ADDRESS> <JMX_PORT> <RMI_PORT> {true|false}"); 102 } 103 int jmxPort = parsePortFromString(args[1]); 104 int rmiPort = parsePortFromString(args[2]); 105 boolean useSSL = Boolean.parseBoolean(args[3]); 106 String strBindAddr = args[0]; 107 System.out.println( 108 "DEBUG: Running test for triplet (hostname,jmxPort,rmiPort) = (" 109 + strBindAddr + "," + jmxPort + "," + rmiPort + "), useSSL = " + useSSL); 110 InetAddress bindAddress; 111 try { 112 bindAddress = InetAddress.getByName(args[0]); 113 } catch (UnknownHostException e) { 114 throw new RuntimeException("Test failed. Unknown ip: " + args[0]); 115 } 116 JMXAgentInterfaceBinding test = new JMXAgentInterfaceBinding(bindAddress, 117 jmxPort, rmiPort, useSSL); 118 test.startEndpoint(); // Expect for main test to terminate process 119 } 120 121 private static int parsePortFromString(String port) { 122 try { 123 return Integer.parseInt(port); 124 } catch (NumberFormatException e) { 125 throw new RuntimeException( 126 "Invalid port specified. Not an integer! Value was: " 127 + port); 128 } 129 } 130 131 private static class JMXConnectorThread extends Thread { 132 133 private final InetAddress addr; 134 private final int jmxPort; 135 private final int rmiPort; 136 private final boolean useSSL; 137 private final CountDownLatch latch; 138 private boolean failed; 139 private boolean jmxConnectWorked; 140 private boolean rmiConnectWorked; 141 142 private JMXConnectorThread(InetAddress addr, 143 int jmxPort, 144 int rmiPort, 145 boolean useSSL, 146 CountDownLatch latch) { 147 this.addr = addr; 148 this.jmxPort = jmxPort; 149 this.rmiPort = rmiPort; 150 this.latch = latch; 151 this.useSSL = useSSL; 152 } 153 154 @Override 155 public void run() { 156 try { 157 connect(); 158 } catch (IOException e) { 159 failed = true; 160 } 161 } 162 163 private void connect() throws IOException { 164 System.out.println( 165 "JMXConnectorThread: Attempting JMX connection on: " 166 + addr.getHostAddress() + " on port " + jmxPort); 167 JMXServiceURL url; 168 try { 169 url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" 170 + addr.getHostAddress() + ":" + jmxPort + "/jmxrmi"); 171 } catch (MalformedURLException e) { 172 throw new RuntimeException("Test failed.", e); 173 } 174 Map<String, Object> env = new HashMap<>(); 175 if (useSSL) { 176 SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); 177 env.put("com.sun.jndi.rmi.factory.socket", csf); 178 env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); 179 } 180 // connect and immediately close 181 JMXConnector c = JMXConnectorFactory.connect(url, env); 182 c.close(); 183 System.out.println("JMXConnectorThread: connection to JMX worked"); 184 jmxConnectWorked = true; 185 checkRmiSocket(); 186 latch.countDown(); // signal we are done. 187 } 188 189 private void checkRmiSocket() throws IOException { 190 Socket rmiConnection; 191 if (useSSL) { 192 rmiConnection = SSLSocketFactory.getDefault().createSocket(); 193 } else { 194 rmiConnection = new Socket(); 195 } 196 SocketAddress target = new InetSocketAddress(addr, rmiPort); 197 rmiConnection.connect(target); 198 if (useSSL) { 199 ((SSLSocket)rmiConnection).startHandshake(); 200 } 201 System.out.println( 202 "JMXConnectorThread: connection to rmi socket worked host/port = " 203 + addr.getHostAddress() + "/" + rmiPort); 204 rmiConnectWorked = true; 205 // Closing the channel without sending any data will cause an 206 // java.io.EOFException on the server endpoint. We don't care about this 207 // though, since we only want to test if we can connect. 208 rmiConnection.close(); 209 } 210 211 public boolean isFailed() { 212 return failed; 213 } 214 215 public boolean jmxConnectionWorked() { 216 return jmxConnectWorked; 217 } 218 219 public boolean rmiConnectionWorked() { 220 return rmiConnectWorked; 221 } 222 } 223 224 private static class MainThread extends Thread { 225 226 private static final int WAIT_FOR_JMX_AGENT_TIMEOUT_MS = 500; 227 private final InetAddress bindAddress; 228 private final int jmxPort; 229 private final int rmiPort; 230 private final boolean useSSL; 231 private boolean terminated = false; 232 private boolean jmxAgentStarted = false; 233 private Exception excptn; 234 235 private MainThread(InetAddress bindAddress, int jmxPort, int rmiPort, boolean useSSL) { 236 this.bindAddress = bindAddress; 237 this.jmxPort = jmxPort; 238 this.rmiPort = rmiPort; 239 this.useSSL = useSSL; 240 } 241 242 @Override 243 public void run() { 244 try { 245 waitUntilReadyForConnections(); 246 // Do nothing, but wait for termination. 247 try { 248 while (!terminated) { 249 Thread.sleep(100); 250 } 251 } catch (InterruptedException e) { // ignore 252 } 253 System.out.println("MainThread: Thread stopped."); 254 } catch (Exception e) { 255 this.excptn = e; 256 } 257 } 258 259 private void waitUntilReadyForConnections() { 260 CountDownLatch latch = new CountDownLatch(1); 261 JMXConnectorThread connectionTester = new JMXConnectorThread( 262 bindAddress, jmxPort, rmiPort, useSSL, latch); 263 connectionTester.start(); 264 boolean expired = false; 265 try { 266 expired = !latch.await(WAIT_FOR_JMX_AGENT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 267 System.out.println( 268 "MainThread: Finished waiting for JMX agent to become available: expired == " 269 + expired); 270 jmxAgentStarted = !expired; 271 } catch (InterruptedException e) { 272 throw new RuntimeException("Test failed", e); 273 } 274 if (!jmxAgentStarted) { 275 throw new RuntimeException( 276 "Test failed. JMX server agents not becoming available."); 277 } 278 if (connectionTester.isFailed() 279 || !connectionTester.jmxConnectionWorked() 280 || !connectionTester.rmiConnectionWorked()) { 281 throw new RuntimeException( 282 "Test failed. JMX agent does not seem ready. See log output for details."); 283 } 284 // The main test expects this exact message being printed 285 System.out.println("MainThread: Ready for connections"); 286 } 287 288 private boolean isFailed() { 289 return excptn != null; 290 } 291 292 private void rethrowException() throws RuntimeException { 293 throw new RuntimeException(excptn); 294 } 295 } 296 297 }