/* * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved. * 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 sun.management.jmxremote.ConnectorBootstrap; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.FilenameFilter; import java.io.IOException; import java.util.Properties; import java.util.Iterator; import java.util.Set; import java.util.Arrays; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Enumeration; import javax.management.remote.*; import javax.management.*; import sun.management.AgentConfigurationError; import util.TestLogger; /** * <p>This class implements unit test for RMI Bootstrap. * When called with no arguments main() looks in the directory indicated * by the "test.src" system property for files called management*ok.properties * or management*ko.properties. The *ok.properties files are assumed to be * valid Java M&M config files for which the bootstrap should succeed. * The *ko.properties files are assumed to be configurations for which the * bootstrap & connection test will fail.</p> * * <p>The rmi port number can be specified with the "rmi.port" system property. * If not, this test will use 12424</p> * * <p>When called with some argument, the main() will interprete its args to * be Java M&M configuration file names. The filenames are expected to end * with ok.properties or ko.properties - and are interpreted as above.</p> * * <p>Note that a limitation of the RMI registry (bug 4267864) prevent * this test from succeeding if more than 1 configuration is used. * As long as 4267864 isn't fix, this test must be called as many times * as needed but with a single argument (no arguments, or several arguments * will fail).</p> * * <p>Debug traces are logged in "sun.management.test"</p> **/ public class RmiBootstrapTest { static TestLogger log = new TestLogger("RmiBootstrapTest"); /** * When launching several registries, we increment the port number * to avoid falling into "port number already in use" problems. **/ static int testPort = 0; /** * Default values for RMI configuration properties. **/ public static interface DefaultValues { public static final String PORT="0"; public static final String CONFIG_FILE_NAME="management.properties"; public static final String USE_SSL="true"; public static final String USE_AUTHENTICATION="true"; public static final String PASSWORD_FILE_NAME="jmxremote.password"; public static final String ACCESS_FILE_NAME="jmxremote.access"; public static final String KEYSTORE="keystore"; public static final String KEYSTORE_PASSWD="password"; public static final String TRUSTSTORE="truststore"; public static final String TRUSTSTORE_PASSWD="trustword"; public static final String SSL_NEED_CLIENT_AUTH="false"; } /** * Names of RMI configuration properties. **/ public static interface PropertyNames { public static final String PORT= "com.sun.management.jmxremote.port"; public static final String CONFIG_FILE_NAME= "com.sun.management.config.file"; public static final String USE_SSL= "com.sun.management.jmxremote.ssl"; public static final String USE_AUTHENTICATION= "com.sun.management.jmxremote.authenticate"; public static final String PASSWORD_FILE_NAME= "com.sun.management.jmxremote.password.file"; public static final String ACCESS_FILE_NAME= "com.sun.management.jmxremote.access.file"; public static final String INSTRUMENT_ALL= "com.sun.management.instrumentall"; public static final String CREDENTIALS = "jmx.remote.credentials"; public static final String KEYSTORE= "javax.net.ssl.keyStore"; public static final String KEYSTORE_PASSWD= "javax.net.ssl.keyStorePassword"; public static final String TRUSTSTORE= "javax.net.ssl.trustStore"; public static final String TRUSTSTORE_PASSWD= "javax.net.ssl.trustStorePassword"; public static final String SSL_ENABLED_CIPHER_SUITES = "com.sun.management.jmxremote.ssl.enabled.cipher.suites"; public static final String SSL_ENABLED_PROTOCOLS = "com.sun.management.jmxremote.ssl.enabled.protocols"; public static final String SSL_NEED_CLIENT_AUTH = "com.sun.management.jmxremote.ssl.need.client.auth"; } /** * A filter to find all filenames who match <prefix>*<suffix>. * Note that <prefix> and <suffix> can overlap. **/ private static class ConfigFilenameFilter implements FilenameFilter { final String suffix; final String prefix; ConfigFilenameFilter(String prefix, String suffix) { this.suffix=suffix; this.prefix=prefix; } public boolean accept(File dir, String name) { return (name.startsWith(prefix) && name.endsWith(suffix)); } } /** * Get all "management*ok.properties" files in the directory * indicated by the "test.src" management property. **/ private static File[] findConfigurationFilesOk() { final String testSrc = System.getProperty("test.src"); final File dir = new File(testSrc); final FilenameFilter filter = new ConfigFilenameFilter("management_test","ok.properties"); return dir.listFiles(filter); } /** * Get all "management*ko.properties" files in the directory * indicated by the "test.src" management property. **/ private static File[] findConfigurationFilesKo() { final String testSrc = System.getProperty("test.src"); final File dir = new File(testSrc); final FilenameFilter filter = new ConfigFilenameFilter("management_test","ko.properties"); return dir.listFiles(filter); } /** * List all MBeans and their attributes. Used to test communication * with the Java M&M MBean Server. * @return the number of queried MBeans. */ public static int listMBeans(MBeanServerConnection server) throws IOException { return listMBeans(server,null,null); } /** * List all matching MBeans and their attributes. * Used to test communication with the Java M&M MBean Server. * @return the number of matching MBeans. */ public static int listMBeans(MBeanServerConnection server, ObjectName pattern, QueryExp query) throws IOException { final Set names = server.queryNames(pattern,query); for (final Iterator i=names.iterator(); i.hasNext(); ) { ObjectName name = (ObjectName)i.next(); log.trace("listMBeans","Got MBean: "+name); try { MBeanInfo info = server.getMBeanInfo((ObjectName)name); MBeanAttributeInfo[] attrs = info.getAttributes(); if (attrs == null) continue; for (int j=0; j<attrs.length; j++) { if (attrs[j].isReadable()) { try { Object o = server.getAttribute(name,attrs[j].getName()); if (log.isDebugOn()) log.debug("listMBeans","\t\t" + attrs[j].getName() + " = "+o); } catch (Exception x) { log.trace("listMBeans","JmxClient failed to get " + attrs[j].getName() + ": " + x); final IOException io = new IOException("JmxClient failed to get " + attrs[j].getName()); io.initCause(x); throw io; } } } } catch (Exception x) { log.trace("listMBeans", "JmxClient failed to get MBeanInfo: " + x); final IOException io = new IOException("JmxClient failed to get MBeanInfo: "+x); io.initCause(x); throw io; } } return names.size(); } /** * Compute the full path name for a default file. * @param basename basename (with extension) of the default file. * @return ${JRE}/lib/management/${basename} **/ private static String getDefaultFileName(String basename) { final String fileSeparator = File.separator; final StringBuffer defaultFileName = new StringBuffer(System.getProperty("java.home")). append(fileSeparator).append("lib").append(fileSeparator). append("management").append(fileSeparator). append(basename); return defaultFileName.toString(); } /** * Compute the full path name for a default file. * @param basename basename (with extension) of the default file. * @return ${JRE}/lib/management/${basename} **/ private static String getDefaultStoreName(String basename) { final String fileSeparator = File.separator; final StringBuffer defaultFileName = new StringBuffer(System.getProperty("test.src")). append(fileSeparator).append("ssl").append(fileSeparator). append(basename); return defaultFileName.toString(); } /** * Parses the password file to read the credentials. * Returns an ArrayList of arrays of 2 string: * {<subject>, <password>}. * If the password file does not exists, return an empty list. * (File not found = empty file). **/ private ArrayList readCredentials(String passwordFileName) throws IOException { final Properties pws = new Properties(); final ArrayList result = new ArrayList(); final File f = new File(passwordFileName); if (!f.exists()) return result; FileInputStream fin = new FileInputStream(passwordFileName); try {pws.load(fin);}finally{fin.close();} for (Enumeration en=pws.propertyNames();en.hasMoreElements();) { final String[] cred = new String[2]; cred[0]=(String)en.nextElement(); cred[1]=pws.getProperty(cred[0]); result.add(cred); } return result; } /** * Connect with the given url, using all given credentials in turn. * A null entry in the useCredentials arrays indicate a connection * where no credentials are used. * @param url JMXServiceURL of the server. * @param useCredentials An array of credentials (a credential * is a two String array, so this is an array of arrays * of strings: * useCredentials[i][0]=subject * useCredentials[i][1]=password * if useCredentials[i] == null means no credentials. * @param expectConnectOk true if connection is expected to succeed * Note: if expectConnectOk=false and the test fails to connect * the number of failure is not incremented. Conversely, * if expectConnectOk=false and the test does not fail to * connect the number of failure is incremented. * @param expectReadOk true if communication (listMBeans) is expected * to succeed. * Note: if expectReadOk=false and the test fails to read MBeans * the number of failure is not incremented. Conversely, * if expectReadOk=false and the test does not fail to * read MBeans the number of failure is incremented. * @return number of failure. **/ public int connectAndRead(JMXServiceURL url, Object[] useCredentials, boolean expectConnectOk, boolean expectReadOk) throws IOException { int errorCount = 0; for (int i=0 ; i<useCredentials.length ; i++) { final Map m = new HashMap(); final String[] credentials = (String[])useCredentials[i]; final String crinfo; if (credentials != null) { crinfo = "{"+credentials[0] + ", " + credentials[1] + "}"; m.put(PropertyNames.CREDENTIALS,credentials); } else { crinfo="no credentials"; } log.trace("testCommunication","using credentials: " + crinfo); final JMXConnector c; try { c = JMXConnectorFactory.connect(url,m); } catch (IOException x ) { if (expectConnectOk) { final String err = "Connection failed for " + crinfo + ": " + x; System.out.println(err); log.trace("testCommunication",err); log.debug("testCommunication",x); errorCount++; continue; } else { System.out.println("Connection failed as expected for " + crinfo + ": " + x); continue; } } catch (RuntimeException x ) { if (expectConnectOk) { final String err = "Connection failed for " + crinfo + ": " + x; System.out.println(err); log.trace("testCommunication",err); log.debug("testCommunication",x); errorCount++; continue; } else { System.out.println("Connection failed as expected for " + crinfo + ": " + x); continue; } } try { MBeanServerConnection conn = c.getMBeanServerConnection(); if (log.isDebugOn()) { log.debug("testCommunication","Connection is:" + conn); log.debug("testCommunication","Server domain is: " + conn.getDefaultDomain()); } final ObjectName pattern = new ObjectName("java.lang:type=Memory,*"); final int count = listMBeans(conn,pattern,null); if (count == 0) throw new Exception("Expected at least one matching "+ "MBean for "+pattern); if (expectReadOk) { System.out.println("Communication succeeded " + "as expected for "+ crinfo + ": found " + count + ((count<2)?"MBean":"MBeans")); } else { final String err = "Expected failure didn't occur for " + crinfo; System.out.println(err); errorCount++; } } catch (IOException x ) { if (expectReadOk) { final String err = "Communication failed with " + crinfo + ": " + x; System.out.println(err); log.trace("testCommunication",err); log.debug("testCommunication",x); errorCount++; continue; } else { System.out.println("Communication failed as expected for "+ crinfo + ": " + x); continue; } } catch (RuntimeException x ) { if (expectReadOk) { final String err = "Communication failed with " + crinfo + ": " + x; System.out.println(err); log.trace("testCommunication",err); log.debug("testCommunication",x); errorCount++; continue; } else { System.out.println("Communication failed as expected for "+ crinfo + ": " + x); } } catch (Exception x) { final String err = "Failed to read MBeans with " + crinfo + ": " + x; System.out.println(err); log.trace("testCommunication",err); log.debug("testCommunication",x); errorCount++; continue; } finally { c.close(); } } return errorCount; } private void setSslProperties() { final String defaultKeyStore = getDefaultStoreName(DefaultValues.KEYSTORE); final String defaultTrustStore = getDefaultStoreName(DefaultValues.TRUSTSTORE); final String keyStore = System.getProperty(PropertyNames.KEYSTORE, defaultKeyStore); System.setProperty(PropertyNames.KEYSTORE,keyStore); log.trace("setSslProperties",PropertyNames.KEYSTORE+"="+keyStore); final String password = System.getProperty(PropertyNames.KEYSTORE_PASSWD, DefaultValues.KEYSTORE_PASSWD); System.setProperty(PropertyNames.KEYSTORE_PASSWD,password); log.trace("setSslProperties", PropertyNames.KEYSTORE_PASSWD+"="+password); final String trustStore = System.getProperty(PropertyNames.TRUSTSTORE, defaultTrustStore); System.setProperty(PropertyNames.TRUSTSTORE,trustStore); log.trace("setSslProperties", PropertyNames.TRUSTSTORE+"="+trustStore); final String trustword = System.getProperty(PropertyNames.TRUSTSTORE_PASSWD, DefaultValues.TRUSTSTORE_PASSWD); System.setProperty(PropertyNames.TRUSTSTORE_PASSWD,trustword); log.trace("setSslProperties", PropertyNames.TRUSTSTORE_PASSWD+"="+trustword); } private void checkSslConfiguration() { try { final String defaultConf = getDefaultFileName(DefaultValues.CONFIG_FILE_NAME); final String confname = System.getProperty(PropertyNames.CONFIG_FILE_NAME,defaultConf); final Properties props = new Properties(); final File conf = new File(confname); if (conf.exists()) { FileInputStream fin = new FileInputStream(conf); try {props.load(fin);} finally {fin.close();} } // Do we use SSL? final String useSslStr = props.getProperty(PropertyNames.USE_SSL, DefaultValues.USE_SSL); final boolean useSsl = Boolean.valueOf(useSslStr).booleanValue(); log.debug("checkSslConfiguration", PropertyNames.USE_SSL+"="+useSsl+ ": setting SSL"); // Do we use SSL client authentication? final String useSslClientAuthStr = props.getProperty(PropertyNames.SSL_NEED_CLIENT_AUTH, DefaultValues.SSL_NEED_CLIENT_AUTH); final boolean useSslClientAuth = Boolean.valueOf(useSslClientAuthStr).booleanValue(); log.debug("checkSslConfiguration", PropertyNames.SSL_NEED_CLIENT_AUTH+"="+useSslClientAuth); // Do we use customized SSL cipher suites? final String sslCipherSuites = props.getProperty(PropertyNames.SSL_ENABLED_CIPHER_SUITES); log.debug("checkSslConfiguration", PropertyNames.SSL_ENABLED_CIPHER_SUITES + "=" + sslCipherSuites); // Do we use customized SSL protocols? final String sslProtocols = props.getProperty(PropertyNames.SSL_ENABLED_PROTOCOLS); log.debug("checkSslConfiguration", PropertyNames.SSL_ENABLED_PROTOCOLS + "=" + sslProtocols); if (useSsl) setSslProperties(); } catch (Exception x) { System.out.println("Failed to setup SSL configuration: " + x); log.debug("checkSslConfiguration",x); } } /** * Tests the server bootstraped at the given URL. * Uses the system properties to determine which config file is used. * Loads the config file to determine which password file is used. * Loads the password file to find out wich credentials to use. * Also checks that unregistered user/passwords are not allowed to * connect when a password file is used. * * This method calls connectAndRead(). **/ public void testCommunication(JMXServiceURL url) throws IOException { final String defaultConf = getDefaultFileName(DefaultValues.CONFIG_FILE_NAME); final String confname = System.getProperty(PropertyNames.CONFIG_FILE_NAME,defaultConf); final Properties props = new Properties(); final File conf = new File(confname); if (conf.exists()) { FileInputStream fin = new FileInputStream(conf); try {props.load(fin);} finally {fin.close();} } // Do we use authentication? final String useAuthenticationStr = props.getProperty(PropertyNames.USE_AUTHENTICATION, DefaultValues.USE_AUTHENTICATION); final boolean useAuthentication = Boolean.valueOf(useAuthenticationStr).booleanValue(); // Get Password File final String defaultPasswordFileName = Utils.convertPath( getDefaultFileName(DefaultValues.PASSWORD_FILE_NAME)); final String passwordFileName = Utils.convertPath( props.getProperty(PropertyNames.PASSWORD_FILE_NAME, defaultPasswordFileName)); // Get Access File final String defaultAccessFileName = Utils.convertPath( getDefaultFileName(DefaultValues.ACCESS_FILE_NAME)); final String accessFileName = Utils.convertPath( props.getProperty(PropertyNames.ACCESS_FILE_NAME, defaultAccessFileName)); if (useAuthentication) { System.out.println("PasswordFileName: " + passwordFileName); System.out.println("accessFileName: " + accessFileName); } final Object[] allCredentials; final Object[] noCredentials = { null }; if (useAuthentication) { final ArrayList l = readCredentials(passwordFileName); if (l.size() == 0) allCredentials = null; else allCredentials = l.toArray(); } else allCredentials = noCredentials; int errorCount = 0; if (allCredentials!=null) { // Tests that the registered user/passwords are allowed to // connect & read // errorCount += connectAndRead(url,allCredentials,true,true); } else { // Tests that no one is allowed // connect & read // final String[][] someCredentials = { null, { "modify", "R&D" }, { "measure", "QED" } }; errorCount += connectAndRead(url,someCredentials,false,false); } if (useAuthentication && allCredentials != noCredentials) { // Tests that the registered user/passwords are not allowed to // connect & read // final String[][] badCredentials = { { "bad.user", "R&D" }, { "measure", "bad.password" } }; errorCount += connectAndRead(url,badCredentials,false,false); } if (errorCount > 0) { final String err = "Test " + confname + " failed with " + errorCount + " error(s)"; log.debug("testCommunication",err); throw new RuntimeException(err); } } /** * Test the configuration indicated by `file'. * Sets the appropriate System properties for config file and * port and then calls ConnectorBootstrap.initialize(). * eventually cleans up by calling ConnectorBootstrap.terminate(). * @return null if the test succeeds, an error message otherwise. **/ private String testConfiguration(File file,int port) { final String path; try { path=(file==null)?null:file.getCanonicalPath(); } catch(IOException x) { final String err = "Failed to test configuration " + file + ": " + x; log.trace("testConfiguration",err); log.debug("testConfiguration",x); return err; } final String config = (path==null)?"Default config file":path; System.out.println("***"); System.out.println("*** Testing configuration (port=" + port + "): " + path); System.out.println("***"); System.setProperty("com.sun.management.jmxremote.port", Integer.toString(port)); if (path != null) System.setProperty("com.sun.management.config.file", path); else System.getProperties().remove("com.sun.management.config.file"); log.trace("testConfiguration","com.sun.management.jmxremote.port="+port); if (path != null && log.isDebugOn()) log.trace("testConfiguration", "com.sun.management.config.file="+path); checkSslConfiguration(); final JMXConnectorServer cs; try { cs = ConnectorBootstrap.initialize(); } catch (AgentConfigurationError x) { final String err = "Failed to initialize connector:" + "\n\tcom.sun.management.jmxremote.port=" + port + ((path!=null)?"\n\tcom.sun.management.config.file="+path: "\n\t"+config) + "\n\tError is: " + x; log.trace("testConfiguration",err); log.debug("testConfiguration",x); return err; } catch (Exception x) { log.debug("testConfiguration",x); return x.toString(); } try { JMXServiceURL url = new JMXServiceURL("rmi",null,0,"/jndi/rmi://localhost:"+ port+"/jmxrmi"); try { testCommunication(url); } catch (Exception x) { final String err = "Failed to connect to agent {url="+url+ "}: " + x; log.trace("testConfiguration",err); log.debug("testConfiguration",x); return err; } } catch (Exception x) { final String err = "Failed to test configuration "+config+ ": "+x; log.trace("testConfiguration",err); log.debug("testConfiguration",x); return err; } finally { try { cs.stop(); } catch (Exception x) { final String err = "Failed to terminate: "+x; log.trace("testConfiguration",err); log.debug("testConfiguration",x); } } System.out.println("Configuration " + config + " successfully tested"); return null; } /** * Test a configuration file which should make the bootstrap fail. * The test is assumed to have succeeded if the bootstrap fails. * @return null if the test succeeds, an error message otherwise. **/ private String testConfigurationKo(File conf,int port) { final String errStr = testConfiguration(conf,port+testPort++); if (errStr == null) { return "Configuration " + conf + " should have failed!"; } System.out.println("Configuration " + conf + " failed as expected"); log.debug("runko","Error was: " + errStr); return null; } /** * Test a configuration file. Determines whether the bootstrap * should succeed or fail depending on the file name: * *ok.properties: bootstrap should succeed. * *ko.properties: bootstrap or connection should fail. * @return null if the test succeeds, an error message otherwise. **/ private String testConfigurationFile(String fileName) { File file = new File(fileName); final String portStr = System.getProperty("rmi.port","12424"); final int port = Integer.parseInt(portStr); if (fileName.endsWith("ok.properties")) { return testConfiguration(file,port+testPort++); } if (fileName.endsWith("ko.properties")) { return testConfigurationKo(file,port+testPort++); } return fileName + ": test file suffix must be one of [ko|ok].properties"; } /** * Find all *ko.property files and test them. * (see findConfigurationFilesKo() and testConfigurationKo()) * @throws RuntimeException if the test fails. **/ public void runko() { final String portStr = System.getProperty("rmi.port","12424"); final int port = Integer.parseInt(portStr); final File[] conf = findConfigurationFilesKo(); if ((conf == null)||(conf.length == 0)) throw new RuntimeException("No configuration found"); String errStr; for (int i=0;i<conf.length;i++) { errStr = testConfigurationKo(conf[i],port+testPort++); if (errStr != null) { throw new RuntimeException(errStr); } } } /** * Find all *ok.property files and test them. * (see findConfigurationFilesOk() and testConfiguration()) * @throws RuntimeException if the test fails. **/ public void runok() { final String portStr = System.getProperty("rmi.port","12424"); final int port = Integer.parseInt(portStr); final File[] conf = findConfigurationFilesOk(); if ((conf == null)||(conf.length == 0)) throw new RuntimeException("No configuration found"); String errStr; for (int i=0;i<conf.length;i++) { errStr = testConfiguration(conf[i],port+testPort++); if (errStr != null) { throw new RuntimeException(errStr); } } // FIXME: No jmxremote.password is not installed in JRE by default. // - disable the following test case. // // Test default config // // errStr = testConfiguration(null,port+testPort++); // if (errStr != null) { // throw new RuntimeException(errStr); // } } /** * Finds all configuration files (*ok.properties and *ko.properties) * and tests them. * (see runko() and runok()). * @throws RuntimeException if the test fails. **/ public void run() { runok(); runko(); } /** * Tests the specified configuration files. * If args[] is not empty, each element in args[] is expected to be * a filename ending either by ok.properties or ko.properties. * Otherwise, the configuration files will be automatically determined * by looking at all *.properties files located in the directory * indicated by the System property "test.src". * @throws RuntimeException if the test fails. **/ public void run(String args[]) { if (args.length == 0) { run() ; return; } for (int i=0; i<args.length; i++) { final String errStr =testConfigurationFile(args[i]); if (errStr != null) { throw new RuntimeException(errStr); } } } /** * Calls run(args[]). * exit(1) if the test fails. **/ public static void main(String args[]) { RmiBootstrapTest manager = new RmiBootstrapTest(); try { manager.run(args); } catch (RuntimeException r) { System.out.println("Test Failed: "+ r.getMessage()); System.exit(1); } catch (Throwable t) { System.out.println("Test Failed: "+ t); t.printStackTrace(); System.exit(2); } System.out.println("**** Test RmiBootstrap Passed ****"); } }