/* * 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; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.resource.ResourceUpgradeReport; import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails; import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext; import org.rhq.core.pluginapi.upgrade.ResourceUpgradeContext; import org.rhq.core.pluginapi.upgrade.ResourceUpgradeFacet; /** * Discover subsystems. We need to distinguish two cases denoted by the path * plugin config: * <ul> * <li>Path is a single 'word': here the value denotes a key in the resource path * of AS7, that identifies a child type see e.g. the Connectors below the JBossWeb * service in the plugin descriptor. There can be multiple resources of the given * type. In addition it is possible that a path entry in configuration shares multiple * types that are separated by the pipe symbol.</li> * <li>Path is a key-value pair (e.g. subsystem=web ). This denotes a singleton * subsystem with a fixes path within AS7 (perhaps below another resource in the * tree.</li> * </ul> * * This subclass adds logic for discovering different versions of the same logical resource, * by stripping version info out of the path, removing it from the resourceName and setting it * as the resourceVersion. * <p/> * The default version matching pattern is designed to match maven-like version stamping: * <code>name-version.ext</code> being the basic format. The version must minimally contain * <code>major.minor</code> values. See Maven documentation for more information. The default * pattern is <code>"^(.*?)-([0-9]+\\.[0-9].*)(\\..*)$"</code>. The same pattern is applied to * all <code>Deployment</code> and <code>Subdeployment</code> artifacts. * <p/> * To override the default pattern the following environment variable can be defined: * <code>rhq.as7.VersionedSubsystemDiscovery.pattern=theDesiredRegexPattern</code>. The regex * *must* capture three groups as does the default. Group1=name, Group2=version, Group3=extension. * <p/> * To disable versioned discovery and maintain version strings in the deployment names, set * <code>rhq.as7.VersionedSubsystemDiscovery.pattern=disable</code> * * @author Jay Shaughnessy */ public class VersionedSubsystemDiscovery extends AbstractVersionedSubsystemDiscovery implements ResourceUpgradeFacet { private static final Log LOG = LogFactory.getLog(VersionedSubsystemDiscovery.class); private static final Pattern COMMA_PATTERN = Pattern.compile(","); @Override public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext<BaseComponent<?>> context) throws Exception { // Perform the standard discovery. This can return resources with versions in the name, // key and path. Set<DiscoveredResourceDetails> details = super.discoverResources(context); if (DISABLED || null == details || details.isEmpty()) { return details; } // Now, post-process the discovery as needed. We need to strip the versions from the // resource name and the resource key. We want to leave them in the path plugin config // property, that value reflects the actual DMR used to query EAP. // The stripped versions are then used to set the resource version string. // Work with a list because we may update the key, which is used in the DiscoveredResourceDetails.equals() ArrayList<DiscoveredResourceDetails> updatedDetails = new ArrayList<DiscoveredResourceDetails>(details); HashMap<String, Integer> keyCount = new HashMap<String, Integer>(updatedDetails.size()); details.clear(); for (DiscoveredResourceDetails detail : updatedDetails) { MATCHER.reset(detail.getResourceName()); if (MATCHER.matches()) { // reset the resource name with the stripped value detail.setResourceName(MATCHER.group(1) + MATCHER.group(3)); // The version string for a subdeployment must incorporate the parent deployment's version // so that we detect an overall version change if the parent is re-deployed. Without this // the Subdeployment will not be properly updated if its version remains unchanged in the // updated Deployment. if (SUBDEPLOYMENT_TYPE.equals(context.getResourceType().getName())) { String parentResourceVersion = context.getParentResourceContext().getVersion(); parentResourceVersion = (null == parentResourceVersion) ? "" : (parentResourceVersion + "/"); detail.setResourceVersion(parentResourceVersion + MATCHER.group(2)); } else { detail.setResourceVersion(MATCHER.group(2)); } } StringBuilder sb = new StringBuilder(); String comma = ""; for (String segment : COMMA_PATTERN.split(detail.getResourceKey())) { sb.append(comma); comma = ","; MATCHER.reset(segment); if (MATCHER.matches()) { sb.append(MATCHER.group(1)).append(MATCHER.group(3)); } else { sb.append(segment); } } detail.setResourceKey(sb.toString()); Integer count = keyCount.get(detail.getResourceKey()); keyCount.put(detail.getResourceKey(), (null == count ? 1 : ++count)); } // Now, make sure that after we've stripped the versions that we don't end up with multiple discoveries // for the same key, this is an indication that there are multiple versions of the same Deployment deployed. // In this case we remove the duplicates and issue a warning so the user can hopefully rectify the situation. for (Map.Entry<String, Integer> entry : keyCount.entrySet()) { if (entry.getValue() > 1) { LOG.warn("Discovered multiple resources with resource key [" + entry.getKey() + "]. This is not allowed and they will be removed from discovery. This is typically caused by " + "having multiple versions of the same Deployment deployed. To solve the problem either remove " + "all but one version of the problem deployment or disable versioned deployment handling by " + "setting -Drhq.as7.VersionedSubsystemDiscovery.pattern=disable for the agent."); for (Iterator<DiscoveredResourceDetails> i = updatedDetails.iterator(); i.hasNext();) { DiscoveredResourceDetails detail = i.next(); if (detail.getResourceKey().equals(entry.getKey())) { i.remove(); } } } } details.addAll(updatedDetails); return details; } // The Matching logic here is the same as above, but instead of setting the discovery details we // set new values in the upgrade report for name, version and key. Note that if multiple resources // upgrade to the same resource key it will be caught and fail downstream. @Override public ResourceUpgradeReport upgrade(ResourceUpgradeContext inventoriedResource) { ResourceUpgradeReport result = null; if (DISABLED) { return result; } MATCHER.reset(inventoriedResource.getName()); if (MATCHER.matches()) { result = new ResourceUpgradeReport(); result.setForceGenericPropertyUpgrade(true); // It is critical the name and version get upgraded // reset the resource name with the stripped value result.setNewName(MATCHER.group(1) + MATCHER.group(3)); // The version string for a subdeployment must incorporate the parent deployment's version // so that we detect an overall version change if the parent is re-deployed. Without this // the Subdeployment will not be properly updated if its version remains unchanged in the // updated Deployment. if (SUBDEPLOYMENT_TYPE.equals(inventoriedResource.getResourceType().getName())) { String parentResourceVersion = inventoriedResource.getParentResourceContext().getVersion(); parentResourceVersion = (null == parentResourceVersion) ? "" : (parentResourceVersion + "/"); result.setNewVersion(parentResourceVersion + MATCHER.group(2)); } else { result.setNewVersion(MATCHER.group(2)); } } StringBuilder sb = new StringBuilder(); String comma = ""; boolean upgradeKey = false; for (String segment : COMMA_PATTERN.split(inventoriedResource.getResourceKey())) { sb.append(comma); comma = ","; MATCHER.reset(segment); if (MATCHER.matches()) { upgradeKey = true; sb.append(MATCHER.group(1)).append(MATCHER.group(3)); } else { sb.append(segment); } } if (upgradeKey) { if (null == result) { result = new ResourceUpgradeReport(); } result.setNewResourceKey(sb.toString()); } if (null != result && LOG.isDebugEnabled()) { LOG.debug("Requesting upgrade: " + result); } return result; } }