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.File;
  25 import java.net.InetAddress;
  26 import java.net.UnknownHostException;
  27 import java.util.ArrayList;
  28 import java.util.List;
  29 import java.util.concurrent.CountDownLatch;
  30 import java.util.concurrent.TimeUnit;
  31 import java.util.function.Predicate;
  32 
  33 import jdk.testlibrary.ProcessTools;
  34 
  35 /**
  36  * NOTE:
  37  *    This test requires at least a setup similar to the following in
  38  *    /etc/hosts file (or the windows equivalent). I.e. it expects it to
  39  *    be multi-homed and not both being the loop-back interface.
  40  *    For example:
  41  *    ----->8-------- /etc/hosts ----------->8---
  42  *    127.0.0.1   localhost
  43  *    192.168.0.1 localhost
  44  *    ----->8-------- /etc/hosts ----------->8---
  45  *
  46  * @test
  47  * @bug     6425769
  48  * @summary Test JMX agent host address binding. Same ports but different
  49  *          interfaces to bind to (using plain sockets and SSL sockets).
  50  *
  51  * @modules java.management/sun.management
  52  *          java.management/sun.management.jmxremote
  53  * @library /lib/testlibrary
  54  * @build jdk.testlibrary.* JMXAgentInterfaceBinding
  55  * @run main/timeout=5 JMXInterfaceBindingTest
  56  */
  57 public class JMXInterfaceBindingTest {
  58 
  59     public static final int COMMUNICATION_ERROR_EXIT_VAL = 1;
  60     public static final int STOP_PROCESS_EXIT_VAL = 137;
  61     public static final int JMX_PORT = 9111;
  62     public static final int RMI_PORT = 9112;
  63     public static final String READY_MSG = "MainThread: Ready for connections";
  64     public static final String TEST_CLASS = JMXAgentInterfaceBinding.class.getSimpleName();
  65     public static final String KEYSTORE_LOC = System.getProperty("test.src", ".") +
  66                                               File.separator +
  67                                               "ssl" +
  68                                               File.separator +
  69                                               "keystore";
  70     public static final String TRUSTSTORE_LOC = System.getProperty("test.src", ".") +
  71                                                 File.separator +
  72                                                 "ssl" +
  73                                                 File.separator +
  74                                                 "truststore";
  75     public static final String TEST_CLASSPATH = System.getProperty("test.classes", ".");
  76 
  77     public void run(InetAddress[] addrs) {
  78         System.out.println("DEBUG: Running tests with plain sockets.");
  79         runTests(addrs, false);
  80         System.out.println("DEBUG: Running tests with SSL sockets.");
  81         runTests(addrs, true);
  82     }
  83 
  84     private void runTests(InetAddress[] addrs, boolean useSSL) {
  85         TestProcessThread[] jvms = new TestProcessThread[addrs.length];
  86         for (int i = 0; i < addrs.length; i++) {
  87             System.out.println();
  88             String msg = String.format("DEBUG: Launching java tester for triplet (HOSTNAME,JMX_PORT,RMI_PORT) == (%s,%d,%d)",
  89                     addrs[i].getHostAddress(),
  90                     JMX_PORT,
  91                     RMI_PORT);
  92             System.out.println(msg);
  93             jvms[i] = runJMXBindingTest(addrs[i], useSSL);
  94             jvms[i].start();
  95             System.out.println("DEBUG: Started " + (i + 1) + " Process(es).");
  96         }
  97         int failedProcesses = 0;
  98         for (TestProcessThread pt: jvms) {
  99             try {
 100                 pt.stopProcess();
 101                 pt.join();
 102             } catch (InterruptedException e) {
 103                 System.err.println("Failed to stop process: " + pt.getName());
 104                 throw new RuntimeException("Test failed", e);
 105             }
 106             int exitValue = pt.getExitValue();
 107             // If there is a communication error (the case we care about)
 108             // we get a exit code of 1
 109             if (exitValue == COMMUNICATION_ERROR_EXIT_VAL) {
 110                 // Failure case since the java processes should still be
 111                 // running.
 112                 System.err.println("Test FAILURE on " + pt.getName());
 113                 failedProcesses++;
 114             } else if (exitValue == STOP_PROCESS_EXIT_VAL) {
 115                 System.out.println("DEBUG: OK. Spawned java process terminated with expected exit code of " + STOP_PROCESS_EXIT_VAL);
 116             } else {
 117                 System.err.println("Test FAILURE on " + pt.getName() + " reason: Unexpected exit code => " + exitValue);
 118                 failedProcesses++;
 119             }
 120         }
 121         if (failedProcesses > 0) {
 122             throw new RuntimeException("Test FAILED. " + failedProcesses + " out of " + addrs.length + " process(es) failed to start the JMX agent.");
 123         }
 124     }
 125 
 126     private TestProcessThread runJMXBindingTest(InetAddress a, boolean useSSL) {
 127         List<String> args = new ArrayList<>();
 128         args.add("-classpath");
 129         args.add(TEST_CLASSPATH);
 130         args.add("-Dcom.sun.management.jmxremote.host=" + a.getHostAddress());
 131         args.add("-Dcom.sun.management.jmxremote.port=" + JMX_PORT);
 132         args.add("-Dcom.sun.management.jmxremote.rmi.port=" + RMI_PORT);
 133         args.add("-Dcom.sun.management.jmxremote.authenticate=false");
 134         args.add("-Dcom.sun.management.jmxremote.ssl=" + Boolean.toString(useSSL));
 135         if (useSSL) {
 136             args.add("-Dcom.sun.management.jmxremote.registry.ssl=true");
 137             args.add("-Djavax.net.ssl.keyStore=" + KEYSTORE_LOC);
 138             args.add("-Djavax.net.ssl.trustStore=" + TRUSTSTORE_LOC);
 139             args.add("-Djavax.net.ssl.keyStorePassword=password");
 140             args.add("-Djavax.net.ssl.trustStorePassword=trustword");
 141         }
 142         args.add(TEST_CLASS);
 143         args.add(a.getHostAddress());
 144         args.add(Integer.toString(JMX_PORT));
 145         args.add(Integer.toString(RMI_PORT));
 146         args.add(Boolean.toString(useSSL));
 147         try {
 148             ProcessBuilder builder = ProcessTools.createJavaProcessBuilder(args.toArray(new String[] {}));
 149             System.out.println(ProcessTools.getCommandLine(builder));
 150             TestProcessThread jvm = new TestProcessThread("JMX-Tester-" + a.getHostAddress(), JMXInterfaceBindingTest::isJMXAgentResponseAvailable, builder);
 151             return jvm;
 152         } catch (Exception e) {
 153             throw new RuntimeException("Test failed", e);
 154         }
 155 
 156     }
 157 
 158     private static boolean isJMXAgentResponseAvailable(String line) {
 159         if (line.equals(READY_MSG)) {
 160             System.out.println("DEBUG: Found expected READY_MSG.");
 161             return true;
 162         } else if (line.startsWith("Error:")) {
 163             // Allow for a JVM process that exits with
 164             // "Error: JMX connector server communication error: ..."
 165             // to continue as well since we handle that case elsewhere.
 166             // This has the effect that the test does not timeout and
 167             // fails with an exception in the test.
 168             System.err.println("PROBLEM: JMX agent of target JVM did not start as it should.");
 169             return true;
 170         } else {
 171             return false;
 172         }
 173     }
 174 
 175     public static void main(String[] args) {
 176         InetAddress[] addrs = getAddressesForLocalHost();
 177         if (addrs.length < 2) {
 178             System.out.println("Ignoring manual test since no more than one IPs are configured for 'localhost'");
 179             System.exit(0);
 180         }
 181         JMXInterfaceBindingTest test = new JMXInterfaceBindingTest();
 182         test.run(addrs);
 183         System.out.println("All tests PASSED.");
 184     }
 185 
 186     private static InetAddress[] getAddressesForLocalHost() {
 187         InetAddress[] addrs;
 188         try {
 189             addrs = InetAddress.getAllByName("localhost");
 190         } catch (UnknownHostException e) {
 191             throw new RuntimeException("Test failed", e);
 192         }
 193         return addrs;
 194     }
 195 
 196     private static class TestProcessThread extends Thread {
 197 
 198         private final Predicate<String> predicate;
 199         private final ProcessBuilder pb;
 200         private final CountDownLatch latch;
 201         private Process process;
 202 
 203         public TestProcessThread(String threadName, Predicate<String> predicate, ProcessBuilder pb) {
 204             super(threadName);
 205             this.predicate = predicate;
 206             this.pb = pb;
 207             this.latch = new CountDownLatch(1);
 208         }
 209 
 210         @Override
 211         public void run() {
 212             try {
 213                 process = ProcessTools.startProcess(getName(), pb, predicate, 10, TimeUnit.SECONDS);
 214                 latch.countDown();
 215                 process.waitFor();
 216             } catch (Exception e) {
 217                 throw new RuntimeException("Test failed", e);
 218             }
 219         }
 220 
 221         public void stopProcess() {
 222             try {
 223                 latch.await();
 224             } catch (InterruptedException e1) {
 225                 throw new RuntimeException("Test failed", e1);
 226             }
 227             if (process != null) {
 228                 process.destroyForcibly();
 229                 try {
 230                     process.waitFor();
 231                 } catch (InterruptedException e) {
 232                     throw new RuntimeException("Test failed", e);
 233                 }
 234             }
 235         }
 236 
 237         public int getExitValue() {
 238             return process.exitValue();
 239         }
 240     }
 241 }