/* * Jopr Management Platform * Copyright (C) 2005-2008 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, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * 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 and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser 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.plugins.jbossas; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import javax.management.MBeanServer; import javax.management.ObjectName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jetbrains.annotations.Nullable; import org.mc4j.ems.connection.support.metadata.InternalVMTypeDescriptor; import org.mc4j.ems.connection.support.metadata.JBossConnectionTypeDescriptor; import org.jboss.on.common.jbossas.JBossASDiscoveryUtils; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.PropertyList; import org.rhq.core.domain.configuration.PropertyMap; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.event.EventSeverity; import org.rhq.core.pluginapi.event.log.LogFileEventResourceComponentHelper; import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails; import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException; import org.rhq.core.pluginapi.inventory.ManualAddFacet; import org.rhq.core.pluginapi.inventory.ProcessScanResult; import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent; import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext; import org.rhq.core.pluginapi.util.FileUtils; import org.rhq.core.system.ProcessInfo; import org.rhq.plugins.jbossas.helper.JBossInstallationInfo; import org.rhq.plugins.jbossas.helper.JBossInstanceInfo; import org.rhq.plugins.jbossas.helper.JBossProductType; import org.rhq.plugins.jbossas.helper.JBossProperties; import org.rhq.plugins.jbossas.util.JBossMBeanUtility; import org.rhq.plugins.jbossas.util.JnpConfig; import org.rhq.plugins.jmx.JMXDiscoveryComponent; /** * Discovers instances of JBoss AS 3.2.3 through 4.2.x, and JBoss EAP and SOA-P 4.x. * * @author John Mazzitelli * @author Ian Springer */ public class JBossASDiscoveryComponent implements ResourceDiscoveryComponent, ManualAddFacet { private final Log log = LogFactory.getLog(JBossASDiscoveryComponent.class); private static final String JBOSS_SERVICE_XML = "conf" + File.separator + "jboss-service.xml"; private static final String JBOSS_NAMING_SERVICE_XML = "deploy" + File.separator + "naming-service.xml"; private static final String JAVA_HOME_ENV_VAR = "JAVA_HOME"; private static final String ANY_ADDRESS = "0.0.0.0"; private static final String LOCALHOST = "127.0.0.1"; private static final String CHANGE_ME = "***CHANGE_ME***"; public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext context) { log.debug("Discovering JBoss AS 3.2.x and 4.x servers..."); Set<DiscoveredResourceDetails> resources = new HashSet<DiscoveredResourceDetails>(); DiscoveredResourceDetails jbossPcIsEmbeddedIn = discoverJBossPcIsEmbeddedIn(context); if (jbossPcIsEmbeddedIn != null) { resources.add(jbossPcIsEmbeddedIn); } processAutoDiscoveredProcesses(context, resources, jbossPcIsEmbeddedIn); return resources; } public DiscoveredResourceDetails discoverResource(Configuration pluginConfiguration, ResourceDiscoveryContext discoveryContext) throws InvalidPluginConfigurationException { // Set the connection type (used by JMX plugin to connect to the MBean server). pluginConfiguration.put(new PropertySimple(JMXDiscoveryComponent.CONNECTION_TYPE, JBossConnectionTypeDescriptor.class.getName())); // Set default values on any props that are not set. setPluginConfigurationDefaults(pluginConfiguration); ProcessInfo processInfo = null; String jbossHomeDir = pluginConfiguration.getSimpleValue(JBossASServerComponent.JBOSS_HOME_DIR_CONFIG_PROP, null);// this will never be null JBossInstallationInfo installInfo; try { installInfo = new JBossInstallationInfo(new File(jbossHomeDir)); } catch (IOException e) { throw new InvalidPluginConfigurationException(e); } String version = installInfo.getVersion(); if (version.startsWith("5") || version.startsWith("6")) { throw new InvalidPluginConfigurationException( "The specified server is JBoss AS 5.0 or later - only AS 3.2 or 4.x are valid for this Resource type."); } DiscoveredResourceDetails resourceDetails = createResourceDetails(discoveryContext, pluginConfiguration, processInfo, installInfo); return resourceDetails; } private void processAutoDiscoveredProcesses(ResourceDiscoveryContext context, Set<DiscoveredResourceDetails> resources, DiscoveredResourceDetails jbossPcIsEmbeddedIn) { List<ProcessScanResult> autoDiscoveryResults = context.getAutoDiscoveredProcesses(); for (ProcessScanResult autoDiscoveryResult : autoDiscoveryResults) { ProcessInfo processInfo = autoDiscoveryResult.getProcessInfo(); if (log.isDebugEnabled()) log.debug("Discovered JBoss AS process: " + processInfo); JBossInstanceInfo cmdLine; try { cmdLine = new JBossInstanceInfo(processInfo); } catch (Exception e) { log.error("Failed to process JBoss AS command line: " + Arrays.asList(processInfo.getCommandLine()), e); continue; } // See if we have an AS 5 or AS 6 - if so, skip it. JBossInstallationInfo installInfo = cmdLine.getInstallInfo(); String version = installInfo.getVersion(); if (version.startsWith("5") || version.startsWith("6")) { if (log.isDebugEnabled()) log.debug("Found JBoss AS 5.0 or later, which is not supported by this plugin - skipping..."); continue; } File installHome = new File(cmdLine.getSystemProperties().getProperty(JBossProperties.HOME_DIR)); File configDir = new File(cmdLine.getSystemProperties().getProperty(JBossProperties.SERVER_HOME_DIR)); if ((jbossPcIsEmbeddedIn != null) && jbossPcIsEmbeddedIn.getPluginConfiguration() .getSimple(JBossASServerComponent.CONFIGURATION_PATH_CONFIG_PROP).getStringValue() .equals(configDir.getAbsolutePath())) { // We're running embedded, and the JBossAS server we're embedded in has already been found. continue; } // The config dir might be a symlink - call getCanonicalFile() to resolve it if so, before // calling isDirectory() (isDirectory() returns false for a symlink, even if it points at // a directory). try { if (!configDir.getCanonicalFile().isDirectory()) { log.warn("Skipping discovery for JBoss AS process " + processInfo + ", because configuration dir '" + configDir + "' does not exist or is not a directory."); continue; } } catch (IOException e) { log.error("Skipping discovery for JBoss AS process " + processInfo + ", because configuration dir '" + configDir + "' could not be canonicalized.", e); continue; } Configuration pluginConfiguration = context.getDefaultPluginConfiguration(); String jnpURL = getJnpURL(cmdLine, installHome, configDir); // Set the connection type (used by JMX plugin to connect to the MBean server). pluginConfiguration.put(new PropertySimple(JMXDiscoveryComponent.CONNECTION_TYPE, JBossConnectionTypeDescriptor.class.getName())); // Set the required props... pluginConfiguration.put(new PropertySimple(JBossASServerComponent.NAMING_URL_CONFIG_PROP, jnpURL)); pluginConfiguration.put(new PropertySimple(JBossASServerComponent.JBOSS_HOME_DIR_CONFIG_PROP, installHome .getAbsolutePath())); pluginConfiguration .put(new PropertySimple(JBossASServerComponent.CONFIGURATION_PATH_CONFIG_PROP, configDir)); // Set the optional props... pluginConfiguration.put(new PropertySimple(JBossASServerComponent.CONFIGURATION_SET_CONFIG_PROP, cmdLine .getSystemProperties().getProperty(JBossProperties.SERVER_NAME))); pluginConfiguration.put(new PropertySimple(JBossASServerComponent.BINDING_ADDRESS_CONFIG_PROP, cmdLine .getSystemProperties().getProperty(JBossProperties.BIND_ADDRESS))); JBossASDiscoveryUtils.UserInfo userInfo = JBossASDiscoveryUtils.getJmxInvokerUserInfo(configDir); if (userInfo != null) { pluginConfiguration.put(new PropertySimple(JBossASServerComponent.PRINCIPAL_CONFIG_PROP, userInfo .getUsername())); pluginConfiguration.put(new PropertySimple(JBossASServerComponent.CREDENTIALS_CONFIG_PROP, userInfo .getPassword())); } String javaHome = processInfo.getEnvironmentVariable(JAVA_HOME_ENV_VAR); if (javaHome == null && log.isDebugEnabled()) { log.debug("JAVA_HOME environment variable not set in JBoss AS process - defaulting " + JBossASServerComponent.JAVA_HOME_PATH_CONFIG_PROP + " connection property to the plugin container JRE dir."); } pluginConfiguration.put(new PropertySimple(JBossASServerComponent.JAVA_HOME_PATH_CONFIG_PROP, javaHome)); initLogEventSourcesConfigProp(configDir, pluginConfiguration); // Init props that have static defaults. setPluginConfigurationDefaults(pluginConfiguration); DiscoveredResourceDetails resourceDetails = createResourceDetails(context, pluginConfiguration, processInfo, installInfo); resources.add(resourceDetails); } } private void initLogEventSourcesConfigProp(File configDir, Configuration pluginConfiguration) { File logDir = new File(configDir, "log"); File serverLogFile = new File(logDir, "server.log"); if (serverLogFile.exists() && !serverLogFile.isDirectory()) { PropertyMap serverLogEventSource = new PropertyMap("logEventSource"); serverLogEventSource.put(new PropertySimple( LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.LOG_FILE_PATH, serverLogFile)); serverLogEventSource.put(new PropertySimple( LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.ENABLED, Boolean.FALSE)); PropertyList logEventSources = pluginConfiguration .getList(LogFileEventResourceComponentHelper.LOG_EVENT_SOURCES_CONFIG_PROP); logEventSources.add(serverLogEventSource); } } private static JnpConfig getJnpConfig(File installHome, File configDir, Properties props) { File serviceXML = new File(configDir, JBOSS_SERVICE_XML); JnpConfig config = JnpConfig.getConfig(serviceXML, props); if ((config == null) || (config.getJnpPort() == null)) { File namingServiceFile = new File(configDir, JBOSS_NAMING_SERVICE_XML); if (namingServiceFile.exists()) { config = JnpConfig.getConfig(namingServiceFile, props); } } return config; } private void setPluginConfigurationDefaults(Configuration pluginConfiguration) { setPropertySimpleIfNotAlreadySet(pluginConfiguration, JBossASServerComponent.START_SCRIPT_CONFIG_PROP, JBossASServerComponent.DEFAULT_START_SCRIPT); setPropertySimpleIfNotAlreadySet(pluginConfiguration, JBossASServerComponent.SHUTDOWN_SCRIPT_CONFIG_PROP, JBossASServerComponent.DEFAULT_SHUTDOWN_SCRIPT); setPropertySimpleIfNotAlreadySet(pluginConfiguration, JBossASServerComponent.JAVA_HOME_PATH_CONFIG_PROP, JBossASServerComponent.DEFAULT_JAVA_HOME); setPropertySimpleIfNotAlreadySet(pluginConfiguration, JBossASServerComponent.BINDING_ADDRESS_CONFIG_PROP, JBossASServerComponent.DEFAULT_BIND_ADDRESS); } private void setPropertySimpleIfNotAlreadySet(Configuration pluginConfiguration, String propName, String propValue) { PropertySimple prop = pluginConfiguration.getSimple(propName); if ((prop == null) || (prop.getStringValue() == null)) { pluginConfiguration.put(new PropertySimple(propName, propValue)); } } private DiscoveredResourceDetails createResourceDetails(ResourceDiscoveryContext discoveryContext, Configuration pluginConfiguration, @Nullable ProcessInfo processInfo, JBossInstallationInfo installInfo) { String configPath = pluginConfiguration.getSimple(JBossASServerComponent.CONFIGURATION_PATH_CONFIG_PROP) .getStringValue(); File absoluteConfigPath = JBossASServerComponent.resolvePathRelativeToHomeDir(pluginConfiguration, configPath); // Canonicalize the config path, so it's consistent no matter how it's entered. // This prevents two servers with different forms of the same config path, but // that are actually the same server, from ending up in inventory. // JON: fix for JBNADM-2634 - do not resolve symlinks (ips, 12/18/07) String key = FileUtils.getCanonicalPath(absoluteConfigPath.getPath()); String bindingAddress = pluginConfiguration.getSimple(JBossASServerComponent.BINDING_ADDRESS_CONFIG_PROP) .getStringValue(); String namingUrl = pluginConfiguration.getSimple(JBossASServerComponent.NAMING_URL_CONFIG_PROP) .getStringValue(); // Only include the JNP port in the Resource name if its value is not "***CHANGE_ME***". String namingPort = null; //noinspection ConstantConditions int colonIndex = namingUrl.lastIndexOf(':'); if ((colonIndex != -1) && (colonIndex != (namingUrl.length() - 1))) { // NOTE: We assume the JNP URL does not have a trailing slash. String port = namingUrl.substring(colonIndex + 1); if (!port.equals(CHANGE_ME)) { namingPort = port; } } final JBossProductType productType = installInfo.getProductType(); String description = productType.DESCRIPTION + " " + installInfo.getMajorVersion(); boolean isRhqServer = isRhqServer(absoluteConfigPath); if (isRhqServer) { description += " hosting the RHQ Server"; // RHQ-633 : We know this is an RHQ Server. Let's auto-configure for tracking its log file, which is not in // the standard JBoss AS location. // JOPR-53 : Log tracking will be disabled - user will have to explicitly enable it now. File rhqLogFile = JBossASServerComponent.resolvePathRelativeToHomeDir(pluginConfiguration, "../logs/rhq-server-log4j.log"); if (rhqLogFile.exists() && !rhqLogFile.isDirectory()) { try { PropertyMap serverLogEventSource = new PropertyMap("logEventSource"); serverLogEventSource.put(new PropertySimple( LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.LOG_FILE_PATH, rhqLogFile .getCanonicalPath())); serverLogEventSource.put(new PropertySimple( LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.ENABLED, Boolean.FALSE)); serverLogEventSource.put(new PropertySimple( LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.MINIMUM_SEVERITY, EventSeverity.INFO.name().toLowerCase())); PropertyList logEventSources = pluginConfiguration .getList(LogFileEventResourceComponentHelper.LOG_EVENT_SOURCES_CONFIG_PROP); logEventSources.add(serverLogEventSource); } catch (IOException e) { log.warn("Unable to setup RHQ Server log file monitoring.", e); } } } String name = formatServerName(bindingAddress, namingPort, discoveryContext.getSystemInformation().getHostname(), absoluteConfigPath.getName(), productType, isRhqServer); String version = productType.name() + " " + installInfo.getVersion(); return new DiscoveredResourceDetails(discoveryContext.getResourceType(), key, name, version, description, pluginConfiguration, processInfo); } /** * @param absoluteConfigPath * @return true if this plugin is in an Agent that is embedded in the Server */ private boolean isRhqServer(File absoluteConfigPath) { File deployDir = new File(absoluteConfigPath, "deploy"); File rhqInstallerWar = new File(deployDir, "rhq-installer.war"); File rhqInstallerWarUndeployed = new File(deployDir, "rhq-installer.war.rej"); boolean isInServer = rhqInstallerWar.exists() || rhqInstallerWarUndeployed.exists(); return isInServer; } @Nullable private DiscoveredResourceDetails discoverJBossPcIsEmbeddedIn(ResourceDiscoveryContext context) { MBeanServer server = JBossMBeanUtility.getJBossMBeanServer(); try { String jnpAddress = null; String jnpPort = null; ObjectName namingObjectName = new ObjectName("jboss:service=Naming"); Set namingSet = server.queryNames(namingObjectName, null); if (namingSet.iterator().hasNext()) { jnpAddress = (String) server.getAttribute(namingObjectName, "BindAddress"); jnpPort = String.valueOf(server.getAttribute(namingObjectName, "Port")); } String bindAddress = null; ObjectName systemPropertiesObjectName = new ObjectName("jboss:name=SystemProperties,type=Service"); Set systemPropertiesSet = server.queryNames(systemPropertiesObjectName, null); if (systemPropertiesSet.iterator().hasNext()) { bindAddress = (String) server.invoke(systemPropertiesObjectName, "get", new Object[] { JBossProperties.BIND_ADDRESS }, new String[] { String.class.getName() }); } if (bindAddress == null) { bindAddress = jnpAddress; } ObjectName configObjectName = new ObjectName("jboss.system:type=ServerConfig"); Set set = server.queryNames(configObjectName, null); if (set.iterator().hasNext()) { // ServerConfig MBean found File homeDir = (File) server.getAttribute(configObjectName, "HomeDir"); JBossInstallationInfo installInfo; try { installInfo = new JBossInstallationInfo(homeDir); } catch (IOException e) { throw new IllegalStateException("Failed to determine installation info for JBoss home dir [" + homeDir + "].", e); } File configDir = (File) server.getAttribute(configObjectName, "ServerHomeDir"); //if (isEmbeddedInServer(configDir)) { // return null; // abort - we are embedded, but we're inside the enterprise Server //} String configName = (String) server.getAttribute(configObjectName, "ServerName"); String version = (String) server.getAttribute(configObjectName, "SpecificationVersion"); Configuration pluginConfiguration = context.getDefaultPluginConfiguration(); // Set the connection type (used by JMX plugin to connect to the MBean server). pluginConfiguration.put(new PropertySimple(JMXDiscoveryComponent.CONNECTION_TYPE, InternalVMTypeDescriptor.class.getName())); pluginConfiguration.put(new PropertySimple(JBossASServerComponent.JBOSS_HOME_DIR_CONFIG_PROP, homeDir)); pluginConfiguration.put(new PropertySimple(JBossASServerComponent.CONFIGURATION_PATH_CONFIG_PROP, configDir)); pluginConfiguration.put(new PropertySimple(JBossASServerComponent.CONFIGURATION_SET_CONFIG_PROP, configName)); // Now set default values on any props that are still not set. setPluginConfigurationDefaults(pluginConfiguration); JBossProductType productType = installInfo.getProductType(); String name = formatServerName(bindAddress, jnpPort, context.getSystemInformation().getHostname(), configName, productType, isRhqServer(configDir)); String description = productType.NAME + " server that the RHQ Plugin Container is running within"; DiscoveredResourceDetails resource = new DiscoveredResourceDetails(context.getResourceType(), configDir.getAbsolutePath(), name, version, description, pluginConfiguration, null); return resource; } } catch (Exception e) { // JBoss MBean doesn't exist - not a JBoss server. log.debug("Not detected to be embedded in a JBoss AS Server", e); } return null; } public String formatServerName(String bindingAddress, String jnpPort, String hostname, String configurationName, JBossProductType productType, boolean isRhq) { StringBuilder serverName = new StringBuilder(); if (!isRhq) { // prefix w/ product name (e.g. "AS" or "EAP") serverName.append(productType.name()).append(' '); } String bindingHostname = getBindingHostname(bindingAddress); if (bindingHostname != null) { serverName.append(bindingHostname); } else { serverName.append(hostname); } if (jnpPort != null && !jnpPort.equals(CHANGE_ME)) { serverName.append(':').append(jnpPort); } serverName.append(' '); if (isRhq) { serverName.append("RHQ Server"); } else { serverName.append(configurationName); } return serverName.toString(); } private String getBindingHostname(String bindingAddress) { String bindingHostname = null; if (bindingAddress != null) { try { InetAddress bindAddr = InetAddress.getByName(bindingAddress); if (!bindAddr.isAnyLocalAddress()) { //if the binding address != 0.0.0.0 bindingHostname = bindAddr.getHostName(); } } catch (UnknownHostException e) { //this should not happen? log.warn("Unknown hostname passed in as the binding address for JBoss AS instance: " + bindingAddress); } } return bindingHostname; } private static String getJnpURL(JBossInstanceInfo cmdLine, File installHome, File configDir) { JnpConfig jnpConfig = getJnpConfig(installHome, configDir, cmdLine.getSystemProperties()); String jnpAddress = (jnpConfig.getJnpAddress() != null) ? jnpConfig.getJnpAddress() : CHANGE_ME; if (jnpAddress.equals(ANY_ADDRESS)) { jnpAddress = LOCALHOST; } String jnpPort = (jnpConfig.getJnpPort() != null) ? String.valueOf(jnpConfig.getJnpPort()) : CHANGE_ME; return "jnp://" + jnpAddress + ":" + jnpPort; } }