/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.jmx;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.net.URI;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.ExportException;
import java.util.HashMap;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.management.remote.JMXAuthenticator;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import org.apache.log4j.Logger;
import org.objectweb.proactive.core.util.ProActiveInet;
import org.objectweb.proactive.core.util.URIBuilder;
import org.ow2.proactive.authentication.Authentication;
import org.ow2.proactive.jmx.naming.JMXTransportProtocol;
import org.ow2.proactive.jmx.provider.JMXProviderUtils;
/**
* This helper class represents the common JMX monitoring infrastructure.
* Two levels of JMX monitoring are possible using two separate MBeans, one for
* anonymous users and one for administrators.
* <p>
* Sub-classes must register these MBeans into the MBean server then provide an {@link ObjectName} for each MBean.
* <p>
* This helper exposes two connector servers, one over RMI and one over RO (Remote Objects). Each
* connector server exposes the anonymous MBean for anonymous users and same for admin users.
* <p>
* The service url of the connector server over RMI for anonymous access is built like
* <code>service:jmx:rmi:///jndi/rmi://HOSTNAME_OR_IP:PORT/NAME</code> where
* <ul>
* <li><code>HOSTNAME_OR_IP</code> is specified by the ProActive network configuration.
* <li><code>PORT</code> is provided by sub-classes by the {@link #getJMXRMIConnectorServerPort()} method.
* <li><code>NAME</code> is provided by sub-classes by the {@link #getConnectorServerName()} method.
* </ul>
* <p>
* Once booted with {@link #boot(Authentication)} infrastructure can be shutdown with the {@link #shutdown()} method.
*
* @author The ProActive Team
* @since ProActive Scheduling 1.0
*/
public abstract class AbstractJMXHelper {
/** The logger provided by sub-classes */
private final Logger logger;
/** The standard JMX over RMI connector server */
private JMXConnectorServer rmiCs;
/** The custom JMX over RO connector server */
private JMXConnectorServer roCs;
/** The reason of the failure in case on unsuccessful boot sequence of the JMX RMI */
private String jmxRmiFailureReason;
/** The reason of the failure in case on unsuccessful boot sequence of the JMX RO */
private String jmxRoFailureReason;
/** RRD data base dumper */
private RRDDataStore dataStore;
/** Can be called only by sub-classes */
protected AbstractJMXHelper() {
this.logger = Logger.getLogger(AbstractJMXHelper.class);
}
/** Can be called only by sub-classes */
protected AbstractJMXHelper(final Logger logger) {
this.logger = logger;
}
/**
* Starts the boot sequence of of the JMX monitoring infrastructure with a new JMX server.
* <p>
* <ul>
* <li>Creates a new RMI registry or reuses an existing one needed for the JMX RMI connector server.
* <li>Create a a single MBean server for both administrator users and for anonymous users.
* <li>Registers the MBeans into the MBean server.
* <li>Creates and starts the connector servers, one over RMI and one over RO.
* </ul>
*
* @param auth the object responsible for authentication
* @return <code>true</code> if the boot sequence was successful (at least one of two connector servers were started), otherwise returns <code>false</code>
*/
public final boolean boot(final Authentication auth) {
return boot(auth, true, null);
}
/**
* Starts the boot sequence of of the JMX monitoring infrastructure.
* <p>
* <ul>
* <li>Creates a new RMI registry or reuses an existing one needed for the JMX RMI connector server.
* <li>Create a a single MBean server for both administrator users and for anonymous users.
* <li>Registers the MBeans into the MBean server.
* <li>Creates and starts the connector servers, one over RMI and one over RO.
* </ul>
*
* @param auth the object responsible for authentication
* @param createNewServer defined is new JMX server will be created or default will be used.
* @param permissionChecker additional permission checker
* @return <code>true</code> if the boot sequence was successful (at least one of two connector servers were started), otherwise returns <code>false</code>
*/
public final boolean boot(final Authentication auth, boolean createNewServer, PermissionChecker permissionChecker) {
// Create a single MBean server
MBeanServer mbs = null;
try {
if (createNewServer) {
mbs = MBeanServerFactory.createMBeanServer();
} else {
mbs = ManagementFactory.getPlatformMBeanServer();
}
} catch (Exception e) {
logger.error(jmxRmiFailureReason = jmxRoFailureReason = "Unable to create the JMX MBean server", e);
return false;
}
// Create an authenticator that will be used by the connectors
final JMXAuthenticator authenticator = new JMXAuthenticatorImpl(auth, permissionChecker);
// Let sub-classes create a the MBean server forwarder
this.registerMBeans(mbs);
// Sub-classes provides the name of the connector server and the port
final String serverName = this.getConnectorServerName();
final int port = this.getJMXRMIConnectorServerPort();
// Boot the JMX RMI connector server
final boolean isJMXRMIbooted = this.createJMXRMIConnectorServer(mbs, serverName, port, authenticator);
if (isJMXRMIbooted) {
try {
this.rmiCs.start();
if (logger.isInfoEnabled()) {
logger.info("Started JMX RMI connector server at " + this.rmiCs.getAddress());
}
} catch (Exception e) {
logger.error(this.jmxRoFailureReason = "Unable to start the JMX RMI connector server", e);
}
}
// Boot the JMX RO connector server
final boolean isJMXRObooted = this.createJMXROConnectorServer(mbs, serverName, authenticator);
if (isJMXRObooted) {
try {
this.roCs.start();
if (logger.isInfoEnabled()) {
logger.info("Started JMX RO connector server at " + this.roCs.getAddress());
}
} catch (Exception e) {
logger.error(this.jmxRoFailureReason = "Unable to start the JMX RO connector server", e);
}
}
return isJMXRMIbooted || isJMXRObooted;
}
/**
* Sub-class must register the MBeans into the MBean server.
*
* @param mbs the MBean server
*/
public abstract void registerMBeans(final MBeanServer mbs);
/**
* Sub-classes must provide the name of the connector server.
*
* @return the name of the connector server
*/
public abstract String getConnectorServerName();
/**
* Sub-classes must provide the port to be used by the JMX RMI connector server.
* with correct values.
*
* @return the JMX RMI connector server port
*/
public abstract int getJMXRMIConnectorServerPort();
private boolean createJMXRMIConnectorServer(final MBeanServer mbs, final String connectorServerName, final int port,
final JMXAuthenticator authenticator) {
// Create or reuse an RMI registry needed for the connector server
try {
LocateRegistry.createRegistry(port);
} catch (ExportException ee) {
// Reusing existing registry
// do nothing and continue starting JMX
if (logger.isDebugEnabled()) {
logger.debug("Reusing existing RMI registry on port " + port);
}
} catch (RemoteException e) {
// This can occur if the port is already occupied
logger.error(jmxRmiFailureReason = "Unable to create an RMI registry on port " + port, e);
// do not start JMX service
return false;
}
// Use the same hostname as ProActive (follows properties defined by ProActive configuration)
final String hostname = ProActiveInet.getInstance().getHostname();
// The asked address of the new connector server. The actual address can be different due to
// JMX specification. See {@link JMXConnectorServerFactory} documentation.
final String jmxConnectorServerURL = "service:jmx:rmi:///jndi/rmi://" + hostname + ":" + port + "/" +
connectorServerName;
JMXServiceURL jmxUrl = null;
try {
jmxUrl = new JMXServiceURL(jmxConnectorServerURL);
} catch (MalformedURLException e) {
logger.error(jmxRmiFailureReason = "Unable to create the JMXServiceURL from " + jmxConnectorServerURL, e);
return false;
}
final HashMap<String, Object> env = new HashMap<>(1);
env.put(JMXConnectorServer.AUTHENTICATOR, authenticator);
// Create the connector server
try {
this.rmiCs = JMXConnectorServerFactory.newJMXConnectorServer(jmxUrl, env, mbs);
} catch (Exception e) {
logger.error(jmxRmiFailureReason = "Unable to create the JMXConnectorServer at " + jmxUrl, e);
return false;
}
return true;
}
private boolean createJMXROConnectorServer(final MBeanServer mbs, final String connectorServerName,
final JMXAuthenticator authenticator) {
// Get the base uri (seems to always end with '/')
URI baseURI = null;
try {
baseURI = JMXProviderUtils.getBaseURI();
} catch (Exception e) {
logger.error(jmxRoFailureReason = "Unable to get the base uri", e);
return false;
}
// The asked address of the new connector server. The actual address can be different due to
// JMX specification. See {@link JMXConnectorServerFactory} documentation.
final String jmxConnectorServerURL = "service:jmx:" + JMXProviderUtils.RO_PROTOCOL + ":///jndi/" +
URIBuilder.buildURI(baseURI, connectorServerName);
JMXServiceURL jmxUrl = null;
try {
jmxUrl = new JMXServiceURL(jmxConnectorServerURL);
} catch (MalformedURLException e) {
logger.error(jmxRoFailureReason = "Unable to create the JMXServiceURL from " + jmxConnectorServerURL, e);
return false;
}
final HashMap<String, Object> env = new HashMap<>(2);
env.put(JMXConnectorServer.AUTHENTICATOR, authenticator);
env.put(JMXConnectorServerFactory.PROTOCOL_PROVIDER_PACKAGES, JMXProviderUtils.RO_PROVIDER_PKGS);
// Create the connector server
try {
this.roCs = JMXConnectorServerFactory.newJMXConnectorServer(jmxUrl, env, mbs);
} catch (Exception e) {
logger.error(jmxRoFailureReason = "Unable to create the JMXConnectorServer at " + jmxUrl, e);
return false;
}
return true;
}
/**
* Shutdown the JMX monitoring infrastructure.
*/
public void shutdown() {
try {
// Shutdown the JMX RMI connector server
if (this.rmiCs != null) {
this.rmiCs.stop();
}
} catch (Exception t) {
if (logger.isDebugEnabled()) {
logger.debug("Unable to stop the JMX-RMI connector server", t);
}
}
try {
// Shutdown the JMX RO connector server
if (this.roCs != null) {
this.roCs.stop();
}
} catch (Exception t) {
if (logger.isDebugEnabled()) {
logger.debug("Unable to stop the JMX-RO connector server", t);
}
}
if (dataStore != null) {
dataStore.terminate();
}
}
/**
* Returns the address of the JMX connector server depending on the specified protocol.
*
* @param protocol the JMX transport protocol
* @return the address of the connector server
* @throws JMException in case of boot sequence failure
*/
public JMXServiceURL getAddress(final JMXTransportProtocol protocol) throws JMException {
JMXServiceURL address;
switch (protocol) {
case RMI:
if (this.jmxRmiFailureReason == null) {
this.jmxRmiFailureReason = "Unknown failure. It is possible that the JMX-RMI monitoring infrastructure was not booted";
}
if (this.rmiCs == null) {
throw new JMException(this.jmxRmiFailureReason);
}
address = this.rmiCs.getAddress();
if (address == null) {
throw new JMException(this.jmxRmiFailureReason);
}
return address;
case RO:
if (this.jmxRoFailureReason == null) {
this.jmxRoFailureReason = "Unknown failure. It is possible that the JMX-RO monitoring infrastructure was not booted";
}
if (this.roCs == null) {
throw new JMException(this.jmxRoFailureReason);
}
address = this.roCs.getAddress();
if (address == null) {
throw new JMException(this.jmxRoFailureReason);
}
return address;
default:
throw new JMException("Uknown JMX transport protocol: " + protocol);
}
}
/**
* Sets the rrd data store.
* @param rrdDataStore
*/
public void setDataStore(RRDDataStore rrdDataStore) {
this.dataStore = rrdDataStore;
}
/**
* Gets the RRD data base with statistics.
* @return RRD data base with statistics.
*/
public RRDDataStore getDataStore() {
return dataStore;
}
}