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