/* * RHQ Management Platform * Copyright (C) 2005-2013 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.plugins.jbossas5; import static org.rhq.core.domain.resource.CreateResourceStatus.SUCCESS; import java.io.File; import java.io.InputStream; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.jboss.jbossnetwork.product.jbpm.handlers.ControlActionFacade; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.mc4j.ems.connection.EmsConnection; import org.mc4j.ems.connection.support.metadata.InternalVMTypeDescriptor; import org.jboss.deployers.spi.management.ManagementView; 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.ManagedComponent; import org.jboss.managed.api.ManagedProperty; import org.jboss.metatype.api.values.MetaValue; import org.jboss.metatype.api.values.SimpleValue; import org.jboss.on.common.jbossas.JBPMWorkflowManager; import org.jboss.on.common.jbossas.JBossASPaths; import org.jboss.on.common.jbossas.JmxConnectionHelper; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.PropertyMap; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; import org.rhq.core.domain.content.PackageType; import org.rhq.core.domain.content.transfer.DeployPackageStep; import org.rhq.core.domain.content.transfer.DeployPackagesResponse; import org.rhq.core.domain.content.transfer.RemovePackagesResponse; 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.pluginapi.configuration.ConfigurationFacet; import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport; import org.rhq.core.pluginapi.content.ContentContext; import org.rhq.core.pluginapi.content.ContentFacet; import org.rhq.core.pluginapi.content.ContentServices; import org.rhq.core.pluginapi.event.log.LogFileEventResourceComponentHelper; import org.rhq.core.pluginapi.inventory.CreateChildResourceFacet; import org.rhq.core.pluginapi.inventory.CreateResourceReport; import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException; import org.rhq.core.pluginapi.inventory.ResourceComponent; import org.rhq.core.pluginapi.inventory.ResourceContext; import org.rhq.core.pluginapi.measurement.MeasurementFacet; import org.rhq.core.pluginapi.operation.OperationFacet; import org.rhq.core.pluginapi.operation.OperationResult; import org.rhq.core.util.file.FileUtil; import org.rhq.plugins.jbossas5.adapter.api.PropertyAdapter; import org.rhq.plugins.jbossas5.adapter.api.PropertyAdapterFactory; import org.rhq.plugins.jbossas5.connection.LocalProfileServiceConnectionProvider; import org.rhq.plugins.jbossas5.connection.ProfileServiceConnection; import org.rhq.plugins.jbossas5.connection.ProfileServiceConnectionProvider; import org.rhq.plugins.jbossas5.connection.RemoteProfileServiceConnectionProvider; import org.rhq.plugins.jbossas5.helper.CreateChildResourceFacetDelegate; import org.rhq.plugins.jbossas5.helper.InPluginControlActionFacade; import org.rhq.plugins.jbossas5.helper.JBossAS5ConnectionTypeDescriptor; import org.rhq.plugins.jbossas5.util.ConversionUtils; import org.rhq.plugins.jbossas5.util.ManagedComponentUtils; /** * ResourceComponent for a JBoss AS, 5.1.0.CR1 or later, Server. * * @author Jason Dobies * @author Mark Spritzler * @author Ian Springer */ public class ApplicationServerComponent<T extends ResourceComponent<?>> implements ResourceComponent<T>, ProfileServiceComponent<T>, CreateChildResourceFacet, MeasurementFacet, ConfigurationFacet, ProgressListener, ContentFacet, OperationFacet { private static final Pattern METRIC_NAME_PATTERN = Pattern.compile("(.*)\\|(.*)\\|(.*)\\|(.*)"); private static final Map<String, String> ALTERNATE_METRIC_NAMES = new HashMap<String, String>(); static { ALTERNATE_METRIC_NAMES.put("MCBean|JTA|*|transactionCount", "MCBean|JTA|*|numberOfTransactions"); ALTERNATE_METRIC_NAMES.put("MCBean|JTA|*|commitCount", "MCBean|JTA|*|numberOfCommittedTransactions"); ALTERNATE_METRIC_NAMES.put("MCBean|JTA|*|rollbackCount", "MCBean|JTA|*|numberOfApplicationRollbacks"); ALTERNATE_METRIC_NAMES.put("MCBean|ServerConfig|*|partitionName", "MCBean|HAPartition|*|partitionName"); } private static final Map<String, String> VERIFIED_METRIC_NAMES = new HashMap<String, String>(); private final Log log = LogFactory.getLog(this.getClass()); private ResourceContext resourceContext; private ProfileServiceConnection connection; private JmxConnectionHelper jmxConnectionHelper; private ApplicationServerContentFacetDelegate contentFacetDelegate; private ApplicationServerOperationsDelegate operationDelegate; private LogFileEventResourceComponentHelper logFileEventDelegate; private CreateChildResourceFacetDelegate createChildResourceDelegate; public AvailabilityType getAvailability() { try { connectToProfileService(); } catch (RuntimeException re) { return AvailabilityType.DOWN; } AvailabilityType availability; if (this.connection != null) { try { ManagementView managementView = this.connection.getManagementView(); managementView.load(); //let's see if the connection corresponds to the server //this component represents. This is to prevent 2 servers //with the same JNP URL to be reported as UP when just one //of them can be up at a time. ManagedComponent serverConfig = managementView .getComponentsForType(new ComponentType("MCBean", "ServerConfig")).iterator().next(); String reportedServerHomeDirPath = (String) ((SimpleValue) serverConfig.getProperty("serverHomeDir") .getValue()).getValue(); String configuredServerHomeDirPath = resourceContext.getPluginConfiguration().getSimpleValue( ApplicationServerPluginConfigurationProperties.SERVER_HOME_DIR, null); //the paths might be symlinked File reportedServerHomeDir = new File(reportedServerHomeDirPath); File configuredServerHomeDir = new File(configuredServerHomeDirPath); availability = reportedServerHomeDir.getCanonicalPath().equals( configuredServerHomeDir.getCanonicalPath()) ? AvailabilityType.UP : AvailabilityType.DOWN; } catch (Exception e) { availability = AvailabilityType.DOWN; } } else { availability = AvailabilityType.DOWN; } return availability; } public void start(ResourceContext resourceContext) { this.resourceContext = resourceContext; this.operationDelegate = new ApplicationServerOperationsDelegate(this); // Connect to the JBAS instance's Profile Service and JMX MBeanServer. connectToProfileService(); initializeEmsConnection(); // Now create all our helpers and delegates. this.logFileEventDelegate = new LogFileEventResourceComponentHelper(this.resourceContext); this.logFileEventDelegate.startLogFileEventPollers(); JBPMWorkflowManager workflowManager = createJbpmWorkflowManager(resourceContext); File configPath = getConfigurationPath(); this.contentFacetDelegate = new ApplicationServerContentFacetDelegate(workflowManager, configPath, resourceContext.getContentContext()); this.createChildResourceDelegate = new CreateChildResourceFacetDelegate(this, this.getResourceContext()); return; } public void stop() { this.logFileEventDelegate.stopLogFileEventPollers(); disconnectFromProfileService(); this.jmxConnectionHelper.closeConnection(); } // ------------ MeasurementFacet Implementation ------------ public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> requests) { ManagementView managementView = getConnection().getManagementView(); for (MeasurementScheduleRequest request : requests) { String requestName = request.getName(); String verifiedMetricName = VERIFIED_METRIC_NAMES.get(requestName); String metricName = (verifiedMetricName != null) ? verifiedMetricName : requestName; try { Serializable value = null; boolean foundProperty = false; try { value = getMetric(managementView, metricName); foundProperty = true; } catch (ManagedComponentUtils.PropertyNotFoundException e) { // ignore } if (value == null) { metricName = ALTERNATE_METRIC_NAMES.get(metricName); if (metricName != null) { try { value = getMetric(managementView, metricName); foundProperty = true; } catch (ManagedComponentUtils.PropertyNotFoundException e) { // ignore } } } if (!foundProperty) { List<String> propertyNames = new ArrayList<String>(2); propertyNames.add(requestName); if (ALTERNATE_METRIC_NAMES.containsKey(requestName)) { propertyNames.add(ALTERNATE_METRIC_NAMES.get(requestName)); } throw new IllegalStateException("A property was not found with any of the following names: " + propertyNames); } if (value != null) { VERIFIED_METRIC_NAMES.put(requestName, metricName); } else { 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 '" + requestName + "'.", 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 report) { if (creatingDatasourceOrConnectionFactory(report)) { report = preCreateDatasourceOrConnectionFactory(report); } report = this.createChildResourceDelegate.createResource(report); if (report.getStatus() != SUCCESS) { return report; } if (creatingDatasourceOrConnectionFactory(report)) { report = postCreateDatasourceOrConnectionFactory(report); } return report; } private CreateResourceReport preCreateDatasourceOrConnectionFactory(CreateResourceReport report) { Configuration resourceConfiguration = report.getResourceConfiguration(); PropertyMap securityDomainPropertyMap = (PropertyMap) resourceConfiguration.get("security-domain"); PropertySimple securityDeploymentType = securityDomainPropertyMap.getSimple("securityDeploymentType"); if (securityDeploymentType.getStringValue() == null) { securityDeploymentType.setValue("NONE"); } return report; } private CreateResourceReport postCreateDatasourceOrConnectionFactory(CreateResourceReport report) { Configuration resourceConfiguration = report.getResourceConfiguration(); ConfigurationDefinition resourceConfigurationDefinition = report.getResourceType() .getResourceConfigurationDefinition(); ComponentType componentType = ConversionUtils.getComponentType(report.getResourceType()); String componentName = report.getResourceKey(); try { ManagementView managementView = getConnection().getManagementView(); ManagedComponent managedComponent = managementView.getComponent(componentName, componentType); Map<String, ManagedProperty> managedProperties = managedComponent.getProperties(); ManagedProperty managedProperty = managedProperties.get("security-domain"); MetaValue metaValue = managedProperty.getValue(); PropertyAdapter propertyAdapter = PropertyAdapterFactory.getPropertyAdapter(metaValue); propertyAdapter.populateMetaValueFromProperty(resourceConfiguration.get("security-domain"), metaValue, resourceConfigurationDefinition.get("security-domain")); managementView.updateComponent(managedComponent); managementView.load(); } catch (Exception e) { report.setErrorMessage("Resource was created but an error occured while updating security-domain property"); report.setException(e); } return report; } private boolean creatingDatasourceOrConnectionFactory(CreateResourceReport report) { String resourceTypeName = report.getResourceType().getName(); return Arrays.asList("No Tx Datasource", "Local Tx Datasource", "XA Datasource", "No Tx ConnectionFactory", "Tx ConnectionFactory").contains(resourceTypeName); } // ProgressListener -------------------------------------------- public void progressEvent(ProgressEvent eventInfo) { log.debug(eventInfo); } @Nullable public ProfileServiceConnection getConnection() { try { connectToProfileService(); } catch (RuntimeException ex) { } return this.connection; } // ContentFacet ------------------------------------------------- public DeployPackagesResponse deployPackages(Set<ResourcePackageDetails> packages, ContentServices contentServices) { return contentFacetDelegate.deployPackages(packages, contentServices); } public Set<ResourcePackageDetails> discoverDeployedPackages(PackageType type) { return contentFacetDelegate.discoverDeployedPackages(type); } public List<DeployPackageStep> generateInstallationSteps(ResourcePackageDetails packageDetails) { return contentFacetDelegate.generateInstallationSteps(packageDetails); } public RemovePackagesResponse removePackages(Set<ResourcePackageDetails> packages) { return contentFacetDelegate.removePackages(packages); } public InputStream retrievePackageBits(ResourcePackageDetails packageDetails) { return contentFacetDelegate.retrievePackageBits(packageDetails); } // --------------------------------------------------------------- private void connectToProfileService() { if (this.connection != null) { return; } // TODO: Check for a defunct connection and if found try to reconnect? ProfileServiceConnectionProvider connectionProvider; if (runningEmbedded()) { connectionProvider = new LocalProfileServiceConnectionProvider(); } else { Configuration pluginConfig = this.resourceContext.getPluginConfiguration(); String namingURL = pluginConfig.getSimpleValue(ApplicationServerPluginConfigurationProperties.NAMING_URL, null); validateNamingURL(namingURL); String principal = pluginConfig.getSimpleValue(ApplicationServerPluginConfigurationProperties.PRINCIPAL, null); String credentials = pluginConfig.getSimpleValue( ApplicationServerPluginConfigurationProperties.CREDENTIALS, null); connectionProvider = new RemoteProfileServiceConnectionProvider(namingURL, principal, credentials); } if (Thread.interrupted()) { // In case we've been timed out by the component facet invoker, clear the interrupted status, // so that when the below call to connect() tried to make a remote call, JBoss Remoting // doesn't throw an InterruptedException and short circuit our attempts to reconnect. log.debug("Ignoring facet timeout in order to reconnect to Profile Service."); } try { this.connection = connectionProvider.connect(); } catch (RuntimeException e) { Throwable rootCause = ExceptionUtils.getRootCause(e); rootCause = rootCause == null ? e : rootCause; if (rootCause instanceof SecurityException) { if (log.isDebugEnabled()) { log.debug("Failed to connect to Profile Service.", e); } else { log.warn("Failed to connect to Profile Service - cause: " + rootCause); } throw new InvalidPluginConfigurationException( "Values of 'principal' and/or 'credentials' connection properties are invalid.", rootCause); } log.debug("Failed to connect to Profile Service.", e); // it is not a good idea to catch RuntimeException. If we really have to do it // we need to re-throw it (as it can be InterrupedException or SocketTimoeout..) if(rootCause instanceof java.net.ConnectException) { // Acceptable situation to allow non-started containers to be added return; } throw e; } } private void disconnectFromProfileService() { if (this.connection != null) { try { this.connection.getConnectionProvider().disconnect(); } catch (RuntimeException e) { log.debug("Failed to disconnect from Profile Service.", e); } finally { this.connection = null; } } } @NotNull private JBossASPaths getJBossASPaths() { Configuration pluginConfiguration = this.resourceContext.getPluginConfiguration(); String homeDir = pluginConfiguration.getSimpleValue(ApplicationServerPluginConfigurationProperties.HOME_DIR, null); String serverHomeDir = pluginConfiguration.getSimpleValue( ApplicationServerPluginConfigurationProperties.SERVER_HOME_DIR, null); return new JBossASPaths(homeDir, serverHomeDir); } private boolean runningEmbedded() { Configuration pluginConfiguration = this.resourceContext.getPluginConfiguration(); String namingUrl = pluginConfiguration.getSimpleValue( ApplicationServerPluginConfigurationProperties.NAMING_URL, null); return namingUrl == null; } @NotNull private File resolvePathRelativeToHomeDir(@NotNull String path) { File configDir = new File(path); if (!FileUtil.isAbsolutePath(path)) { Configuration pluginConfig = this.resourceContext.getPluginConfiguration(); String homeDir = pluginConfig.getSimple(ApplicationServerPluginConfigurationProperties.HOME_DIR) .getStringValue(); configDir = new File(homeDir, path); } // BZ 903402 - get the real absolute path - under most conditions, it's the same thing, but if on windows // the drive letter might not have been specified - this makes sure the drive letter is specified. return configDir.getAbsoluteFile(); } private static void validateNamingURL(String namingURL) { URI namingURI; try { namingURI = new URI(namingURL); } catch (URISyntaxException e) { throw new RuntimeException("Naming URL '" + namingURL + "' is not valid: " + e.getLocalizedMessage()); } if (!namingURI.isAbsolute()) throw new RuntimeException("Naming URL '" + namingURL + "' is not absolute."); if (!namingURI.getScheme().equals("jnp")) throw new RuntimeException("Naming URL '" + namingURL + "' has an invalid protocol - the only valid protocol is 'jnp'."); } public EmsConnection getEmsConnection() { return jmxConnectionHelper.getEmsConnection(); } @NotNull private static String getRequiredPropertyValue(@NotNull Configuration config, @NotNull String propName) { String propValue = config.getSimpleValue(propName, null); if (propValue == null) { // Something's not right - neither autodiscovery, nor the config // edit GUI, should ever allow this. throw new IllegalStateException("Required property '" + propName + "' is not set."); } return propValue; } @NotNull public ResourceContext getResourceContext() { return this.resourceContext; } public OperationResult invokeOperation(String name, Configuration parameters) throws InterruptedException, Exception { ApplicationServerSupportedOperations operation = Enum.valueOf(ApplicationServerSupportedOperations.class, name.toUpperCase()); return this.operationDelegate.invoke(operation, parameters); } private void initializeEmsConnection() { Configuration pluginConfiguration = this.resourceContext.getPluginConfiguration(); Configuration jmxConfig = new Configuration(); String jbossHomeDir = pluginConfiguration.getSimpleValue( ApplicationServerPluginConfigurationProperties.HOME_DIR, null); String connectorDescriptorType; boolean runningEmbedded = runningEmbedded(); if (runningEmbedded) { connectorDescriptorType = InternalVMTypeDescriptor.class.getName(); } else { String connectorAddress = pluginConfiguration.getSimpleValue( ApplicationServerPluginConfigurationProperties.NAMING_URL, null); String connectorPrincipal = pluginConfiguration.getSimpleValue( ApplicationServerPluginConfigurationProperties.PRINCIPAL, null); String connectorCredentials = pluginConfiguration.getSimpleValue( ApplicationServerPluginConfigurationProperties.CREDENTIALS, null); connectorDescriptorType = JBossAS5ConnectionTypeDescriptor.class.getName(); jmxConfig.put(new PropertySimple(JmxConnectionHelper.CONNECTOR_ADDRESS, connectorAddress)); jmxConfig.put(new PropertySimple(JmxConnectionHelper.CONNECTOR_CREDENTIALS, connectorCredentials)); jmxConfig.put(new PropertySimple(JmxConnectionHelper.CONNECTOR_PRINCIPAL, connectorPrincipal)); } jmxConfig.put(new PropertySimple(JmxConnectionHelper.CONNECTOR_DESCRIPTOR_TYPE, connectorDescriptorType)); jmxConfig.put(new PropertySimple(JmxConnectionHelper.JBOSS_HOME_DIR, jbossHomeDir)); this.jmxConnectionHelper = new JmxConnectionHelper(!runningEmbedded, resourceContext.getTemporaryDirectory()); EmsConnection conn = this.jmxConnectionHelper.getEmsConnection(jmxConfig); if (conn != null) { log.info("Successfully obtained a JMX connection to " + jmxConfig.getSimpleValue(JmxConnectionHelper.CONNECTOR_ADDRESS, "-n/a-")); } } private JBPMWorkflowManager createJbpmWorkflowManager(ResourceContext resourceContext) { ContentContext contentContext = resourceContext.getContentContext(); ControlActionFacade controlActionFacade = initControlActionFacade(); JBPMWorkflowManager workflowManager = new JBPMWorkflowManager(contentContext, controlActionFacade, this.getJBossASPaths()); return workflowManager; } private ControlActionFacade initControlActionFacade() { // Until the bugs get worked out of the calls back into the PC's operation framework, use the implementation // that will simply make calls directly in the plugin. // return new PluginContainerControlActionFacade(operationContext, this); return new InPluginControlActionFacade(this); } private File getConfigurationPath() { Configuration pluginConfig = this.resourceContext.getPluginConfiguration(); String serverHomeDir = getRequiredPropertyValue(pluginConfig, ApplicationServerPluginConfigurationProperties.SERVER_HOME_DIR); File configPath = resolvePathRelativeToHomeDir(serverHomeDir); if (!configPath.isDirectory()) { throw new InvalidPluginConfigurationException("Configuration path '" + configPath + "' does not exist."); } return configPath; } private static Serializable getMetric(ManagementView managementView, String metricName) throws ManagedComponentUtils.PropertyNotFoundException { // All metric names are expected to have the following syntax: // "<componentType>|<componentSubType>|<componentName>|<propertyName>" Matcher matcher = METRIC_NAME_PATTERN.matcher(metricName); if (!matcher.matches()) { throw new IllegalStateException("Metric name '" + metricName + "' does not match pattern '" + METRIC_NAME_PATTERN + "'."); } 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(managementView, componentType); } else { //component = ManagedComponentUtils.getManagedComponent(managementView, componentType, componentName); try { component = managementView.getComponent(componentName, componentType); } catch (Exception e) { throw new IllegalStateException("Error fetching component " + componentName + "of type " + componentType); } } return ManagedComponentUtils.getSimplePropertyValue(component, propertyName); } /** * Find the parent {@link ApplicationServerComponent} of the specified {@link ProfileServiceComponent}. * * @param profileServiceComponent * @return the parent {@link ApplicationServerComponent} or null */ public static ApplicationServerComponent<?> findApplicationServerComponent(ProfileServiceComponent profileServiceComponent) { ProfileServiceComponent component = profileServiceComponent; while (component != null && !(component instanceof ApplicationServerComponent)) { ResourceComponent parent = component.getResourceContext().getParentResourceComponent(); component = (parent instanceof ProfileServiceComponent) ? (ProfileServiceComponent) parent : null; } return (ApplicationServerComponent<?>) component; } }