From ea1bd6136bca68c07bb32358b71fbaca662980f3 Mon Sep 17 00:00:00 2001 From: dsamersoff Date: Sun, 3 Feb 2013 21:39:58 +0400 Subject: [PATCH] 8002048: Protocol to discovery of manageable Java processes on a network Summary: Introduce a protocol to discover manageble Java instances across a network subnet, JDP Reviewed-by: sla, dfuchs --- src/share/classes/sun/management/Agent.java | 218 ++++++++---- .../sun/management/jdp/JdpBroadcaster.java | 124 +++++++ .../sun/management/jdp/JdpController.java | 196 +++++++++++ .../sun/management/jdp/JdpException.java | 40 +++ .../sun/management/jdp/JdpGenericPacket.java | 95 ++++++ .../sun/management/jdp/JdpJmxPacket.java | 196 +++++++++++ .../classes/sun/management/jdp/JdpPacket.java | 63 ++++ .../sun/management/jdp/JdpPacketReader.java | 139 ++++++++ .../sun/management/jdp/JdpPacketWriter.java | 93 +++++ .../sun/management/jdp/package-info.java | 55 +++ test/sun/management/jdp/JdpClient.java | 160 +++++++++ test/sun/management/jdp/JdpDoSomething.java | 103 ++++++ test/sun/management/jdp/JdpTest.sh | 323 ++++++++++++++++++ test/sun/management/jdp/JdpUnitTest.java | 90 +++++ 14 files changed, 1824 insertions(+), 71 deletions(-) create mode 100644 src/share/classes/sun/management/jdp/JdpBroadcaster.java create mode 100644 src/share/classes/sun/management/jdp/JdpController.java create mode 100644 src/share/classes/sun/management/jdp/JdpException.java create mode 100644 src/share/classes/sun/management/jdp/JdpGenericPacket.java create mode 100644 src/share/classes/sun/management/jdp/JdpJmxPacket.java create mode 100644 src/share/classes/sun/management/jdp/JdpPacket.java create mode 100644 src/share/classes/sun/management/jdp/JdpPacketReader.java create mode 100644 src/share/classes/sun/management/jdp/JdpPacketWriter.java create mode 100644 src/share/classes/sun/management/jdp/package-info.java create mode 100644 test/sun/management/jdp/JdpClient.java create mode 100644 test/sun/management/jdp/JdpDoSomething.java create mode 100644 test/sun/management/jdp/JdpTest.sh create mode 100644 test/sun/management/jdp/JdpUnitTest.java diff --git a/src/share/classes/sun/management/Agent.java b/src/share/classes/sun/management/Agent.java index 19d2dedb5..feef02ad6 100644 --- a/src/share/classes/sun/management/Agent.java +++ b/src/share/classes/sun/management/Agent.java @@ -22,7 +22,6 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package sun.management; import java.io.BufferedInputStream; @@ -31,49 +30,55 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; - +import java.lang.management.ManagementFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.management.ManagementFactory; - +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; import java.text.MessageFormat; - import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXServiceURL; import static sun.management.AgentConfigurationError.*; import sun.management.jmxremote.ConnectorBootstrap; +import sun.management.jdp.JdpController; +import sun.management.jdp.JdpException; import sun.misc.VMSupport; /** - * This Agent is started by the VM when -Dcom.sun.management.snmp - * or -Dcom.sun.management.jmxremote is set. This class will be - * loaded by the system class loader. Also jmx framework could - * be started by jcmd + * This Agent is started by the VM when -Dcom.sun.management.snmp or + * -Dcom.sun.management.jmxremote is set. This class will be loaded by the + * system class loader. Also jmx framework could be started by jcmd */ public class Agent { // management properties + private static Properties mgmtProps; private static ResourceBundle messageRB; - private static final String CONFIG_FILE = - "com.sun.management.config.file"; + "com.sun.management.config.file"; private static final String SNMP_PORT = - "com.sun.management.snmp.port"; + "com.sun.management.snmp.port"; private static final String JMXREMOTE = - "com.sun.management.jmxremote"; + "com.sun.management.jmxremote"; private static final String JMXREMOTE_PORT = - "com.sun.management.jmxremote.port"; + "com.sun.management.jmxremote.port"; + private static final String RMI_PORT = + "com.sun.management.jmxremote.rmi.port"; private static final String ENABLE_THREAD_CONTENTION_MONITORING = - "com.sun.management.enableThreadContentionMonitoring"; + "com.sun.management.enableThreadContentionMonitoring"; private static final String LOCAL_CONNECTOR_ADDRESS_PROP = - "com.sun.management.jmxremote.localConnectorAddress"; - + "com.sun.management.jmxremote.localConnectorAddress"; private static final String SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME = - "sun.management.snmp.AdaptorBootstrap"; + "sun.management.snmp.AdaptorBootstrap"; + + private static final String JDP_DEFAULT_ADDRESS = "239.255.255.225"; + private static final int JDP_DEFAULT_PORT = 7095; // The only active agent allowed private static JMXConnectorServer jmxServer = null; @@ -81,26 +86,25 @@ public class Agent { // Parse string com.sun.management.prop=xxx,com.sun.management.prop=yyyy // and return property set if args is null or empty // return empty property set - private static Properties parseString(String args){ + private static Properties parseString(String args) { Properties argProps = new Properties(); if (args != null) { - for (String option : args.split(",")) { - String s[] = option.split("=", 2); - String name = s[0].trim(); - String value = (s.length > 1) ? s[1].trim() : ""; + for (String option : args.split(",")) { + String s[] = option.split("=", 2); + String name = s[0].trim(); + String value = (s.length > 1) ? s[1].trim() : ""; - if (!name.startsWith("com.sun.management.")) { - error(INVALID_OPTION, name); - } + if (!name.startsWith("com.sun.management.")) { + error(INVALID_OPTION, name); + } - argProps.setProperty(name, value); - } + argProps.setProperty(name, value); + } } return argProps; } - // invoked by -javaagent or -Dcom.sun.management.agent.class public static void premain(String args) throws Exception { agentmain(args); @@ -115,18 +119,18 @@ public class Agent { Properties arg_props = parseString(args); // Read properties from the config file - Properties config_props = new Properties(); - String fname = arg_props.getProperty(CONFIG_FILE); - readConfiguration(fname, config_props); + Properties config_props = new Properties(); + String fname = arg_props.getProperty(CONFIG_FILE); + readConfiguration(fname, config_props); - // Arguments override config file - config_props.putAll(arg_props); - startAgent(config_props); + // Arguments override config file + config_props.putAll(arg_props); + startAgent(config_props); } // jcmd ManagementAgent.start_local entry point // Also called due to command-line via startAgent() - private static synchronized void startLocalManagementAgent(){ + private static synchronized void startLocalManagementAgent() { Properties agentProps = VMSupport.getAgentProperties(); // start local connector if not started @@ -156,7 +160,7 @@ public class Agent { throw new RuntimeException(getText(INVALID_STATE, "Agent already started")); } - Properties argProps = parseString(args); + Properties argProps = parseString(args); Properties configProps = new Properties(); // Load the management properties from the config file @@ -169,7 +173,7 @@ public class Agent { // management properties can be overridden by system properties // which take precedence Properties sysProps = System.getProperties(); - synchronized(sysProps){ + synchronized (sysProps) { configProps.putAll(sysProps); } @@ -190,21 +194,26 @@ public class Agent { // can specify this property inside config file, so enable optional // monitoring functionality if this property is set final String enableThreadContentionMonitoring = - configProps.getProperty(ENABLE_THREAD_CONTENTION_MONITORING); + configProps.getProperty(ENABLE_THREAD_CONTENTION_MONITORING); if (enableThreadContentionMonitoring != null) { ManagementFactory.getThreadMXBean(). - setThreadContentionMonitoringEnabled(true); + setThreadContentionMonitoringEnabled(true); } String jmxremotePort = configProps.getProperty(JMXREMOTE_PORT); if (jmxremotePort != null) { jmxServer = ConnectorBootstrap. - startRemoteConnectorServer(jmxremotePort, configProps); + startRemoteConnectorServer(jmxremotePort, configProps); + + startDiscoveryService(configProps); } } private static synchronized void stopRemoteManagementAgent() throws Exception { + + JdpController.stopDiscoveryService(); + if (jmxServer != null) { ConnectorBootstrap.unexportRegistry(); @@ -222,15 +231,15 @@ public class Agent { // Enable optional monitoring functionality if requested final String enableThreadContentionMonitoring = - props.getProperty(ENABLE_THREAD_CONTENTION_MONITORING); + props.getProperty(ENABLE_THREAD_CONTENTION_MONITORING); if (enableThreadContentionMonitoring != null) { ManagementFactory.getThreadMXBean(). - setThreadContentionMonitoringEnabled(true); + setThreadContentionMonitoringEnabled(true); } try { if (snmpPort != null) { - loadSnmpAgent(snmpPort, props); + loadSnmpAgent(snmpPort, props); } /* @@ -242,13 +251,14 @@ public class Agent { * of this "local" server is exported as a counter to the jstat * instrumentation buffer. */ - if (jmxremote != null || jmxremotePort != null) { + if (jmxremote != null || jmxremotePort != null) { if (jmxremotePort != null) { - jmxServer = ConnectorBootstrap. - startRemoteConnectorServer(jmxremotePort, props); + jmxServer = ConnectorBootstrap. + startRemoteConnectorServer(jmxremotePort, props); + startDiscoveryService(props); } startLocalManagementAgent(); - } + } } catch (AgentConfigurationError e) { error(e.getError(), e.getParams()); @@ -257,6 +267,73 @@ public class Agent { } } + private static void startDiscoveryService(Properties props) + throws IOException { + // Start discovery service if requested + String discoveryPort = props.getProperty("com.sun.management.jdp.port"); + String discoveryAddress = props.getProperty("com.sun.management.jdp.address"); + String discoveryShouldStart = props.getProperty("com.sun.management.jmxremote.autodiscovery"); + + // Decide whether we should start autodicovery service. + // To start autodiscovery following conditions should be met: + // autodiscovery==true OR (autodicovery==null AND jdp.port != NULL) + + boolean shouldStart = false; + if (discoveryShouldStart == null){ + shouldStart = (discoveryPort != null); + } + else{ + try{ + shouldStart = Boolean.parseBoolean(discoveryShouldStart); + } catch (NumberFormatException e) { + throw new AgentConfigurationError("Couldn't parse autodiscovery argument"); + } + } + + if (shouldStart) { + // port and address are required arguments and have no default values + InetAddress address; + try { + address = (discoveryAddress == null) ? + InetAddress.getByName(JDP_DEFAULT_ADDRESS) : InetAddress.getByName(discoveryAddress); + } catch (UnknownHostException e) { + throw new AgentConfigurationError("Unable to broadcast to requested address", e); + } + + int port = JDP_DEFAULT_PORT; + if (discoveryPort != null) { + try { + port = Integer.parseInt(discoveryPort); + } catch (NumberFormatException e) { + throw new AgentConfigurationError("Couldn't parse JDP port argument"); + } + } + + // Rebuilding service URL to broadcast it + String jmxremotePort = props.getProperty(JMXREMOTE_PORT); + String rmiPort = props.getProperty(RMI_PORT); + + JMXServiceURL url = jmxServer.getAddress(); + String hostname = url.getHost(); + + String jmxUrlStr = (rmiPort != null) + ? String.format( + "service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/jmxrmi", + hostname, rmiPort, hostname, jmxremotePort) + : String.format( + "service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", hostname, jmxremotePort); + + String instanceName = System.getProperty("com.sun.management.jdp.name"); + + try{ + JdpController.startDiscoveryService(address, port, instanceName, jmxUrlStr); + } + catch(JdpException e){ + throw new AgentConfigurationError("Couldn't start JDP service", e); + } + } + } + public static Properties loadManagementProperties() { Properties props = new Properties(); @@ -268,22 +345,22 @@ public class Agent { // management properties can be overridden by system properties // which take precedence Properties sysProps = System.getProperties(); - synchronized(sysProps){ + synchronized (sysProps) { props.putAll(sysProps); } return props; - } + } - public static synchronized Properties getManagementProperties() { + public static synchronized Properties getManagementProperties() { if (mgmtProps == null) { String configFile = System.getProperty(CONFIG_FILE); String snmpPort = System.getProperty(SNMP_PORT); String jmxremote = System.getProperty(JMXREMOTE); String jmxremotePort = System.getProperty(JMXREMOTE_PORT); - if (configFile == null && snmpPort == null && - jmxremote == null && jmxremotePort == null) { + if (configFile == null && snmpPort == null + && jmxremote == null && jmxremotePort == null) { // return if out-of-the-management option is not specified return null; } @@ -297,22 +374,23 @@ public class Agent { // invoke the following through reflection: // AdaptorBootstrap.initialize(snmpPort, props); final Class adaptorClass = - Class.forName(SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME,true,null); + Class.forName(SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME, true, null); final Method initializeMethod = adaptorClass.getMethod("initialize", - String.class, Properties.class); - initializeMethod.invoke(null,snmpPort,props); + String.class, Properties.class); + initializeMethod.invoke(null, snmpPort, props); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException x) { // snmp runtime doesn't exist - initialization fails - throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT,x); + throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT, x); } catch (InvocationTargetException x) { final Throwable cause = x.getCause(); - if (cause instanceof RuntimeException) + if (cause instanceof RuntimeException) { throw (RuntimeException) cause; - else if (cause instanceof Error) + } else if (cause instanceof Error) { throw (Error) cause; + } // should not happen... - throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT,cause); + throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT, cause); } } @@ -353,8 +431,8 @@ public class Agent { } catch (IOException e) { error(CONFIG_FILE_CLOSE_FAILED, fname); } - } - } + } + } } public static void startAgent() throws Exception { @@ -389,9 +467,9 @@ public class Agent { // invoke the premain(String args) method Class clz = ClassLoader.getSystemClassLoader().loadClass(cname); Method premain = clz.getMethod("premain", - new Class[] { String.class }); + new Class[]{String.class}); premain.invoke(null, /* static */ - new Object[] { args }); + new Object[]{args}); } catch (ClassNotFoundException ex) { error(AGENT_CLASS_NOT_FOUND, "\"" + cname + "\""); } catch (NoSuchMethodException ex) { @@ -400,8 +478,8 @@ public class Agent { error(AGENT_CLASS_ACCESS_DENIED); } catch (Exception ex) { String msg = (ex.getCause() == null - ? ex.getMessage() - : ex.getCause().getMessage()); + ? ex.getMessage() + : ex.getCause().getMessage()); error(AGENT_CLASS_FAILED, msg); } } @@ -425,7 +503,6 @@ public class Agent { } } - public static void error(String key, String message) { String keyText = getText(key); System.err.print(getText("agent.err.error") + ": " + keyText); @@ -447,7 +524,7 @@ public class Agent { private static void initResource() { try { messageRB = - ResourceBundle.getBundle("sun.management.resources.agent"); + ResourceBundle.getBundle("sun.management.resources.agent"); } catch (MissingResourceException e) { throw new Error("Fatal: Resource for management agent is missing"); } @@ -470,10 +547,9 @@ public class Agent { } String format = messageRB.getString(key); if (format == null) { - format = "missing resource key: key = \"" + key + "\", " + - "arguments = \"{0}\", \"{1}\", \"{2}\""; + format = "missing resource key: key = \"" + key + "\", " + + "arguments = \"{0}\", \"{1}\", \"{2}\""; } return MessageFormat.format(format, (Object[]) args); } - } diff --git a/src/share/classes/sun/management/jdp/JdpBroadcaster.java b/src/share/classes/sun/management/jdp/JdpBroadcaster.java new file mode 100644 index 000000000..df5f23458 --- /dev/null +++ b/src/share/classes/sun/management/jdp/JdpBroadcaster.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2012, 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. + */ +package sun.management.jdp; + +import java.io.IOException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.ProtocolFamily; +import java.net.StandardProtocolFamily; +import java.net.StandardSocketOptions; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.channels.UnsupportedAddressTypeException; + +/** + * JdpBroadcaster is responsible for sending pre-built JDP packet across a Net + * + *

Multicast group address, port number and ttl have to be chosen on upper + * level and passed to broadcaster constructor. Also it's possible to specify + * source address to broadcast from.

+ * + *

JdpBradcaster doesn't perform any validation on a supplied {@code port} and {@code ttl} because + * the allowed values depend on an operating system setup

+ * + */ +public final class JdpBroadcaster { + + private final InetAddress addr; + private final int port; + private final DatagramChannel channel; + + /** + * Create a new broadcaster + * + * @param address - multicast group address + * @param srcAddress - address of interface we should use to broadcast. + * @param port - udp port to use + * @param ttl - packet ttl + * @throws IOException + */ + public JdpBroadcaster(InetAddress address, InetAddress srcAddress, int port, int ttl) + throws IOException, JdpException { + this.addr = address; + this.port = port; + + ProtocolFamily family = (address instanceof Inet6Address) + ? StandardProtocolFamily.INET6 : StandardProtocolFamily.INET; + + channel = DatagramChannel.open(family); + channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); + channel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, ttl); + + // with srcAddress equal to null, this constructor do exactly the same as + // if srcAddress is not passed + if (srcAddress != null) { + // User requests particular interface to bind to + NetworkInterface interf = NetworkInterface.getByInetAddress(srcAddress); + try { + channel.bind(new InetSocketAddress(srcAddress, 0)); + } catch (UnsupportedAddressTypeException ex) { + throw new JdpException("Unable to bind to source address"); + } + channel.setOption(StandardSocketOptions.IP_MULTICAST_IF, interf); + } + } + + /** + * Create a new broadcaster + * + * @param address - multicast group address + * @param port - udp port to use + * @param ttl - packet ttl + * @throws IOException + */ + public JdpBroadcaster(InetAddress address, int port, int ttl) + throws IOException, JdpException { + this(address, null, port, ttl); + } + + /** + * Broadcast pre-built packet + * + * @param packet - instance of JdpPacket + * @throws IOException + */ + public void sendPacket(JdpPacket packet) + throws IOException { + byte[] data = packet.getPacketData(); + // Unlike allocate/put wrap don't need a flip afterward + ByteBuffer b = ByteBuffer.wrap(data); + channel.send(b, new InetSocketAddress(addr, port)); + } + + /** + * Shutdown broadcaster and close underlying socket channel + * + * @throws IOException + */ + public void shutdown() throws IOException { + channel.close(); + } +} diff --git a/src/share/classes/sun/management/jdp/JdpController.java b/src/share/classes/sun/management/jdp/JdpController.java new file mode 100644 index 000000000..d8d0ed469 --- /dev/null +++ b/src/share/classes/sun/management/jdp/JdpController.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2012, 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. + */ +package sun.management.jdp; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.UUID; + +/** + * JdpController is responsible to create and manage a broadcast loop + * + *

Other part of code has no access to broadcast loop and have to use + * provided static methods + * {@link #startDiscoveryService(InetAddress,int,String,String) startDiscoveryService} + * and {@link #stopDiscoveryService() stopDiscoveryService}

+ *

{@link #startDiscoveryService(InetAddress,int,String,String) startDiscoveryService} could be called multiple + * times as it stops the running service if it is necessary. Call to {@link #stopDiscoveryService() stopDiscoveryService} + * ignored if service isn't run

+ * + * + *

+ * + *

System properties below could be used to control broadcast loop behavior. + * Property below have to be set explicitly in command line. It's not possible to + * set it in management.config file. Careless changes of these properties could + * lead to security or network issues. + *

+

+ *

null parameters values are filtered out on {@link JdpPacketWriter} level and + * corresponding keys are not placed to packet.

+ */ +public final class JdpController { + + private static class JDPControllerRunner implements Runnable { + + private final JdpJmxPacket packet; + private final JdpBroadcaster bcast; + private final int pause; + private volatile boolean shutdown = false; + + private JDPControllerRunner(JdpBroadcaster bcast, JdpJmxPacket packet, int pause) { + this.bcast = bcast; + this.packet = packet; + this.pause = pause; + } + + @Override + public void run() { + try { + while (!shutdown) { + bcast.sendPacket(packet); + try { + Thread.sleep(this.pause); + } catch (InterruptedException e) { + // pass + } + } + + } catch (IOException e) { + // pass; + } + + // It's not possible to re-use controller, + // nevertheless reset shutdown variable + try { + stop(); + bcast.shutdown(); + } catch (IOException ex) { + // pass - ignore IOException during shutdown + } + } + + public void stop() { + shutdown = true; + } + } + private static JDPControllerRunner controller = null; + + private JdpController(){ + // Don't allow to instantiate this class. + } + + // Utility to handle optional system properties + // Parse an integer from string or return default if provided string is null + private static int getInteger(String val, int dflt, String msg) throws JdpException { + try { + return (val == null) ? dflt : Integer.parseInt(val); + } catch (NumberFormatException ex) { + throw new JdpException(msg); + } + } + + // Parse an inet address from string or return default if provided string is null + private static InetAddress getInetAddress(String val, InetAddress dflt, String msg) throws JdpException { + try { + return (val == null) ? dflt : InetAddress.getByName(val); + } catch (UnknownHostException ex) { + throw new JdpException(msg); + } + } + + /** + * Starts discovery service + * + * @param address - multicast group address + * @param port - udp port to use + * @param instanceName - name of running JVM instance + * @param url - JMX service url + * @throws IOException + */ + public static synchronized void startDiscoveryService(InetAddress address, int port, String instanceName, String url) + throws IOException, JdpException { + + // Limit packet to local subnet by default + int ttl = getInteger( + System.getProperty("com.sun.management.jdp.ttl"), 1, + "Invalid jdp packet ttl"); + + // Broadcast once a 5 seconds by default + int pause = getInteger( + System.getProperty("com.sun.management.jdp.pause"), 5, + "Invalid jdp pause"); + + // Converting seconds to milliseconds + pause = pause * 1000; + + // Allow OS to choose broadcast source + InetAddress sourceAddress = getInetAddress( + System.getProperty("com.sun.management.jdp.source_addr"), null, + "Invalid source address provided"); + + // Generate session id + UUID id = UUID.randomUUID(); + + JdpJmxPacket packet = new JdpJmxPacket(id, url); + + // Don't broadcast whole command line for security reason. + // Strip everything after first space + String javaCommand = System.getProperty("sun.java.command"); + if (javaCommand != null) { + String[] arr = javaCommand.split(" ", 2); + packet.setMainClass(arr[0]); + } + + // Put optional explicit java instance name to packet, if user doesn't specify + // it the key is skipped. PacketWriter is responsible to skip keys having null value. + packet.setInstanceName(instanceName); + + JdpBroadcaster bcast = new JdpBroadcaster(address, sourceAddress, port, ttl); + + // Stop discovery service if it's already running + stopDiscoveryService(); + + controller = new JDPControllerRunner(bcast, packet, pause); + + Thread t = new Thread(controller, "JDP broadcaster"); + t.setDaemon(true); + t.start(); + } + + /** + * Stop running discovery service, + * it's safe to attempt to stop not started service + */ + public static synchronized void stopDiscoveryService() { + if ( controller != null ){ + controller.stop(); + controller = null; + } + } +} diff --git a/src/share/classes/sun/management/jdp/JdpException.java b/src/share/classes/sun/management/jdp/JdpException.java new file mode 100644 index 000000000..03404223e --- /dev/null +++ b/src/share/classes/sun/management/jdp/JdpException.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012, 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. + */ +package sun.management.jdp; + +/** + * An Exception thrown if a JDP implementation encounters a problem. + */ +public final class JdpException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new JDP exception with a meaningful message + * + * @param msg - message + */ + public JdpException(String msg) { + super(msg); + } +} diff --git a/src/share/classes/sun/management/jdp/JdpGenericPacket.java b/src/share/classes/sun/management/jdp/JdpGenericPacket.java new file mode 100644 index 000000000..8e88b1482 --- /dev/null +++ b/src/share/classes/sun/management/jdp/JdpGenericPacket.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012, 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. + */ +package sun.management.jdp; + +/** + * JdpGenericPacket responsible to provide fields + * common for all Jdp packets + */ +public abstract class JdpGenericPacket implements JdpPacket { + + /** + * JDP protocol magic. Magic allows a reader to quickly select + * JDP packets from a bunch of broadcast packets addressed to the same port + * and broadcast group. Any packet intended to be parsed by JDP client + * has to start from this magic. + */ + private static final int MAGIC = 0xC0FFEE42; + + /** + * Current version of protocol. Any implementation of this protocol has to + * conform with the packet structure and the flow described in JEP-168 + */ + private static final short PROTOCOL_VERSION = 1; + + /** + * Default do-nothing constructor + */ + protected JdpGenericPacket(){ + // do nothing + } + + + /** + * Validate protocol header magic field + * + * @param magic - value to validate + * @throws JdpException + */ + public static void checkMagic(int magic) + throws JdpException { + if (magic != MAGIC) { + throw new JdpException("Invalid JDP magic header: " + magic); + } + } + + /** + * Validate protocol header version field + * + * @param version - value to validate + * @throws JdpException + */ + public static void checkVersion(short version) + throws JdpException { + + if (version > PROTOCOL_VERSION) { + throw new JdpException("Unsupported protocol version: " + version); + } + } + + /** + * + * @return protocol magic + */ + public static int getMagic() { + return MAGIC; + } + + /** + * + * @return current protocol version + */ + public static short getVersion() { + return PROTOCOL_VERSION; + } +} diff --git a/src/share/classes/sun/management/jdp/JdpJmxPacket.java b/src/share/classes/sun/management/jdp/JdpJmxPacket.java new file mode 100644 index 000000000..7d5ccc2f8 --- /dev/null +++ b/src/share/classes/sun/management/jdp/JdpJmxPacket.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2012, 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. + */ +package sun.management.jdp; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * A packet to broadcasts JMX URL + * + * Fields: + * + * + */ +public final class JdpJmxPacket + extends JdpGenericPacket + implements JdpPacket { + + /** + * Session ID + */ + public final static String UUID_KEY = "DISCOVERABLE_SESSION_UUID"; + /** + * Name of main class + */ + public final static String MAIN_CLASS_KEY = "MAIN_CLASS"; + /** + * JMX service URL + */ + public final static String JMX_SERVICE_URL_KEY = "JMX_SERVICE_URL"; + /** + * Name of Java instance + */ + public final static String INSTANCE_NAME_KEY = "INSTANCE_NAME"; + + private UUID id; + private String mainClass; + private String jmxServiceUrl; + private String instanceName; + + /** + * Create new instance from user provided data. Set mandatory fields + * + * @param id - java instance id + * @param jmxServiceUrl - JMX service url + */ + public JdpJmxPacket(UUID id, String jmxServiceUrl) { + this.id = id; + this.jmxServiceUrl = jmxServiceUrl; + } + + /** + * Create new instance from network data Parse packet and set fields. + * + * @param data - raw packet data as it came from a Net + * @throws JdpException + */ + public JdpJmxPacket(byte[] data) + throws JdpException { + JdpPacketReader reader; + + reader = new JdpPacketReader(data); + Map p = reader.getDiscoveryDataAsMap(); + + String sId = p.get(UUID_KEY); + this.id = (sId == null) ? null : UUID.fromString(sId); + this.jmxServiceUrl = p.get(JMX_SERVICE_URL_KEY); + this.mainClass = p.get(MAIN_CLASS_KEY); + this.instanceName = p.get(INSTANCE_NAME_KEY); + } + + /** + * Set main class field + * + * @param mainClass - main class of running app + */ + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + /** + * Set instance name field + * + * @param instanceName - name of instance as provided by customer + */ + public void setInstanceName(String instanceName) { + this.instanceName = instanceName; + } + + /** + * @return id of discovery session + */ + public UUID getId() { + return id; + } + + /** + * + * @return main class field + */ + public String getMainClass() { + return mainClass; + } + + /** + * + * @return JMX service URL + */ + public String getJmxServiceUrl() { + return jmxServiceUrl; + } + + /** + * + * @return instance name + */ + public String getInstanceName() { + return instanceName; + } + + /** + * + * @return assembled packet ready to be sent across a Net + * @throws IOException + */ + @Override + public byte[] getPacketData() throws IOException { + // Assemble packet from fields to byte array + JdpPacketWriter writer; + writer = new JdpPacketWriter(); + writer.addEntry(UUID_KEY, (id == null) ? null : id.toString()); + writer.addEntry(MAIN_CLASS_KEY, mainClass); + writer.addEntry(JMX_SERVICE_URL_KEY, jmxServiceUrl); + writer.addEntry(INSTANCE_NAME_KEY, instanceName); + return writer.getPacketBytes(); + } + + /** + * + * @return packet hash code + */ + @Override + public int hashCode() { + int hash = 1; + hash = hash * 31 + id.hashCode(); + hash = hash * 31 + jmxServiceUrl.hashCode(); + return hash; + } + + /** + * Compare two packets + * + * @param o - packet to compare + * @return either packet equals or not + */ + @Override + public boolean equals(Object o) { + + if (o == null || ! (o instanceof JdpJmxPacket) ){ + return false; + } + + JdpJmxPacket p = (JdpJmxPacket) o; + return Objects.equals(id, p.getId()) && Objects.equals(jmxServiceUrl, p.getJmxServiceUrl()); + } +} diff --git a/src/share/classes/sun/management/jdp/JdpPacket.java b/src/share/classes/sun/management/jdp/JdpPacket.java new file mode 100644 index 000000000..ba0ec4f97 --- /dev/null +++ b/src/share/classes/sun/management/jdp/JdpPacket.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012, 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. + */ + +package sun.management.jdp; + +import java.io.IOException; + +/** + * Packet to broadcast + * + *

Each packet have to contain MAGIC and PROTOCOL_VERSION in order to be + * recognized as a valid JDP packet.

+ * + *

Default implementation build packet as a set of UTF-8 encoded Key/Value pairs + * are stored as an ordered list of values, and are sent to the server + * in that order.

+ * + *

+ * Packet structure: + * + * 4 bytes JDP magic (0xC0FFE42) + * 2 bytes JDP protocol version (01) + * + * 2 bytes size of key + * x bytes key (UTF-8 encoded) + * 2 bytes size of value + * x bytes value (UTF-8 encoded) + * + * repeat as many times as necessary ... + *

+ */ +public interface JdpPacket { + + /** + * This method responsible to assemble packet and return a byte array + * ready to be sent across a Net. + * + * @return assembled packet as an array of bytes + * @throws IOException + */ + public byte[] getPacketData() throws IOException; + +} diff --git a/src/share/classes/sun/management/jdp/JdpPacketReader.java b/src/share/classes/sun/management/jdp/JdpPacketReader.java new file mode 100644 index 000000000..9f3957ab5 --- /dev/null +++ b/src/share/classes/sun/management/jdp/JdpPacketReader.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2012, 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. + */ +package sun.management.jdp; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * JdpPacketReader responsible for reading a packet

This class gets a byte + * array as it came from a Net, validates it and breaks a part

+ */ +public final class JdpPacketReader { + + private final DataInputStream pkt; + private Map pmap = null; + + /** + * Create packet reader, extract and check magic and version + * + * @param packet - packet received from a Net + * @throws JdpException + */ + public JdpPacketReader(byte[] packet) + throws JdpException { + ByteArrayInputStream bais = new ByteArrayInputStream(packet); + pkt = new DataInputStream(bais); + + try { + int magic = pkt.readInt(); + JdpGenericPacket.checkMagic(magic); + } catch (IOException e) { + throw new JdpException("Invalid JDP packet received, bad magic"); + } + + try { + short version = pkt.readShort(); + JdpGenericPacket.checkVersion(version); + } catch (IOException e) { + throw new JdpException("Invalid JDP packet received, bad protocol version"); + } + } + + /** + * Get next entry from packet + * + * @return the entry + * @throws EOFException + * @throws JdpException + */ + public String getEntry() + throws EOFException, JdpException { + + try { + short len = pkt.readShort(); + // Artificial setting the "len" field to Short.MAX_VALUE may cause a reader to allocate + // to much memory. Prevent this possible DOS attack. + if (len < 1 && len > pkt.available()) { + throw new JdpException("Broken JDP packet. Invalid entry length field."); + } + + byte[] b = new byte[len]; + if (pkt.read(b) != len) { + throw new JdpException("Broken JDP packet. Unable to read entry."); + } + return new String(b, "UTF-8"); + + } catch (EOFException e) { + throw e; + } catch (UnsupportedEncodingException ex) { + throw new JdpException("Broken JDP packet. Unable to decode entry."); + } catch (IOException e) { + throw new JdpException("Broken JDP packet. Unable to read entry."); + } + + + } + + /** + * return packet content as a key/value map + * + * @return map containing packet entries pair of entries treated as + * key,value + * @throws IOException + * @throws JdpException + */ + public Map getDiscoveryDataAsMap() + throws JdpException { + // return cached map if possible + if (pmap != null) { + return pmap; + } + + String key = null, value = null; + + final Map tmpMap = new HashMap<>(); + try { + while (true) { + key = getEntry(); + value = getEntry(); + tmpMap.put(key, value); + } + } catch (EOFException e) { + // EOF reached on reading value, report broken packet + // otherwise ignore it. + if (value == null) { + throw new JdpException("Broken JDP packet. Key without value." + key); + } + } + + pmap = Collections.unmodifiableMap(tmpMap); + return pmap; + } +} diff --git a/src/share/classes/sun/management/jdp/JdpPacketWriter.java b/src/share/classes/sun/management/jdp/JdpPacketWriter.java new file mode 100644 index 000000000..7ebd00260 --- /dev/null +++ b/src/share/classes/sun/management/jdp/JdpPacketWriter.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2012, 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. + */ +package sun.management.jdp; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * JdpPacketWriter responsible for writing a packet + *

This class assembles a set of key/value pairs to valid JDP packet, + * ready to be sent across a Net

+ */ +public final class JdpPacketWriter { + + private final ByteArrayOutputStream baos; + private final DataOutputStream pkt; + + /** + * Create a JDP packet, add mandatory magic and version headers + * + * @throws IOException + */ + public JdpPacketWriter() + throws IOException { + baos = new ByteArrayOutputStream(); + pkt = new DataOutputStream(baos); + + pkt.writeInt(JdpGenericPacket.getMagic()); + pkt.writeShort(JdpGenericPacket.getVersion()); + } + + /** + * Put string entry to packet + * + * @param entry - string to put (utf-8 encoded) + * @throws IOException + */ + public void addEntry(String entry) + throws IOException { + pkt.writeShort(entry.length()); + byte[] b = entry.getBytes("UTF-8"); + pkt.write(b); + } + + /** + * Put key/value pair to packet + * + * @param key - key to put (utf-8 encoded) + * @param val - value to put (utf-8 encoded) + * @throws IOException + */ + public void addEntry(String key, String val) + throws IOException { + /* Silently skip key if value is null. + * We don't need to distinguish between key missing + * and key has no value cases + */ + if (val != null) { + addEntry(key); + addEntry(val); + } + } + + /** + * Return assembled packet as a byte array + * + * @return packet bytes + */ + public byte[] getPacketBytes() { + return baos.toByteArray(); + } +} diff --git a/src/share/classes/sun/management/jdp/package-info.java b/src/share/classes/sun/management/jdp/package-info.java new file mode 100644 index 000000000..e21e46121 --- /dev/null +++ b/src/share/classes/sun/management/jdp/package-info.java @@ -0,0 +1,55 @@ +/** + * Summary + * ------- + * + * Define a lightweight network protocol for discovering running and + * manageable Java processes within a network subnet. + * + * + * Description + * ----------- + * + * The protocol is lightweight multicast based, and works like a beacon, + * broadcasting the JMXService URL needed to connect to the external JMX + * agent if an application is started with appropriate parameters. + * + * The payload is structured like this: + * + * 4 bytes JDP magic (0xC0FFEE42) + * 2 bytes JDP protocol version (1) + * 2 bytes size of the next entry + * x bytes next entry (UTF-8 encoded) + * 2 bytes size of next entry + * ... Rinse and repeat... + * + * The payload will be parsed as even entries being keys, odd entries being + * values. + * + * The standard JDP packet contains four entries: + * + * - `DISCOVERABLE_SESSION_UUID` -- Unique id of the instance; this id changes every time + * the discovery protocol starts and stops + * + * - `MAIN_CLASS` -- The value of the `sun.java.command` property + * + * - `JMX_SERVICE_URL` -- The URL to connect to the JMX agent + * + * - `INSTANCE_NAME` -- The user-provided name of the running instance + * + * The protocol sends packets to 239.255.255.225:7095 by default. + * + * The protocol uses system properties to control it's behaviour: + * - `com.sun.management.jdp.port` -- override default port + * + * - `com.sun.management.jdp.address` -- override default address + * + * - `com.sun.management.jmxremote.autodiscovery` -- whether we should start autodiscovery or + * not. Autodiscovery starts if and only if following conditions are met: (autodiscovery is + * true OR (autodiscovery is not set AND jdp.port is set)) + * + * - `com.sun.management.jdp.ttl` -- set ttl for broadcast packet, default is 1 + * - `com.sun.management.jdp.pause` -- set broadcast interval in seconds default is 5 + * - `com.sun.management.jdp.source_addr` -- an address of interface to use for broadcast + */ + +package sun.management.jdp; diff --git a/test/sun/management/jdp/JdpClient.java b/test/sun/management/jdp/JdpClient.java new file mode 100644 index 000000000..4a2348468 --- /dev/null +++ b/test/sun/management/jdp/JdpClient.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2012, 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 java.io.IOException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.ProtocolFamily; +import java.net.StandardProtocolFamily; +import java.net.StandardSocketOptions; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Collections; +import java.util.Enumeration; +import sun.management.jdp.JdpException; +import sun.management.jdp.JdpJmxPacket; + +public class JdpClient { + + private static class PacketListener implements Runnable { + + private static final int BUFFER_LENGTH = 4096; + private final DatagramChannel channel; + private static int maxPacketCount = 1; + private static int maxEmptyPacketCount = 10; + + + PacketListener(DatagramChannel channel) { + this.channel = channel; + } + + @java.lang.Override + public void run() { + try { + Selector sel; + sel = Selector.open(); + channel.configureBlocking(false); + channel.register(sel, SelectionKey.OP_READ); + ByteBuffer buf = ByteBuffer.allocate(1024); + + int count = 1; + int emptyPacketsCount = 1; + + try { + while (true) { + + sel.selectedKeys().clear(); + buf.rewind(); + + sel.select(10 * 1000); + channel.receive(buf); + + if (buf.position() == 0 ){ + if (JdpDoSomething.getVerbose()){ + System.err.println("Empty packet received"); + } + if (++emptyPacketsCount > maxEmptyPacketCount){ + throw new RuntimeException("Test failed, maxEmptyPacketCount reached"); + } + + continue; + } + + buf.flip(); + byte[] dgramData = new byte[buf.remaining()]; + buf.get(dgramData); + + try { + JdpJmxPacket packet = new JdpJmxPacket(dgramData); + JdpDoSomething.printJdpPacket(packet); + if(++count > maxPacketCount){ + break; + } + } catch (JdpException e) { + e.printStackTrace(); + throw new RuntimeException("Test failed"); + } + + } + + System.out.println("OK: Test passed"); + + } finally { + sel.close(); + channel.close(); + } + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Test failed"); + } + } + } + + public static void main(String[] args) { + try { + String discoveryPort = System.getProperty("com.sun.management.jdp.port"); + String discoveryAddress = System.getProperty("com.sun.management.jdp.address"); + if (discoveryAddress == null || discoveryPort == null) { + System.out.println("Test failed. address and port must be specified"); + return; + } + + int port = Integer.parseInt(discoveryPort); + InetAddress address = InetAddress.getByName(discoveryAddress); + + + ProtocolFamily family = (address instanceof Inet6Address) + ? StandardProtocolFamily.INET6 : StandardProtocolFamily.INET; + + DatagramChannel channel; + + channel = DatagramChannel.open(family); + channel.setOption(StandardSocketOptions.SO_REUSEADDR, true); + channel.bind(new InetSocketAddress(port)); + + Enumeration nets = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface interf : Collections.list(nets)) { + if (interf.supportsMulticast()) { + try { + channel.join(address, interf); + } catch (IOException e) { + // Skip not configured interfaces + } + } + } + + PacketListener listener = new PacketListener(channel); + new Thread(listener, "Jdp Client").start(); + + } catch (RuntimeException e){ + System.out.println("Test failed."); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Test failed. unexpected error " + e); + } + } +} diff --git a/test/sun/management/jdp/JdpDoSomething.java b/test/sun/management/jdp/JdpDoSomething.java new file mode 100644 index 000000000..ad9366cba --- /dev/null +++ b/test/sun/management/jdp/JdpDoSomething.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2012, 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 java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Objects; + +import sun.management.jdp.JdpJmxPacket; +import sun.management.jdp.JdpException; + +public class JdpDoSomething { + + private static final String lockFileName = "JdpDoSomething.lck"; + private static final boolean verbose=false; + + public static boolean getVerbose(){ + return verbose; + } + + public static void printJdpPacket(JdpJmxPacket p) { + if (getVerbose()) { + try { + RandomAccessFile f = new RandomAccessFile("out.dmp", "rw"); + f.write(p.getPacketData()); + f.close(); + } catch (IOException e) { + System.out.println("Can't write a dump file: " + e); + } + + System.out.println("Id: " + p.getId()); + System.out.println("Jmx: " + p.getJmxServiceUrl()); + System.out.println("Main: " + p.getMainClass()); + System.out.println("InstanceName: " + p.getInstanceName()); + + System.out.flush(); + } + } + + public static void compaireJdpPacketEx(JdpJmxPacket p1, JdpJmxPacket p2) + throws JdpException { + + if (!Objects.equals(p1, p1)) { + throw new JdpException("Packet mismatch error"); + } + + if (!Objects.equals(p1.getMainClass(), p2.getMainClass())) { + throw new JdpException("Packet mismatch error (main class)"); + } + + if (!Objects.equals(p1.getInstanceName(), p2.getInstanceName())) { + throw new JdpException("Packet mismatch error (instance name)"); + } + } + + public static void doSomething() { + try { + File lockFile = new File(lockFileName); + lockFile.createNewFile(); + + while (lockFile.exists()) { + long datetime = lockFile.lastModified(); + long epoch = System.currentTimeMillis() / 1000; + + // Don't allow test app to run more than an hour + if (epoch - datetime > 3600) { + System.err.println("Lock is too old. Aborting"); + return; + } + Thread.sleep(1); + } + + } catch (Throwable e) { + System.err.println("Something bad happens:" + e); + } + } + + public static void main(String args[]) throws Exception { + System.err.println("main enter"); + doSomething(); + System.err.println("main exit"); + } +} diff --git a/test/sun/management/jdp/JdpTest.sh b/test/sun/management/jdp/JdpTest.sh new file mode 100644 index 000000000..2aded7299 --- /dev/null +++ b/test/sun/management/jdp/JdpTest.sh @@ -0,0 +1,323 @@ +#!/bin/sh + +# Copyright (c) 2011, 2012 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. + +# @test +# @bug 7169888 +# @build JdpUnitTest JdpClient JdpDoSomething +# @run shell JdpTest.sh --jtreg --no-compile +# @summary No word Failed expected in the test output + +_verbose=no +_jtreg=no +_compile=yes + +# temporary disable jcmd related tests +# _testsuite="01,02,03,04,05" +_testsuite="01,02,04" + +_pwd=`pwd` + +_testclasses=".classes" +_testsrc="${_pwd}" +_lockFileName="JdpDoSomething.lck" + +_logname=".classes/output.txt" +_last_pid="" + + +_compile(){ + + if [ ! -e ${_testclasses} ] + then + mkdir -p ${_testclasses} + fi + + rm -f ${_testclasses}/*.class + + # Compile testcase + ${TESTJAVA}/bin/javac -d ${_testclasses} JdpUnitTest.java \ + JdpDoSomething.java \ + JdpClient.java + + + if [ ! -e ${_testclasses}/JdpDoSomething.class -o ! -e ${_testclasses}/JdpClient.class -o ! -e ${_testclasses}/JdpUnitTest.class ] + then + echo "ERROR: Can't compile" + exit -1 + fi +} + + +_app_start(){ + + testappname=$1 + shift + + ${TESTJAVA}/bin/java -server $* -cp ${_testclasses} ${testappname} >> ${_logname} 2>&1 & + _last_pid=$! + + npid=`_get_pid` + if [ "${npid}" = "" ] + then + echo "ERROR: Test app not started" + if [ "${_jtreg}" = "yes" ] + then + exit -1 + fi + fi +} + +_get_pid(){ + ${TESTJAVA}/bin/jps | sed -n "/Jdp/s/ .*//p" +} + +_app_stop(){ + rm ${_lockFileName} + +# wait until VM is actually shuts down + while true + do + npid=`_get_pid` + if [ "${npid}" = "" ] + then + break + fi + sleep 1 + done +} + +_testme(){ + ${TESTJAVA}/bin/java \ + -cp ${_testclasses} \ + $* \ + -Dcom.sun.management.jdp.port=7095 \ + -Dcom.sun.management.jdp.address=239.255.255.225 \ + JdpClient + +} + + +_jcmd(){ + ${TESTJAVA}/bin/jcmd JdpDoSomething $* > /dev/null 2>/dev/null +} + + +_echo(){ + echo "$*" + echo "$*" >> ${_logname} +} + +# ============= TESTS ====================================== + +test_01(){ + + _echo "**** Test one ****" + + _app_start JdpUnitTest \ + -Dcom.sun.management.jdp.port=7095 \ + -Dcom.sun.management.jdp.address=239.255.255.225 \ + -Dcom.sun.management.jdp.pause=5 + + res=`_testme` + + case "${res}" in + OK*) + _echo "Passed" + ;; + *) + _echo "Failed!" + ;; + esac + + _app_stop +} + +test_02(){ + + _echo "**** Test two ****" + + _app_start JdpDoSomething \ + -Dcom.sun.management.jdp.port=7095 \ + -Dcom.sun.management.jdp.address=239.255.255.225 \ + -Dcom.sun.management.jdp.pause=5 \ + -Dcom.sun.management.jmxremote.port=4545 \ + -Dcom.sun.management.jmxremote.authenticate=false \ + -Dcom.sun.management.jmxremote.ssl=false + + res=`_testme` + + case "${res}" in + OK*) + _echo "Passed" + ;; + *) + _echo "Failed!" + ;; + esac + + _app_stop +} + +test_03(){ + + _echo "**** Test three ****" + + _app_start JdpDoSomething + + _jcmd ManagementAgent.start\ + jdp.port=7095 \ + jdp.address=239.255.255.225 \ + jdp.pause=5 \ + jmxremote.port=4545 \ + jmxremote.authenticate=false \ + jmxremote.ssl=false + + res=`_testme` + + case "${res}" in + OK*) + _echo "Passed" + ;; + *) + _echo "Failed!" + ;; + esac + + _app_stop +} + +test_04(){ + + _echo "**** Test four ****" + + _app_start JdpDoSomething \ + -Dcom.sun.management.jmxremote.autodiscovery=true \ + -Dcom.sun.management.jmxremote.port=4545 \ + -Dcom.sun.management.jmxremote.authenticate=false \ + -Dcom.sun.management.jmxremote.ssl=false + + res=`_testme` + + case "${res}" in + OK*) + _echo "Passed" + ;; + *) + _echo "Failed!" + ;; + esac + + _app_stop +} + +test_05(){ + + _echo "**** Test five ****" + + _app_start JdpDoSomething + + _jcmd ManagementAgent.start\ + jmxremote.autodiscovery=true \ + jmxremote.port=4545 \ + jmxremote.authenticate=false \ + jmxremote.ssl=false + + + res=`_testme` + + case "${res}" in + OK*) + _echo "Passed" + ;; + *) + _echo "Failed!" + ;; + esac + + _app_stop +} + + +# ============= MAIN ======================================= + +if [ "x${TESTJAVA}" = "x" ] +then + echo "TESTJAVA env have to be set" + exit +fi + +#------------------------------------------------------------------------------ +# reading parameters + +for parm in "$@" +do + case $parm in + --verbose) _verbose=yes ;; + --jtreg) _jtreg=yes ;; + --no-compile) _compile=no ;; + --testsuite=*) _testsuite=`_echo $parm | sed "s,^--.*=\(.*\),\1,"` ;; + *) + echo "Undefined parameter $parm. Try --help for help" + exit + ;; + esac +done + +if [ ${_compile} = "yes" ] +then + _compile +fi + +if [ ${_jtreg} = "yes" ] +then + _testclasses=${TESTCLASSES} + _testsrc=${TESTSRC} + _logname="output.txt" +fi + +# Make sure _tesclasses is absolute path +tt=`echo ${_testclasses} | sed -e 's,/,,'` +if [ ${tt} = ${_testclasses} ] +then + _testclasses="${_pwd}/${_testclasses}" +fi + +_policyname="${_testclasses}/policy" + +rm -f ${_logname} +rm -f ${_policyname} + +if [ -e ${_testsrc}/policy.tpl ] +then + +cat ${_testsrc}/policy.tpl | \ + sed -e "s,@_TESTCLASSES@,${_testclasses},g" -e "s,@TESTJAVA@,${TESTJAVA},g" \ + > ${_policyname} + +fi + +# Local mode tests +for i in `echo ${_testsuite} | sed -e "s/,/ /g"` +do + test_${i} +done diff --git a/test/sun/management/jdp/JdpUnitTest.java b/test/sun/management/jdp/JdpUnitTest.java new file mode 100644 index 000000000..fed4ae216 --- /dev/null +++ b/test/sun/management/jdp/JdpUnitTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2012, 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 java.io.IOException; +import java.net.InetAddress; +import java.util.UUID; + +import sun.management.jdp.JdpController; +import sun.management.jdp.JdpPacket; +import sun.management.jdp.JdpJmxPacket; +import sun.management.jdp.JdpException; + +public class JdpUnitTest { + + /** + * This test tests that complete packet is build correctly + */ + public static void PacketBuilderTest() + throws IOException, JdpException { + + /* Complete packet test */ + { + JdpJmxPacket p1 = new JdpJmxPacket(UUID.randomUUID(), "fake://unit-test"); + p1.setMainClass("FakeUnitTest"); + p1.setInstanceName("Fake"); + byte[] b = p1.getPacketData(); + + JdpJmxPacket p2 = new JdpJmxPacket(b); + JdpDoSomething.printJdpPacket(p1); + JdpDoSomething.compaireJdpPacketEx(p1, p2); + } + + /*Missed field packet test*/ + { + JdpJmxPacket p1 = new JdpJmxPacket(UUID.randomUUID(), "fake://unit-test"); + p1.setMainClass("FakeUnitTest"); + p1.setInstanceName(null); + byte[] b = p1.getPacketData(); + + JdpJmxPacket p2 = new JdpJmxPacket(b); + JdpDoSomething.printJdpPacket(p1); + JdpDoSomething.compaireJdpPacketEx(p1, p2); + } + + System.out.println("OK: Test passed"); + + } + + public static void startFakeDiscoveryService() + throws IOException, JdpException { + + String discoveryPort = System.getProperty("com.sun.management.jdp.port"); + String discoveryAddress = System.getProperty("com.sun.management.jdp.address"); + InetAddress address = InetAddress.getByName(discoveryAddress); + int port = Integer.parseInt(discoveryPort); + JdpController.startDiscoveryService(address, port, "FakeDiscovery", "fake://unit-test"); + } + + public static void main(String[] args) { + try { + PacketBuilderTest(); + startFakeDiscoveryService(); + JdpDoSomething.doSomething(); + + } catch (Throwable e) { + e.printStackTrace(); + System.out.println("Test failed. unexpected error " + e); + } + } +} -- GitLab