/* * 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.core.pc.inventory; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Method; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jetbrains.annotations.NotNull; import org.rhq.core.clientapi.agent.PluginContainerException; import org.rhq.core.clientapi.agent.metadata.ResourceTypeNotEnabledException; import org.rhq.core.clientapi.server.discovery.AutoDiscoveryRequest; import org.rhq.core.clientapi.server.discovery.InventoryReport; import org.rhq.core.domain.resource.InventoryStatus; import org.rhq.core.domain.resource.ProcessScan; import org.rhq.core.domain.resource.Resource; import org.rhq.core.domain.resource.ResourceCategory; import org.rhq.core.domain.resource.ResourceType; import org.rhq.core.domain.state.discovery.AutoDiscoveryScanType; import org.rhq.core.pc.plugin.PluginComponentFactory; import org.rhq.core.pc.plugin.PluginManager; import org.rhq.core.pluginapi.inventory.ProcessScanResult; import org.rhq.core.pluginapi.inventory.ResourceComponent; import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent; import org.rhq.core.system.ProcessInfo; import org.rhq.core.system.SystemInfo; import org.rhq.core.system.SystemInfoFactory; import org.rhq.core.system.pquery.ProcessInfoQuery; import org.rhq.core.util.exception.ExceptionPackage; import org.rhq.core.util.exception.Severity; /** * Standard platform/server inventory detection execution. This looks for top level servers, typically doing * process-based discovery. It should probably be renamed to ServerDiscoveryExecutor as "AutoDiscovery" is * pretty much redundant and non-descriptive. This is typically called in a non-blocking fashion, and the report * is returned asynchronously to the server. It is available for direct execution via a Future when running in * an embedded mode. This is complemented by {@link RuntimeDiscoveryExecutor} for discovering new child resources * in the existing inventory hierarchy. * * @author Greg Hinkle * @author John Mazzitelli * @author Ian Springer */ public class AutoDiscoveryExecutor implements Runnable, Callable<InventoryReport> { private static final Log log = LogFactory.getLog(AutoDiscoveryExecutor.class); private final AutoDiscoveryRequest autoDiscoveryRequest; private final InventoryManager inventoryManager; public AutoDiscoveryExecutor(AutoDiscoveryRequest autoDiscoveryRequest, InventoryManager inventoryManager) { this.autoDiscoveryRequest = autoDiscoveryRequest; this.inventoryManager = inventoryManager; } public void run() { call(); } @NotNull public InventoryReport call() { log.info("Executing server discovery scan..."); InventoryReport report = new InventoryReport(inventoryManager.getAgent()); try { report.setStartTime(System.currentTimeMillis()); if ((autoDiscoveryRequest == null) || autoDiscoveryRequest.getScanTypes().contains(AutoDiscoveryScanType.Plugin)) { List<ProcessInfo> processInfos = getProcessInfos(); pluginDiscovery(report, processInfos); } report.setEndTime(System.currentTimeMillis()); if (report.getAddedRoots().size() == 1 && report.getAddedRoots().iterator().next().getResourceType().getCategory() == ResourceCategory.PLATFORM) { Resource platform = report.getAddedRoots().iterator().next(); log.info("Discovered new platform with " + platform.getChildResources().size() + " child server(s)."); } else { log.info("Discovered " + report.getAddedRoots().size() + " new server(s)."); } if (log.isDebugEnabled()) { log.debug("Server discovery scan took [" + (report.getEndTime() - report.getStartTime()) + "] ms."); } // TODO GH: This is principally valuable only until we work out the last of the data transfer situations if (log.isTraceEnabled()) { ByteArrayOutputStream baos = new ByteArrayOutputStream(10000); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(report); log.trace("Server Discovery report for [" + report.getResourceCount() + "] resources with a size of [" + baos.size() + "] bytes"); } inventoryManager.handleReport(report); } catch (Exception e) { log.warn("Exception caught while executing server discovery scan.", e); report.addError(new ExceptionPackage(Severity.Warning, e)); } return report; } private List<ProcessInfo> getProcessInfos() { SystemInfo systemInfo = SystemInfoFactory.createSystemInfo(); log.debug("Retrieving process table..."); long startTime = System.currentTimeMillis(); List<ProcessInfo> processInfos = null; try { processInfos = systemInfo.getAllProcesses(); } catch (UnsupportedOperationException uoe) { log.debug("Cannot perform process scan - not supported on this platform. (" + systemInfo.getClass() + ")"); } long elapsedTime = System.currentTimeMillis() - startTime; if (log.isDebugEnabled()) { log.debug("Retrieval of process table took " + elapsedTime + " ms."); } return processInfos; } /** * Goes through server plugins running auto discovery * * @param report the inventory report to which to add the discovered servers * @param processInfos */ @SuppressWarnings("unchecked") private void pluginDiscovery(InventoryReport report, List<ProcessInfo> processInfos) { inventoryManager.executePlatformScan(); PluginManager pluginManager = inventoryManager.getPluginManager(); PluginComponentFactory factory = inventoryManager.getPluginComponentFactory(); Set<ResourceType> serverTypes = pluginManager.getMetadataManager().getTypesForCategory(ResourceCategory.SERVER); ResourceContainer platformContainer = inventoryManager.getResourceContainer(inventoryManager.getPlatform()); Resource platformResource = platformContainer.getResource(); for (ResourceType serverType : serverTypes) { if (!serverType.getParentResourceTypes().isEmpty()) { continue; // TODO GH: Need to stop discovering embedded tomcats here and other non-top level servers } try { ResourceDiscoveryComponent component = factory.getDiscoveryComponent(serverType, platformContainer); // TODO GH: Manage plugin component call /* TODO GH: Fixme * if (!verifyComponentCompatibility(component,platformComponent)) { log.warn("Resource has parent * resource with incompatible component " + serverType); continue; } */ if (platformContainer.getSynchronizationState() == ResourceContainer.SynchronizationState.NEW) { report.addAddedRoot(platformResource); } // Perform auto-discovery PIQL queries now to see if we can auto-detect servers that are currently running. List<ProcessScanResult> scanResults = performProcessScans(processInfos, serverType); Set<Resource> discoveredServers = this.inventoryManager.executeComponentDiscovery(serverType, component, platformContainer, scanResults); for (Resource discoveredServer : discoveredServers) { Resource inventoriedResource = this.inventoryManager.mergeResourceFromDiscovery(discoveredServer, platformResource); if (inventoriedResource.getInventoryStatus() == InventoryStatus.NEW) { // The resource is new to the Server inventory. if (platformContainer.getSynchronizationState() == ResourceContainer.SynchronizationState.SYNCHRONIZED) { // The Platform is already in Server inventory, so this'll be a report root. Otherwise, // it'll get included in the report under the Platform. report.addAddedRoot(inventoriedResource); } } } } catch (ResourceTypeNotEnabledException rtne) { // skipping all ignored resources } catch (Throwable e) { report.getErrors().add(new ExceptionPackage(Severity.Severe, e)); log.error("Error in auto discovery", e); } } // if we have nothing, our plugins didn't discovery anything, but we want to at least report the platform if (report.getAddedRoots().isEmpty()) { if (platformContainer.getSynchronizationState() == ResourceContainer.SynchronizationState.NEW) { report.addAddedRoot(platformResource); } } return; } private List<ProcessScanResult> performProcessScans(List<ProcessInfo> processInfos, ResourceType serverType) { if (processInfos == null || processInfos.isEmpty()) return Collections.emptyList(); List<ProcessScanResult> scanResults = new ArrayList<ProcessScanResult>(); Set<ProcessScan> processScans = serverType.getProcessScans(); if (processScans != null && !processScans.isEmpty()) { log.debug("Executing process scans for server type " + serverType + "..."); ProcessInfoQuery piq = new ProcessInfoQuery(processInfos); for (ProcessScan processScan : processScans) { List<ProcessInfo> queryResults = piq.query(processScan.getQuery()); if ((queryResults != null) && (queryResults.size() > 0)) { for (ProcessInfo autoDiscoveredProcess : queryResults) { scanResults.add(new ProcessScanResult(processScan, autoDiscoveredProcess)); if (log.isDebugEnabled()) { log.debug("Process scan auto-detected potential new server Resource: scan=[" + processScan + "], discovered-process=[" + autoDiscoveredProcess + "]"); } } } } } return scanResults; } private boolean verifyComponentCompatibility(ResourceDiscoveryComponent component, ResourceComponent parentResourceComponent) throws PluginContainerException { Method discoveryCall; try { discoveryCall = component.getClass().getMethod("discoverResources", ResourceCategory.class); } catch (NoSuchMethodException e) { throw new PluginContainerException("Resource component doesn't implement resource component interface", e); } Class<?> componentParameterType = discoveryCall.getParameterTypes()[0]; TypeVariable<? extends Class<?>>[] types = componentParameterType.getTypeParameters(); if (types.length == 0) { // The component doesn't declare type and therefore doesn't care what its parent type is return true; } TypeVariable<? extends Class<?>> type = types[0]; // TODO GH: Figure this out // if (type.getBounds()) // can we use: parentResourceComponent.getClass().isAssignableFrom( type.getGenericDeclaration().getClass() ) return true; } }