/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.modules.plugins.jbossas7.jmx;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.rhq.modules.plugins.jbossas7.jmx.ApplicationMBeansDiscoveryComponent.PluginConfigProps.BEANS_QUERY_STRING;
import static org.rhq.modules.plugins.jbossas7.jmx.ApplicationMBeansDiscoveryComponent.PluginConfigProps.NEW_RESOURCE_DESCRIPTION;
import static org.rhq.modules.plugins.jbossas7.jmx.ApplicationMBeansDiscoveryComponent.PluginConfigProps.NEW_RESOURCE_NAME;
import static org.rhq.modules.plugins.jbossas7.jmx.ApplicationMBeansDiscoveryComponent.PluginConfigProps.NEW_RESOURCE_VERSION;
import java.io.File;
import java.util.Collections;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mc4j.ems.connection.ConnectionFactory;
import org.mc4j.ems.connection.EmsConnection;
import org.mc4j.ems.connection.settings.ConnectionSettings;
import org.mc4j.ems.connection.support.ConnectionProvider;
import org.mc4j.ems.connection.support.metadata.JSR160ConnectionTypeDescriptor;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext;
import org.rhq.modules.plugins.jbossas7.BaseComponent;
import org.rhq.modules.plugins.jbossas7.BaseServerComponent;
import org.rhq.modules.plugins.jbossas7.JBossProductType;
import org.rhq.modules.plugins.jbossas7.ManagedASComponent;
import org.rhq.modules.plugins.jbossas7.StandaloneASComponent;
import org.rhq.modules.plugins.jbossas7.helper.ServerPluginConfiguration;
/**
* Discovery class for the container {@link ApplicationMBeansComponent} component class.
*
* <h3>Custom plugin setup</h3>
*
* <p>This class is <strong>not</strong> declared in the AS7 plugin descriptor. It is a tool that that plugin developers
* may use while writing their <strong>own</strong> plugin to monitor their application MBeans.</p>
*
* <p>The custom plugin <strong>must</strong>:</p>
* <ul>
* <li>depend on the AS7 plugin and re-use its classes (<em>useClasses</em> attribute set to true in the plugin
* descriptor);</li>
* <li>embed the JMX plugin as a library.</li>
* </ul>
*
* <p>The resource type using this class (or a subclass of this class) as a discovery component must indicate which type
* of server it runs inside. It may be either a <em>JBossAS7 Standalone Server</em> or a <em>Managed Server</em>.</p>
*
* <p>Plugins authors are <em>required</em> to provide a resource key and name and <em>may</em>provide a resource
* description and version by declaring plugin configuration properties of the following names:</p>
*
* <ul>
* <li>{@link PluginConfigProps#NEW_RESOURCE_KEY}</li>
* <li>{@link PluginConfigProps#NEW_RESOURCE_NAME}</li>
* <li>{@link PluginConfigProps#NEW_RESOURCE_DESCRIPTION}</li>
* <li>{@link PluginConfigProps#NEW_RESOURCE_VERSION}</li>
* </ul>
*
* <p>Alternatively, they can subclass create a subclass of this discovery component and override one/some/all of the
* following methods:</p>
*
* <ul>
* <li>{@link #getNewResourceKey(org.rhq.core.domain.configuration.Configuration)}</li>
* <li>{@link #getNewResourceName(org.rhq.core.domain.configuration.Configuration)}</li>
* <li>{@link #getNewResourceDescription(org.rhq.core.domain.configuration.Configuration)} </li>
* <li>{@link #getNewResourceVersion(org.rhq.core.domain.configuration.Configuration)}</li>
* </ul>
*
* <h3>Auto-discovery</h3>
*
* <p>By default, application MBeans will be searched with an EMS query defined in the plugin descriptor by the
* {@link PluginConfigProps#BEANS_QUERY_STRING} plugin-config property.
* <br>
* It is also possible to define the query string in a subclass overriding the
* {@link #getBeansQueryString(org.rhq.core.domain.configuration.Configuration)} method.
* </p>
*
* <p>Plugin developers can implement their custom MBeans lookup method in a subclass overriding the
* {@link #hasApplicationMBeans(org.rhq.core.domain.configuration.Configuration, org.mc4j.ems.connection.EmsConnection)}
* method.</p>
*
* <h3>JMX host and port discovery</h3>
*
* <p>The JMX server host will be detected by looking at the top level server resource plugin configuration
* ({@link org.rhq.modules.plugins.jbossas7.StandaloneASComponent} and {@link org.rhq.modules.plugins.jbossas7.HostControllerComponent}). The JMX port detection mechanism depends on the
* parent resource.</p>
*
* <h4>On standalone servers</h4>
*
* <p>For AS7 and EAP6 servers, the discovery component will look at the management port defined in the parent
* {@link org.rhq.modules.plugins.jbossas7.StandaloneASComponent} plugin configuration and add the value of {@link #STANDALONE_REMOTING_PORT_OFFSET}.</p>
* <p>For Wildfly8 servers, the discovery component will look at the management port defined in the parent
* {@link org.rhq.modules.plugins.jbossas7.StandaloneASComponent} plugin configuration.</p>
*
* <h4>On managed servers</h4>
*
* <p>For AS7 and EAP6 servers, the discovery component will use {@link #DOMAIN_REMOTING_PORT_DEFAULT} <em>plus</em> the port offset of the managed server.</p>
* <p>For Wildfly8 servers, the discovery component will use {@link #HTTP_PORT_DEFAULT} <em>plus</em> the port offset of the managed server.</p>
*
* <h3>Authentication</h3>
*
* <p>JMX connectivity on AS7 and EAP6 requires authentication and standalone and managed servers behaviors differ.</p>
*
* <h4>On standalone servers</h4>
*
* <p>In standalone mode, the server will use the ManagementRealm, just as for HTTP management interface authentication.
* Consequently, this class will pick up the credentials of the management user defined in the plugin configuration of
* the parent server resource ({@link org.rhq.modules.plugins.jbossas7.StandaloneASComponent}).</p>
*
* <p>In other words, plugin authors have nothing to do.</p>
*
* <h4>On managed servers</h4>
*
* <p>In domain mode, the server will use the ApplicationRealm. Consequently, there is no way for the discovery
* component to discover credentials automatically and it will use "rhqadmin:rhqadmin" by default.</p>
*
* <p>When working with managed servers, plugin developers should subclass the discovery component and override the
* {@link #getCredentialsForManagedAS()} method. For example, an implementation could lookup the credentials in a
* text file.</p>
*
* <h3>Plugin descriptor example</h3>
*
* <pre>
* <plugin
* name="MyappMBeansPlugin"
* displayName="Myapp MBeans Plugin"
* package="com.myapp.services.plugin"
* xmlns="urn:xmlns:rhq-plugin"
* xmlns:c="urn:xmlns:rhq-configuration">
*
* <depends plugin="JBossAS7" useClasses="true"/>
*
* <service name="Myapp Services"
* discovery="org.rhq.modules.plugins.jbossas7.jmx.ApplicationMBeansDiscoveryComponent"
* class="org.rhq.modules.plugins.jbossas7.jmx.ApplicationMBeansComponent"
* description="Container for Myapp Services"
* singleton="true">
*
* <runs-inside>
* <!-- The type of the server the application is running on -->
* <parent-resource-type name="JBossAS7 Standalone Server" plugin="JBossAS7"/>
* <parent-resource-type name="Managed Server" plugin="JBossAS7"/>
* </runs-inside>
*
* <plugin-configuration>
* <c:simple-property name="beansQueryString" readOnly="true" default="myapp:service=*"/>
* <c:simple-property name="newResourceKey" readOnly="true" default="myappServices"/>
* <c:simple-property name="newResourceName" readOnly="true" default="Myapp Services"/>
* <c:simple-property name="newResourceDescription" readOnly="true" default="Container for Myapp Services"/>
* </plugin-configuration>
*
* <!--
* ApplicationMBeansComponent can be the parent of any JMXComponent (JMX plugin). For example, it's possible
* to monitor a MBean with no line of Java code thanks to the MBeanResourceComponent facility. Plugin authors
* only have to configure metrics (mapped to MBeans attributes) and operations (MBeans operations).
* -->
* <service name="HelloService" discovery="org.rhq.plugins.jmx.MBeanResourceDiscoveryComponent"
* class="org.rhq.plugins.jmx.MBeanResourceComponent" singleton="true">
* <plugin-configuration>
* <c:simple-property name="objectName" default="myapp:service=HelloService" readOnly="true"/>
* </plugin-configuration>
* <operation name="helloTo">
* <parameters>
* <c:simple-property name="p1" displayName="somebody" type="string" required="true"/>
* </parameters>
* <results>
* <c:simple-property name="operationResult" type="string"/>
* </results>
* </operation>
* </service>
*
* </service>
*
* </plugin>
* </pre>
*
* @author Thomas Segismont
* @see ApplicationMBeansComponent
*/
public class ApplicationMBeansDiscoveryComponent implements ResourceDiscoveryComponent<BaseComponent<?>> {
private static final Log LOG = LogFactory.getLog(ApplicationMBeansDiscoveryComponent.class);
public static final class PluginConfigProps {
public static final String BEANS_QUERY_STRING = "beansQueryString";
public static final String NEW_RESOURCE_KEY = "newResourceKey";
public static final String NEW_RESOURCE_NAME = "newResourceName";
public static final String NEW_RESOURCE_DESCRIPTION = "newResourceDescription";
public static final String NEW_RESOURCE_VERSION = "newResourceVersion";
private PluginConfigProps() {
// Constants class
}
}
private static final String HOSTNAME = "hostname";
private static final String PORT = "port";
private static final String USERNAME = "username";
private static final String PASSWORD = "password";
private static final String CLIENT_JAR_LOCATION = "clientJarLocation";
// "remoting-jmx" for AS7 and EAP6, "http-remoting-jmx" for Wildfly8;
private static final String PROTOCOL = "protocol";
private static final int STANDALONE_REMOTING_PORT_OFFSET = 9;
private static final int DOMAIN_REMOTING_PORT_DEFAULT = 4447;
private static final String MANAGED_SERVER_PORT_OFFSET_PROPERTY_NAME = "socket-binding-port-offset";
private static final int HTTP_PORT_DEFAULT = 8080;
@Override
public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext<BaseComponent<?>> context)
throws Exception {
BaseComponent<?> parentComponent = context.getParentResourceComponent();
BaseServerComponent baseServerComponent = parentComponent.getServerComponent();
ServerPluginConfiguration serverPluginConfiguration = baseServerComponent.getServerPluginConfiguration();
JBossProductType productType = serverPluginConfiguration.getProductType();
Configuration pluginConfig = context.getDefaultPluginConfiguration();
int port;
String username, password;
if (parentComponent instanceof ManagedASComponent) {
ManagedASComponent managedASComponent = (ManagedASComponent) parentComponent;
Configuration managedASConfig = managedASComponent.loadResourceConfiguration();
PropertySimple offsetProp = managedASConfig.getSimple(MANAGED_SERVER_PORT_OFFSET_PROPERTY_NAME);
if (offsetProp == null) {
LOG.warn("Could not find Managed Server socket binding offset, skipping discovery");
return Collections.emptySet();
}
port = DOMAIN_REMOTING_PORT_DEFAULT;
port += offsetProp.getIntegerValue();
String[] credentials = getCredentialsForManagedAS();
username = credentials[0];
password = credentials[1];
} else if (parentComponent instanceof StandaloneASComponent) {
port = serverPluginConfiguration.getPort();
port += STANDALONE_REMOTING_PORT_OFFSET;
username = serverPluginConfiguration.getUser();
password = serverPluginConfiguration.getPassword();
} else {
LOG.warn(parentComponent + " is not a supported parent component");
return Collections.emptySet();
}
String clientJarPath = "bin" + File.separator + "client" + File.separator + "jboss-client.jar";
File clientJarFile = new File(serverPluginConfiguration.getHomeDir(), clientJarPath);
if (!clientJarFile.isFile()) {
LOG.warn(clientJarFile + " does not exist.");
return Collections.emptySet();
}
pluginConfig.setSimpleValue(HOSTNAME, serverPluginConfiguration.getHostname());
pluginConfig.setSimpleValue(PORT, String.valueOf(port));
pluginConfig.setSimpleValue(USERNAME, username);
pluginConfig.setSimpleValue(PASSWORD, password);
pluginConfig.setSimpleValue(CLIENT_JAR_LOCATION, clientJarFile.getAbsolutePath());
pluginConfig.setSimpleValue(PROTOCOL, "remoting-jmx");
EmsConnection emsConnection = null;
try {
emsConnection = loadEmsConnection(pluginConfig, context.getParentResourceContext().getTemporaryDirectory());
if (emsConnection == null) {
// An error occured while creating the connection
return Collections.emptySet();
}
if (!hasApplicationMBeans(pluginConfig, emsConnection)) {
if (LOG.isDebugEnabled()) {
LOG.debug("No application MBeans found");
}
return Collections.emptySet();
}
return Collections.singleton(new DiscoveredResourceDetails(context.getResourceType(),
getNewResourceKey(pluginConfig), getNewResourceName(pluginConfig), getNewResourceVersion(pluginConfig),
getNewResourceDescription(pluginConfig), pluginConfig, null));
} finally {
if (emsConnection != null) {
emsConnection.close();
}
}
}
/**
* Indicates if a managed server or a standalone server has application MBeans. This implementation searches MBeans
* with an EMS query string.
*
* @param pluginConfig - the plugin configuration object for the {@link ApplicationMBeansComponent} that may be
* created
* @param emsConnection - an active emsConnection which can be used to communicate with the server
* @return true if application MBeans were detected, false otherwise
*/
protected boolean hasApplicationMBeans(Configuration pluginConfig, EmsConnection emsConnection) {
String beansQueryString = getBeansQueryString(pluginConfig);
if (emsConnection.queryBeans(beansQueryString).isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Found no MBeans with query '" + beansQueryString + "'");
}
return false;
}
return true;
}
/**
* Default implementation of the credentials lookup for JMX authentication against managed servers (domain mode).
*
* @return an array of two elements, the first being the username and the second the password
*/
protected String[] getCredentialsForManagedAS() {
return new String[] { "rhqadmin", "rhqadmin" };
}
/**
* The EMS query string chosen when using the default implementation of
* {@link #hasApplicationMBeans(org.rhq.core.domain.configuration.Configuration, org.mc4j.ems.connection.EmsConnection)}.
*
* @param pluginConfig - the plugin configuration object for the {@link ApplicationMBeansComponent} that will be
* created
* @return an EMS query string
*/
protected String getBeansQueryString(Configuration pluginConfig) {
return pluginConfig.getSimpleValue(BEANS_QUERY_STRING);
}
/**
* @param pluginConfig - the plugin configuration object for the {@link ApplicationMBeansComponent} that will be
* created
* @return the new resource key
*/
protected String getNewResourceKey(Configuration pluginConfig) {
return pluginConfig.getSimpleValue(PluginConfigProps.NEW_RESOURCE_KEY);
}
/**
* @param pluginConfig - the plugin configuration object for the {@link ApplicationMBeansComponent} that will be
* created
* @return the new resource name
*/
protected String getNewResourceName(Configuration pluginConfig) {
return pluginConfig.getSimpleValue(NEW_RESOURCE_NAME);
}
/**
* @param pluginConfig - the plugin configuration object for the {@link ApplicationMBeansComponent} that will be
* created
* @return the new resource description
*/
protected String getNewResourceDescription(Configuration pluginConfig) {
return pluginConfig.getSimpleValue(NEW_RESOURCE_DESCRIPTION);
}
/**
* @param pluginConfig - the plugin configuration object for the {@link ApplicationMBeansComponent} that will be
* created
* @return the new resource version
*/
protected String getNewResourceVersion(Configuration pluginConfig) {
return pluginConfig.getSimpleValue(NEW_RESOURCE_VERSION);
}
/**
* Creates a new {@link EmsConnection} object.
*
* @param pluginConfig - a plugin configuration object of the {@link ApplicationMBeansComponent}
* @return a new {@link EmsConnection} object or null if connecting failed
* @deprecated as of RHQ4.12. Use
* {@link #loadEmsConnection(org.rhq.core.domain.configuration.Configuration, java.io.File)} instead.
*/
@Deprecated
public static EmsConnection loadEmsConnection(Configuration pluginConfig) {
return loadEmsConnection(pluginConfig, new File(System.getProperty("java.io.tmpdir")));
}
/**
* Creates a new {@link EmsConnection} object.
*
* @param pluginConfig - a plugin configuration object of the {@link ApplicationMBeansComponent}
* @param jarTempDir - the directory where the additional JARs for the JMX connection classloader should be copied
* @return a new {@link EmsConnection} object or null if connecting failed
*/
public static EmsConnection loadEmsConnection(Configuration pluginConfig, File jarTempDir) {
EmsConnection emsConnection = null;
try {
File clientJarFile = new File(pluginConfig.getSimpleValue(CLIENT_JAR_LOCATION));
ConnectionSettings connectionSettings = new ConnectionSettings();
connectionSettings.initializeConnectionType(new A7ConnectionTypeDescriptor(clientJarFile));
connectionSettings.setLibraryURI(clientJarFile.getParent());
String serverUrl = "service:jmx:" //
+ pluginConfig.getSimpleValue(PROTOCOL) //
+ "://" //
+ pluginConfig.getSimpleValue(HOSTNAME) //
+ ":" //
+ pluginConfig.getSimpleValue(PORT);
connectionSettings.setServerUrl(serverUrl);
connectionSettings.setPrincipal(pluginConfig.getSimpleValue(USERNAME));
connectionSettings.setCredentials(pluginConfig.getSimpleValue(PASSWORD));
if (connectionSettings.getControlProperties() == null) {
connectionSettings.setControlProperties(new Properties());
}
connectionSettings.getControlProperties().setProperty(ConnectionFactory.COPY_JARS_TO_TEMP,
String.valueOf(TRUE));
connectionSettings.getControlProperties().setProperty(ConnectionFactory.JAR_TEMP_DIR,
jarTempDir.getAbsolutePath());
if (connectionSettings.getAdvancedProperties() == null) {
connectionSettings.setAdvancedProperties(new Properties());
}
connectionSettings.getAdvancedProperties().setProperty(ConnectionFactory.USE_CONTEXT_CLASSLOADER,
String.valueOf(FALSE));
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.discoverServerClasses(connectionSettings);
ConnectionProvider connectionProvider = connectionFactory.getConnectionProvider(connectionSettings);
return connectionProvider.connect();
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Could not create EmsConnection", e);
}
if (emsConnection != null) {
emsConnection.close();
}
return null;
}
}
private static class A7ConnectionTypeDescriptor extends JSR160ConnectionTypeDescriptor {
private final File clientJarFile;
public A7ConnectionTypeDescriptor(File clientJarFile) {
this.clientJarFile = clientJarFile;
}
@Override
public String[] getConnectionClasspathEntries() {
return new String[] { clientJarFile.getName() };
}
@Override
public boolean isUseChildFirstClassLoader() {
return true;
}
}
}