brooklyn.util.jmx.jmxrmi.JmxRmiAgent Maven / Gradle / Ivy
Show all versions of brooklyn-jmxrmi-agent Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package brooklyn.util.jmx.jmxrmi;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.registry.LocateRegistry;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import javax.management.MBeanServer;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
/**
* This exposes JMX support for going through firewalls by starting an RMI registry server
* on a well-known port.
*
* This implementation DOES NOT support port-forwarding however. The same hostname used internally
* (specified in {@link #RMI_HOSTNAME_PROPERTY} or autodetected by java) must also be addressable
* by the JMX client. This is due to how the property is used internally by java during the
* RMI registry re-direction.
*
* If you require that the client connects to a different hostname/IP than the one where the
* service is bound, consider using the Brooklyn JmxmpAgent, as this will not work!
*
* This listens on {@value #RMI_REGISTRY_PORT_PROPERTY} unless overridden by system property
* {@link #RMI_REGISTRY_PORT_PROPERTY} ({@value #RMI_REGISTRY_PORT_PROPERTY}).
*
* @see brooklyn.util.jmx.jmxmp.JmxmpAgent
* @see https://blogs.oracle.com/jmxetc/entry/connecting_through_firewall_using_jmx
* @see https://blogs.oracle.com/jmxetc/entry/more_on_premain_and_jmx
*/
public class JmxRmiAgent {
/** Port for RMI registry to listen on. Default to {@link #RMI_REGISTRY_DEFAULT_PORT}. */
public static final String RMI_REGISTRY_PORT_PROPERTY = "brooklyn.jmx-agent.rmi-port";
public static final String RMI_REGISTRY_DEFAULT_PORT = "9001";
/** Port for JMX server (sometimes called JMX_RMI server) to listen on. Default to {@link #JMX_SERVER_DEFAULT_PORT}. */
public static final String JMX_SERVER_PORT_PROPERTY = "brooklyn.jmx-agent.jmx-port";
public static final String JMX_SERVER_DEFAULT_PORT = "11099";
/** Hostname to advertise, and if {@value #JMX_SERVER_ADDRESS_WILDCARD_PROPERTY} is false also the hostname/interface to bind to.
* Should never be 0.0.0.0 as it is publicly advertised. */
public static final String RMI_HOSTNAME_PROPERTY = "java.rmi.server.hostname";
/** Whether JMX should bind to all interfaces. */
public static final String JMX_SERVER_ADDRESS_WILDCARD_PROPERTY = "jmx.remote.server.address.wildcard";
/**
* The entry point, uses the JDK dynamic agent loading feature.
*/
public static void premain(String agentArgs) {
doMain(agentArgs);
}
public static void agentmain(String agentArgs) {
doMain(agentArgs);
}
public static void doMain(final String agentArgs) {
// taken from JmxmpAgent in sister project
// do the work in a daemon thread so that if the main class terminates abnormally,
// such that shutdown hooks aren't called, we don't keep the application running
// (e.g. if the app is compiled with java7 then run with java6, with a java6 agent here;
// that causes the agent to launch, the main to fail, but the process to keep going)
Thread t = new Thread() {
public void run() {
doMainForeground(agentArgs);
}
};
t.setDaemon(true);
t.start();
}
public static void doMainForeground(String agentArgs) {
final JMXConnectorServer connector = new JmxRmiAgent().startServer(System.getProperties());
if (connector != null) {
Runtime.getRuntime().addShutdownHook(new Thread("jmxrmi-agent-shutdownHookThread") {
@Override public void run() {
try {
connector.stop();
} catch (Exception e) {
System.err.println("Error closing jmxrmi connector in shutdown hook (continuing): "+e);
}
}});
}
}
public JMXConnectorServer startServer(Properties properties) {
try {
// Ensure cryptographically strong random number generator used
// to choose the object number - see java.rmi.server.ObjID
System.setProperty("java.rmi.server.randomIDs", "true");
// Start an RMI registry on port specified
final int rmiPort = Integer.parseInt(System.getProperty(RMI_REGISTRY_PORT_PROPERTY, RMI_REGISTRY_DEFAULT_PORT));
final int jmxPort = Integer.parseInt(System.getProperty(JMX_SERVER_PORT_PROPERTY, JMX_SERVER_DEFAULT_PORT));
final String hostname = getLocalhostHostname(properties);
System.out.println("Setting up JmxRmiAgent for: "+hostname+" "+rmiPort+" / "+jmxPort);
LocateRegistry.createRegistry(rmiPort);
// Retrieve the PlatformMBeanServer.
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// Environment map.
Map env = new LinkedHashMap();
propagate(properties, env, JMX_SERVER_ADDRESS_WILDCARD_PROPERTY, "true");
// TODO Security
// Create an RMI connector server.
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://" + hostname + ":" + jmxPort + "/jndi/rmi://" + hostname + ":" + rmiPort + "/jmxrmi");
// Now create the server from the JMXServiceURL
JMXConnectorServer connector = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
// Start the RMI connector server.
connector.start();
System.out.println("JmxRmiAgent JMXConnectorServer active at: " + url);
return connector;
} catch (RuntimeException e) {
System.err.println("Unable to start JMXConnectorServer: " + e);
throw e;
} catch (Exception e) {
System.err.println("Unable to start JMXConnectorServer: " + e);
throw new RuntimeException(e);
}
}
/**
* Copies the value of key from the source to the target, if set. Otherwise
* sets the {@code defaultValueIfNotNull} if that is not null.
*
* @return whether anything is set
*/
private static boolean propagate(Properties source, Map target, String key, Object defaultValueIfNotNull) {
Object v = source.getProperty(key);
if (v == null) v = defaultValueIfNotNull;
if (v == null) return false;
target.put(key, v);
return true;
}
private String getLocalhostHostname(Properties properties) throws UnknownHostException {
String hostname = properties == null ? null : properties.getProperty(RMI_HOSTNAME_PROPERTY);
if ("0.0.0.0".equals(hostname)) {
System.err.println("WARN: invalid hostname 0.0.0.0 specified for JmxRmiAgent; " +
"it typically must be an address or hostname which is bindable on the machine where " +
"this service is running AND accessible by a client machine (access will likely be impossible)");
}
if (hostname == null || hostname.isEmpty()) {
hostname = InetAddress.getLocalHost().getHostName();
}
return hostname;
}
/**
* Convenience main method.
*/
public static void main(String[] args) throws Exception {
premain("");
}
}