/* * Jopr Management Platform * Copyright (C) 2005-2009 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.mobicents.servlet.sip.jboss5; import java.io.File; import java.io.Serializable; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.deployers.spi.management.KnownDeploymentTypes; import org.jboss.deployers.spi.management.ManagementView; import org.jboss.deployers.spi.management.deploy.DeploymentStatus; import org.jboss.deployers.spi.management.deploy.ProgressEvent; import org.jboss.deployers.spi.management.deploy.ProgressListener; import org.jboss.managed.api.ComponentType; import org.jboss.managed.api.DeploymentTemplateInfo; import org.jboss.managed.api.ManagedComponent; import org.jboss.managed.api.ManagedDeployment; import org.jboss.managed.api.ManagedProperty; import org.jboss.profileservice.spi.NoSuchDeploymentException; import org.mc4j.ems.connection.ConnectionFactory; import org.mc4j.ems.connection.EmsConnection; import org.mc4j.ems.connection.bean.EmsBean; import org.mc4j.ems.connection.settings.ConnectionSettings; import org.mc4j.ems.connection.support.ConnectionProvider; import org.mc4j.ems.connection.support.metadata.ConnectionTypeDescriptor; import org.mc4j.ems.connection.support.metadata.InternalVMTypeDescriptor; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.Property; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; import org.rhq.core.domain.configuration.definition.ConfigurationTemplate; import org.rhq.core.domain.configuration.definition.PropertyDefinition; import org.rhq.core.domain.content.PackageDetailsKey; import org.rhq.core.domain.content.transfer.ResourcePackageDetails; import org.rhq.core.domain.measurement.AvailabilityType; import org.rhq.core.domain.measurement.DataType; import org.rhq.core.domain.measurement.MeasurementDataNumeric; import org.rhq.core.domain.measurement.MeasurementDataTrait; import org.rhq.core.domain.measurement.MeasurementReport; import org.rhq.core.domain.measurement.MeasurementScheduleRequest; import org.rhq.core.domain.resource.CreateResourceStatus; import org.rhq.core.domain.resource.ResourceCreationDataType; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.pluginapi.configuration.ConfigurationFacet; import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport; import org.rhq.core.pluginapi.inventory.CreateChildResourceFacet; import org.rhq.core.pluginapi.inventory.CreateResourceReport; import org.rhq.core.pluginapi.inventory.ResourceComponent; import org.rhq.core.pluginapi.inventory.ResourceContext; import org.rhq.core.pluginapi.measurement.MeasurementFacet; import org.rhq.plugins.jbossas5.adapter.api.PropertyAdapter; import org.rhq.plugins.jbossas5.adapter.api.PropertyAdapterFactory; import org.rhq.plugins.jbossas5.factory.ProfileServiceFactory; import org.rhq.plugins.jbossas5.util.ConversionUtils; import org.rhq.plugins.jbossas5.util.DebugUtils; import org.rhq.plugins.jbossas5.util.ManagedComponentUtils; import org.rhq.plugins.jbossas5.util.ResourceComponentUtils; import org.rhq.plugins.jmx.JMXDiscoveryComponent; import org.rhq.plugins.jmx.JMXServerComponent; import org.rhq.plugins.jmx.ObjectNameQueryUtility; import org.rhq.plugins.mobicents.servlet.sip.jboss5.util.DeploymentUtils; import org.rhq.plugins.mobicents.servlet.sip.jboss5.util.MainDeployer; /** * ResourceComponent for a JBoss AS, 5.1.0.CR1 or later, Server. * * @author Jason Dobies * @author Mark Spritzler * @author Ian Springer */ public class ApplicationServerComponent extends JMXServerComponent implements ResourceComponent, CreateChildResourceFacet, MeasurementFacet, ConfigurationFacet, ProgressListener { static final String TEMPLATE_NAME_PROPERTY = "templateName"; static final String RESOURCE_NAME_PROPERTY = "resourceName"; static final String SERVER_NAME_PROPERTY = "serverName"; public static final String JBOSS_HOME_DIR_CONFIG_PROP = "jbossHomeDir"; public static final String NAMING_URL_CONFIG_PROP = "namingURL"; private static final String JNP_DISABLE_DISCOVERY_JNP_INIT_PROP = "jnp.disableDiscovery"; /** * This is the timeout for the initial connection to the MBeanServer that is made by {@link #start(ResourceContext)}. */ private static final int JNP_TIMEOUT = 30 * 1000; // 30 seconds /** * This is the timeout for MBean attribute gets/sets and operations invoked on the remote MBeanServer. * NOTE: This timeout comes into play if the JBossAS instance has gone down since the original JNP connection was made. */ private static final int JNP_SO_TIMEOUT = 15 * 1000; // 15 seconds private static final String MANAGED_PROPERTY_GROUP = "managedPropertyGroup"; private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("(.*)\\|(.*)\\|(.*)\\|(.*)"); private final Log log = LogFactory.getLog(this.getClass()); private ResourceContext resourceContext; private File deployDirectory; private EmsConnection connection; /** * Controls the dampening of connection error stack traces in an attempt to control spam to the log * file. Each time a connection error is encountered, this will be incremented. When the connection * is finally established, this will be reset to zero. */ private int consecutiveConnectionErrors; private MainDeployer mainDeployer; public AvailabilityType getAvailability() { // TODO: Always returning UP is fine for Embedded, but we'll need to actually check avail for Enterprise. return AvailabilityType.UP; } public void start(ResourceContext resourceContext) { this.resourceContext = resourceContext; } public void stop() { return; } // ------------ MeasurementFacet Implementation ------------ public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> requests) { for (MeasurementScheduleRequest request : requests) { String metricName = request.getName(); try { // Metric names are expected to have the following syntax: // "<componentType>|<componentSubType>|<componentName>|<propertyName>" Matcher matcher = METRIC_NAME_PATTERN.matcher(metricName); if (!matcher.matches()) { log.error("Metric name '" + metricName + "' does not match pattern '" + METRIC_NAME_PATTERN + "'."); continue; } String componentCategory = matcher.group(1); String componentSubType = matcher.group(2); String componentName = matcher.group(3); String propertyName = matcher.group(4); ComponentType componentType = new ComponentType(componentCategory, componentSubType); ManagedComponent component; if (componentName.equals("*")) { component = ManagedComponentUtils.getSingletonManagedComponent(componentType); } else { component = ManagedComponentUtils.getManagedComponent(componentType, componentName); } Serializable value = ManagedComponentUtils.getSimplePropertyValue(component, propertyName); if (value == null) { log.debug("Null value returned for metric '" + metricName + "'."); continue; } if (request.getDataType() == DataType.MEASUREMENT) { Number number = (Number)value; report.addData(new MeasurementDataNumeric(request, number.doubleValue())); } else if (request.getDataType() == DataType.TRAIT) { report.addData(new MeasurementDataTrait(request, value.toString())); } } catch (RuntimeException e) { log.error("Failed to obtain metric '" + metricName + "'.", e); } } } // ------------ ConfigurationFacet Implementation ------------ public Configuration loadResourceConfiguration() { /* Need to determine what we consider server configuration to return. Also need to understand what ComponentType the profile service would use to retrieve "server" level configuration. */ return null; } public void updateResourceConfiguration(ConfigurationUpdateReport configurationUpdateReport) { // See above comment on server configuration. } // CreateChildResourceFacet -------------------------------------------- public CreateResourceReport createResource(CreateResourceReport createResourceReport) { //ProfileServiceFactory.refreshCurrentProfileView(); ResourceType resourceType = createResourceReport.getResourceType(); if (resourceType.getCreationDataType() == ResourceCreationDataType.CONTENT) createContentBasedResource(createResourceReport, resourceType); else createConfigurationBasedResource(createResourceReport, resourceType); return createResourceReport; } public File getDeployDirectory() { if (this.deployDirectory == null) this.deployDirectory = computeDeployDirectory(); return this.deployDirectory; } // ProgressListener -------------------------------------------- public void progressEvent(ProgressEvent eventInfo) { log.debug(eventInfo); } private void handleMiscManagedProperties(Collection<PropertyDefinition> managedPropertyGroup, Map<String, ManagedProperty> managedProperties, Configuration pluginConfiguration) { for (PropertyDefinition propertyDefinition : managedPropertyGroup) { String propertyKey = propertyDefinition.getName(); Property property = pluginConfiguration.get(propertyKey); ManagedProperty managedProperty = managedProperties.get(propertyKey); if (managedProperty != null && property != null) { PropertyAdapter propertyAdapter = PropertyAdapterFactory.getPropertyAdapter(managedProperty.getMetaType()); propertyAdapter.populateMetaValueFromProperty(property, managedProperty.getValue(), propertyDefinition); } } } private static String getResourceName(Configuration pluginConfig, Configuration resourceConfig) { PropertySimple resourceNameProp = pluginConfig.getSimple(RESOURCE_NAME_PROPERTY); if (resourceNameProp == null || resourceNameProp.getStringValue() == null) throw new IllegalStateException("Property [" + RESOURCE_NAME_PROPERTY + "] is not defined in the default plugin configuration."); String resourceNamePropName = resourceNameProp.getStringValue(); PropertySimple propToUseAsResourceName = resourceConfig.getSimple(resourceNamePropName); if (propToUseAsResourceName == null) throw new IllegalStateException("Property [" + resourceNamePropName + "] is not defined in initial Resource configuration."); return propToUseAsResourceName.getStringValue(); } private String getResourceKey(ResourceType resourceType, String resourceName) { ComponentType componentType = ConversionUtils.getComponentType(resourceType); if (componentType == null) throw new IllegalStateException("Unable to map " + resourceType + " to a ComponentType."); // TODO (ips): I think the key can just be the resource name. return componentType.getType() + ":" + componentType.getSubtype() + ":" + resourceName; } private void createConfigurationBasedResource(CreateResourceReport createResourceReport, ResourceType resourceType) { Configuration defaultPluginConfig = getDefaultPluginConfiguration(resourceType); Configuration resourceConfig = createResourceReport.getResourceConfiguration(); String resourceName = getResourceName(defaultPluginConfig, resourceConfig); ComponentType componentType = ConversionUtils.getComponentType(resourceType); if (ProfileServiceFactory.isManagedComponent(resourceName, componentType)) { createResourceReport.setStatus(CreateResourceStatus.FAILURE); createResourceReport.setErrorMessage("A " + resourceType.getName() + " named '" + resourceName + "' already exists."); return; } createResourceReport.setResourceName(resourceName); String resourceKey = getResourceKey(resourceType, resourceName); createResourceReport.setResourceKey(resourceKey); PropertySimple templateNameProperty = defaultPluginConfig.getSimple(TEMPLATE_NAME_PROPERTY); String templateName = templateNameProperty.getStringValue(); ManagementView managementView = ProfileServiceFactory.getCurrentProfileView(); DeploymentTemplateInfo template; try { template = managementView.getTemplate(templateName); Map<String, ManagedProperty> managedProperties = template.getProperties(); Map<String, PropertySimple> customProps = ResourceComponentUtils.getCustomProperties(defaultPluginConfig); if (log.isDebugEnabled()) log.debug("BEFORE CREATE:\n" + DebugUtils.convertPropertiesToString(template)); ConversionUtils.convertConfigurationToManagedProperties(managedProperties, resourceConfig, resourceType, customProps); if (log.isDebugEnabled()) log.debug("AFTER CREATE:\n" + DebugUtils.convertPropertiesToString(template)); ConfigurationDefinition pluginConfigDef = resourceType.getPluginConfigurationDefinition(); Collection<PropertyDefinition> managedPropertyGroup = pluginConfigDef.getPropertiesInGroup(MANAGED_PROPERTY_GROUP); handleMiscManagedProperties(managedPropertyGroup, managedProperties, defaultPluginConfig); log.debug("Applying template [" + templateName + "] to create ManagedComponent of type [" + componentType + "]..."); try { managementView.applyTemplate(resourceName, template); managementView.process(); createResourceReport.setStatus(CreateResourceStatus.SUCCESS); } catch (Exception e) { log.error("Unable to apply template [" + templateName + "] to create ManagedComponent of type " + componentType + ".", e); createResourceReport.setStatus(CreateResourceStatus.FAILURE); createResourceReport.setException(e); } } catch (NoSuchDeploymentException e) { log.error("Unable to find template [" + templateName + "].", e); createResourceReport.setStatus(CreateResourceStatus.FAILURE); createResourceReport.setException(e); } catch (Exception e) { log.error("Unable to process create request", e); createResourceReport.setStatus(CreateResourceStatus.FAILURE); createResourceReport.setException(e); } } private void createContentBasedResource(CreateResourceReport createResourceReport, ResourceType resourceType) { ResourcePackageDetails details = createResourceReport.getPackageDetails(); PackageDetailsKey key = details.getKey(); // This is the full path to a temporary file which was written by the UI layer. String archivePath = key.getName(); try { File archiveFile = new File(archivePath); if (!DeploymentUtils.hasCorrectExtension(archiveFile, resourceType)) { createResourceReport.setStatus(CreateResourceStatus.FAILURE); createResourceReport.setErrorMessage("Incorrect extension specified on filename [" + archivePath + "]"); return; } abortIfApplicationAlreadyDeployed(resourceType, archiveFile); Configuration deployTimeConfig = details.getDeploymentTimeConfiguration(); @SuppressWarnings({"ConstantConditions"}) boolean deployExploded = deployTimeConfig.getSimple("deployExploded").getBooleanValue(); DeploymentStatus status = DeploymentUtils.deployArchive(archiveFile, getDeployDirectory(), deployExploded); if (status.getState() == DeploymentStatus.StateType.COMPLETED) { createResourceReport.setResourceName(archivePath); createResourceReport.setResourceKey(archivePath); createResourceReport.setStatus(CreateResourceStatus.SUCCESS); } else { createResourceReport.setStatus(CreateResourceStatus.FAILURE); createResourceReport.setErrorMessage(status.getMessage()); //noinspection ThrowableResultOfMethodCallIgnored createResourceReport.setException(status.getFailure()); } } catch (Throwable t) { log.error("Error deploying application for report: " + createResourceReport, t); createResourceReport.setStatus(CreateResourceStatus.FAILURE); createResourceReport.setException(t); } } private static Configuration getDefaultPluginConfiguration(ResourceType resourceType) { ConfigurationTemplate pluginConfigDefaultTemplate = resourceType.getPluginConfigurationDefinition().getDefaultTemplate(); return (pluginConfigDefaultTemplate != null) ? pluginConfigDefaultTemplate.createConfiguration() : new Configuration(); } private void abortIfApplicationAlreadyDeployed(ResourceType resourceType, File archiveFile) throws Exception { String archiveFileName = archiveFile.getName(); KnownDeploymentTypes deploymentType = ConversionUtils.getDeploymentType(resourceType); String deploymentTypeString = deploymentType.getType(); ProfileServiceFactory.refreshCurrentProfileView(); ManagementView managementView = ProfileServiceFactory.getCurrentProfileView(); Set<ManagedDeployment> managedDeployments = managementView.getDeploymentsForType(deploymentTypeString); for (ManagedDeployment managedDeployment : managedDeployments) { if (managedDeployment.getSimpleName().equals(archiveFileName)) throw new IllegalArgumentException("An application named '" + archiveFileName + "' is already deployed."); } } private File computeDeployDirectory() { ManagementView managementView = ProfileServiceFactory.getCurrentProfileView(); Set<ManagedDeployment> warDeployments; try { warDeployments = managementView.getDeploymentsForType( KnownDeploymentTypes.JavaEEWebApplication.getType()); } catch (Exception e) { throw new IllegalStateException(e); } ManagedDeployment standaloneWarDeployment = null; for (ManagedDeployment warDeployment : warDeployments) { if (warDeployment.getParent() == null) { standaloneWarDeployment = warDeployment; break; } } if (standaloneWarDeployment == null) // This could happen if no standalone WARs, including the admin console WAR, have been fully deployed yet. return null; log.debug("Standalone WAR deployment: " + standaloneWarDeployment.getName()); URL warUrl; try { warUrl = new URL(standaloneWarDeployment.getName()); } catch (MalformedURLException e) { throw new IllegalStateException(e); } File warFile = new File(warUrl.getPath()); File deployDir = warFile.getParentFile(); log.debug(">>>>> Deploy directory: " + deployDir); return deployDir; } @Override public EmsConnection getEmsConnection() { EmsConnection emsConnection = null; try { emsConnection = loadConnection(); } catch (Exception e) { log.error("Component attempting to access a connection that could not be loaded"); } return emsConnection; } /** * This is the preferred way to use a connection from within this class; methods should not access the connection * property directly as it may not have been instantiated if the connection could not be made. * * <p>If the connection has already been established, return the object reference to it. If not, attempt to make * a live connection to the JMX server.</p> * * <p>If the connection could not be made in the {@link #start(org.rhq.core.pluginapi.inventory.ResourceContext)} * method, this method will effectively try to load the connection on each attempt to use it. As such, multiple * threads may attempt to access the connection through this means at a time. Therefore, the method has been * made synchronized on instances of the class.</p> * * <p>If any errors are encountered, this method will log the error, taking into account logic to prevent spamming * the log file. Calling methods should take care to not redundantly log the exception thrown by this method.</p> * * @return live connection to the JMX server; this will not be <code>null</code> * * @throws Exception if there are any issues at all connecting to the server */ private synchronized EmsConnection loadConnection() throws Exception { if (this.connection == null) { try { Configuration pluginConfig = resourceContext.getPluginConfiguration(); String jbossHomeDir = pluginConfig.getSimpleValue(JBOSS_HOME_DIR_CONFIG_PROP, null); ConnectionSettings connectionSettings = new ConnectionSettings(); String connectionTypeDescriptorClass = pluginConfig.getSimple(JMXDiscoveryComponent.CONNECTION_TYPE) .getStringValue(); connectionSettings.initializeConnectionType((ConnectionTypeDescriptor) Class.forName( connectionTypeDescriptorClass).newInstance()); connectionSettings.setServerUrl(pluginConfig.getSimpleValue(NAMING_URL_CONFIG_PROP, null)); connectionSettings.setPrincipal(pluginConfig.getSimpleValue(PRINCIPAL_CONFIG_PROP, null)); connectionSettings.setCredentials(pluginConfig.getSimpleValue(CREDENTIALS_CONFIG_PROP, null)); connectionSettings.setLibraryURI(jbossHomeDir); ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.discoverServerClasses(connectionSettings); if (connectionSettings.getAdvancedProperties() == null) { connectionSettings.setAdvancedProperties(new Properties()); } connectionSettings.getAdvancedProperties().setProperty(JNP_DISABLE_DISCOVERY_JNP_INIT_PROP, "true"); // Make sure the timeout always happens, even if the JBoss server is hung. connectionSettings.getAdvancedProperties().setProperty("jnp.timeout", String.valueOf(JNP_TIMEOUT)); connectionSettings.getAdvancedProperties().setProperty("jnp.sotimeout", String.valueOf(JNP_SO_TIMEOUT)); // Tell EMS to make copies of jar files so that the ems classloader doesn't lock // application files (making us unable to update them) Bug: JBNADM-670 // TODO GH: turn this off in the embedded case connectionSettings.getControlProperties().setProperty(ConnectionFactory.COPY_JARS_TO_TEMP, String.valueOf(Boolean.TRUE)); // But tell it to put them in a place that we clean up when shutting down the agent connectionSettings.getControlProperties().setProperty(ConnectionFactory.JAR_TEMP_DIR, resourceContext.getTemporaryDirectory().getAbsolutePath()); connectionSettings.getAdvancedProperties().setProperty(InternalVMTypeDescriptor.DEFAULT_DOMAIN_SEARCH, "jboss"); log.info("Loading JBoss connection [" + connectionSettings.getServerUrl() + "] with install path [" + connectionSettings.getLibraryURI() + "]..."); ConnectionProvider connectionProvider = connectionFactory.getConnectionProvider(connectionSettings); this.connection = connectionProvider.connect(); this.connection.loadSynchronous(false); // this loads all the MBeans this.consecutiveConnectionErrors = 0; try { this.mainDeployer = new MainDeployer(this.connection); } catch (Exception e) { log.error("Unable to access MainDeployer MBean required for creation and deletion of managed " + "resources - this should never happen. Cause: " + e); } if (log.isDebugEnabled()) log.debug("Successfully made connection to the AS instance for resource [" + this.resourceContext.getResourceKey() + "]"); } catch (Exception e) { // The connection will be established even in the case that the principal cannot be authenticated, // but the connection will not work. That failure seems to come from the call to loadSynchronous after // the connection is established. If we get to this point that an exception was thrown, close any // connection that was made and null it out so we can try to establish it again. if (connection != null) { if (log.isDebugEnabled()) log.debug("Connection created but an exception was thrown. Closing the connection.", e); connection.close(); connection = null; } // Since the connection is attempted each time it's used, failure to connect could result in log // file spamming. Log it once for every 10 consecutive times it's encountered. if (consecutiveConnectionErrors % 10 == 0) { log.warn("Could not establish connection to the JBoss AS instance [" + (consecutiveConnectionErrors + 1) + "] times for resource [" + resourceContext.getResourceKey() + "]", e); } if (log.isDebugEnabled()) log.debug("Could not connect to the JBoss AS instance for resource [" + resourceContext.getResourceKey() + "]", e); consecutiveConnectionErrors++; throw e; } } return connection; } public List<EmsBean> getWebApplicationEmsBeans(String applicationName) { String pattern = "jboss.web:host=%host%,path=" + getContextPath(applicationName) + ",type=Manager"; ObjectNameQueryUtility queryUtil = new ObjectNameQueryUtility(pattern); return getEmsConnection().queryBeans(queryUtil.getTranslatedQuery()); } public List<EmsBean> getConvergedSipApplicationEmsBeans(String applicationName) { String pattern = "jboss.web:host=%host%,path=" + getContextPath(applicationName) + ",type=SipManager"; ObjectNameQueryUtility queryUtil = new ObjectNameQueryUtility(pattern); return getEmsConnection().queryBeans(queryUtil.getTranslatedQuery()); } public static String getContextPath(String contextRoot) { return contextRoot.equals("/") ? "/" : "/" + contextRoot; } }