|
|
Proxying a JMX Connection: Adapting To Another Protocol |
We have found some interoperation problems in the JMX RMI/IIOP
connector, or more exactly, in the way some JMX classes
from the javax.management.modelmbean package are deserialized
by the JMX RMI/IIOP stack.
The bug is strictly linked to the IIOP
stack and doesn't show up with the regurar RMI/JRMP protocol
connector.
Nevertheless, there are some JDK 5 applications
out there which
use the JMX RMI/IIOP connector to expose Model MBeans through
JMX. When connecting to such a JDK 5 application with
JDK 6 jconsole, through the IIOP connector, the tool will
spit out ominous stack traces on the terminal console and the MBean Tab
will not display any of the exposed Model MBeans (other types of
MBeans are correctly displayed).
Until such a time where the bug gets fixed, I can
suggest two work around:
- Use jconsole from JDK 5 on the client side, or
- Creates a JDK 5 proxy that will use RMI/IIOP on the
south side to connect to the JDK 5 application, and
will open a JMX RMI/JRMP connector on the north
side to allow JDK 6 clients to use RMI/JRMP instead of
RMI/IIOP.
|
You can run the proxy on the client side, so there's no need
to modify the server application. The proxy will simply
take any JMX RMI/JRMP requests it receives from the north
side and translate them into corresponding
JMX RMI/IIOP requests
to the south side.
A connector server is usually associated with an MBean Server
obtained using the methods
ManagementFactory.getPlatformMBeanServer()
or MBeanServerFactory.createMBeanServer(). But in fact it can be
associated with any object that implements the MBeanServer interface.
Here, we create such an object (an instance of MBeanServerProxyHandler in the
code below) where every method in the
MBeanServer interface is forwarded to the remote MBeanServer using
the RMI/IIOP connector. There are a few MBeanServer methods that are
not available remotely, but that will still be called by the
JMX Connector Server when deserialiazing attribute values and
parameters sent by the client, so we need to fake those methods with
code that does something reasonable.
|
Below is the implementation of such a proxy - which uses
a java.reflect.Proxy to achieve this trick. Simply compile
the class and run it with JDK 5 (do not run it with JDK 6):
$JDK5_HOME/java com.sun.jmx.example.proxy.RMIIIOPProxy \
<rmi-port (north)> <rmi-lookup-name (north)> \
<iiop-host (south)> <iiop-port (south)> <iiop-lookup-name (south)>
You can then simply point your JDK client to
service:jmx:rmi:///jndi/rmi://<host>:<rmi-port>/<rmi-lookup-name>
and the proxy will translate and forward all your JDK 6 client
requests through IIOP to the south side...
This is not an ideal situation, but you might be able to use that as
a temporary work around.
package com.sun.jmx.example.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.loading.ClassLoaderRepository;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
@author
public class RMIIIOPProxy {
private static final Logger LOG =
Logger.getLogger(RMIIIOPProxy.class.getName());
private static class ProxyMethodMap {
static final Map<Method,Method> methodMap;
static {
try {
final Method[] mm =
MBeanServerConnection.class.getMethods();
methodMap = new HashMap<Method,Method>(mm.length);
for (Method m : mm) {
try {
final Method ms =
MBeanServer.class.getMethod(m.getName(),
m.getParameterTypes());
methodMap.put(ms,m);
} catch (Exception x) {
}
}
} catch (Exception x) {
throw new ExceptionInInitializerError(x);
}
}
}
private static class MBeanServerProxyHandler
implements InvocationHandler {
final MBeanServerConnection conn;
final ClassLoaderRepository classLoaderRepository;
public MBeanServerProxyHandler(MBeanServerConnection conn) {
this.conn=conn;
classLoaderRepository = new ClassLoaderRepository() {
public Class loadClass(String className)
throws ClassNotFoundException {
return Class.forName(className,false,
ClassLoader.getSystemClassLoader());
}
public Class loadClassBefore(ClassLoader stop, String className)
throws ClassNotFoundException {
return loadClass(className);
}
public Class loadClassWithout(ClassLoader exclude,
String className) throws ClassNotFoundException {
return loadClass(className);
}
};
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
final Method toInvoke = ProxyMethodMap.methodMap.get(method);
if (toInvoke != null) {
try {
return toInvoke.invoke(conn,args);
} catch (InvocationTargetException x) {
throw x.getCause();
}
}
final String name = method.getName();
final Class[] sig = method.getParameterTypes();
if ("equals".equals(name) && sig.length == 1
&& Object.class.equals(sig[0])) {
return super.equals(args[0]);
}
if ("hashCode".equals(name) && sig.length == 0) {
return super.hashCode();
}
if ("toString".equals(name) && sig.length == 0) {
return super.toString();
}
if ("getClassLoaderFor".equals(name) && sig.length == 1
&& ObjectName.class.equals(sig[0])) {
return ClassLoader.getSystemClassLoader();
}
if ("getClassLoader".equals(name) && sig.length == 1
&& ObjectName.class.equals(sig[0])) {
return ClassLoader.getSystemClassLoader();
}
if ("getClassLoaderRepository".equals(name) && sig.length == 0) {
return classLoaderRepository;
}
throw new UnsupportedOperationException(name+Arrays.asList(sig));
}
}
@paramargs
public static void main(String[] args) throws Exception {
if (args.length < 5) {
System.err.println("syntax: java "+RMIIIOPProxy.class.getName()+
" <rmiport> <rmi-server-name> <iiophost> <iiopport> <iiop-server-name>");
System.exit(1);
}
final int rmiport = Integer.parseInt(args[0]);
final String rmiServerName = args[1];
final String iiophost = args[2];
final int iiopport = Integer.parseInt(args[3]);
final String iiopServerName = args[4];
if (rmiport < 1) throw new IllegalArgumentException(args[0]);
if (iiopport < 1) throw new IllegalArgumentException(args[3]);
final JMXServiceURL rmi =
new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:"
+rmiport+"/"+rmiServerName);
final Map<String,Object> northEnv = Collections.emptyMap();
final JMXServiceURL iiop =
new JMXServiceURL("service:jmx:iiop:///jndi/iiop://"+iiophost+":"
+iiopport+"/"+iiopServerName);
final Map<String,Object> southEnv = Collections.emptyMap();
final JMXConnector south = JMXConnectorFactory.connect(iiop,southEnv);
try {
final MBeanServerProxyHandler proxyHandler =
new MBeanServerProxyHandler(south.getMBeanServerConnection());
final MBeanServer remote = (MBeanServer) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[] {MBeanServer.class}, proxyHandler);
System.out.println("Connection established with south server at: "+
iiop);
LocateRegistry.createRegistry(rmiport);
final JMXConnectorServer cs =
JMXConnectorServerFactory.newJMXConnectorServer(rmi,
northEnv,remote);
cs.start();
System.out.println("Proxy server listening at: "+rmi);
System.out.println("Strike <Enter> to exit");
System.in.read();
cs.stop();
} finally {
south.close();
}
}
}
|
Cheers, and my thanks to
Eamonn for his editorial review!
-- daniel
Tags:
java
jconsole
jmx
Posted by dfuchs
( Nov 12 2007, 06:52:14 PM CET )
Permalink
|
Hello Daniel, first of all thanks you for providing this article, My question is will it be possible to proxy multiple JMX Instances through single JMX Proxy Server if so what and how would you recommend ?
Posted by Arron on April 17, 2008 at 04:59 PM CEST #
Hi Arron,
It depends on what you mean by "proxying".
If it's the kind of proxying described above then you can start as many proxies as you want in the same JVM.
You would only need to rework a bit the main() method above in order to allow to pass several proxy configurations, and then create one proxy per such configuration.
If what you have in mind is rather federating several servers views into a single MBeanServer, then what you need is something like cascading/MBeanServer federation.
There's an MBeanServer federation feature planned for JMX 2.0 which will allow you to do that, but we haven't released any prototype yet. You could also use OpenDMK cascading - but it might not be scalable enough - depending on how many MBeans and servers you federate.
Hope this helps,
-- daniel
Posted by daniel on April 17, 2008 at 05:58 PM CEST #
Hello Daniel,
Thanks for rapid reply,
I am actually trying to Act as a Proxy Server infront of over 100 JMX Servers, each having their own security models and protocols, i.e some use JMXMP and others use RMI.
you can say I am trying to have a fedaration feature, What I am trying to do is to enable clients(GUI) connect to a JMX Server without knowing where the server is located (i.e IP and port )but just let them select the server they wish to connect to by an ID and query methods by providing Username and Password This way I will not have to open JMX Servers to public and be able to use private IP addresses and still be able to connect to them etc as well as having single control panel managing multiple Servers.
If I were to develop my own way of accomplishing this, what would you recommend the best solution be as I have very limited amount of time and waiting for JMX2.0 will not be reasonable for me.
i.e I could create an HTTP Server that lets you query the JMX Servers or to Create an JMX Server in proxy itself and register couple of MBeans that keeps the state of Sessions and forward the requests to required Server? What would you say best option will be? will the latter option be able to handle quite few amount of connections lets say 100 or maybe 1000 at a given time?.
Your ideas are greatly appreciated.
Regards
Arron.
Posted by Arron on April 17, 2008 at 06:43 PM CEST #