/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License 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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.autoinventory.agent.server; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.hq.agent.AgentRemoteException; import org.hyperic.hq.agent.AgentRemoteValue; import org.hyperic.hq.agent.diagnostics.AgentDiagnosticObject; import org.hyperic.hq.agent.diagnostics.AgentDiagnostics; import org.hyperic.hq.agent.server.AgentDaemon; import org.hyperic.hq.agent.server.AgentRunningException; import org.hyperic.hq.agent.server.AgentStorageException; import org.hyperic.hq.agent.server.AgentStorageProvider; import org.hyperic.hq.agent.server.ConfigStorage; import org.hyperic.hq.agent.server.ConfigStorage.Key; import org.hyperic.hq.appdef.shared.AppdefEntityConstants; import org.hyperic.hq.autoinventory.AutoinventoryException; import org.hyperic.hq.autoinventory.CompositeRuntimeResourceReport; import org.hyperic.hq.autoinventory.RuntimeScanner; import org.hyperic.hq.autoinventory.Scanner; import org.hyperic.hq.bizapp.client.AutoinventoryCallbackClient; import org.hyperic.hq.product.AutoinventoryPluginManager; import org.hyperic.hq.product.PlatformResource; import org.hyperic.hq.product.PluginException; import org.hyperic.hq.product.PluginNotFoundException; import org.hyperic.hq.product.ProductPlugin; import org.hyperic.hq.product.RuntimeDiscoverer; import org.hyperic.hq.product.RuntimeResourceReport; import org.hyperic.hq.product.ServerDetector; import org.hyperic.util.PluginLoader; import org.hyperic.util.config.ConfigResponse; import org.hyperic.util.timer.StopWatch; class RuntimeAutodiscoverer implements RuntimeScanner, AgentDiagnosticObject { private static final long DEFAULT_SCAN_INTERVAL = 15 * 60000; private static final String STORAGE_PREFIX = "runtimeautodiscovery"; private static final String STORAGE_KEYLIST = "runtimeAD-keylist"; private static final String SERVICE_PREFIX = "service-config"; private static final String SERVICE_KEYLIST = "service-keylist"; private static Log _log = LogFactory.getLog(RuntimeAutodiscoverer.class); private AutoinventoryCommandsServer _aicmd; private AgentDaemon _agent; private AutoinventoryCallbackClient _client; private AutoinventoryPluginManager _apm; private ConfigStorage _storage, _serviceStorage; //"current" interval can be changed on demand by the //trigger methods to scan sooner than the "normal" interval. private long _currentScanInterval; private long _normalScanInterval; private long _currentDefaultScanInterval; private long _normalDefaultScanInterval; private volatile boolean _isRuntimeScanning = false; private volatile Map<Key, ConfigResponse> _insertsDuringScan = new HashMap<Key, ConfigResponse>(); private final AtomicReference<CompositeRuntimeResourceReport> _lastReport = new AtomicReference<CompositeRuntimeResourceReport>(); private final AtomicBoolean _writeLastReportDiag = new AtomicBoolean(true); public RuntimeAutodiscoverer (AutoinventoryCommandsServer aicmd, AgentStorageProvider storageProvider, AgentDaemon agent, AutoinventoryCallbackClient client) { AgentDiagnostics.getInstance().addDiagnostic(this); _aicmd = aicmd; _storage = new ConfigStorage(storageProvider, STORAGE_KEYLIST, STORAGE_PREFIX); _serviceStorage = new ConfigStorage(storageProvider, SERVICE_KEYLIST, SERVICE_PREFIX); _agent = agent; _client = client; try { _apm = (AutoinventoryPluginManager) agent.getPluginManager(ProductPlugin.TYPE_AUTOINVENTORY); } catch (AgentRunningException are) { throw new IllegalStateException("Agent not running? " + are); } catch (PluginException gpme) { throw new IllegalStateException("Error getting plugin managers: " + gpme); } _currentScanInterval = _normalScanInterval = loadScanInterval("runtimeScan"); triggerScan(); _currentDefaultScanInterval = _normalDefaultScanInterval = loadScanInterval("defaultScan"); triggerDefaultScan(); } public void updateConfig(AgentRemoteValue args) throws AgentRemoteException { ConfigStorage configStorage; int type = Integer.parseInt(args.getValue(ConfigStorage.PROP_TYPE)); if (type == AppdefEntityConstants.APPDEF_TYPE_SERVICE) { configStorage = _serviceStorage; } else { configStorage = _storage; } ConfigStorage.Key key = configStorage.getKey(args); boolean isEnable = (args.getValue("disable.rtad") == null); try { _lastReport.set(null); //clear cache if (isEnable) { ConfigResponse config = configStorage.put(key, args); if (_isRuntimeScanning) { _log.debug("Scan running while storing config for: " + key); synchronized (_insertsDuringScan) { _insertsDuringScan.put(key, config); } } else { _log.debug("Triggering scan after storing config for: " + key); triggerScan(); } } else { configStorage.remove(key); } } catch (AgentStorageException e) { String method = isEnable ? "store" : "remove"; String msg = "Failed to " + method + " config for " + key + ": " + e; throw new AgentRemoteException(msg, e); } } /** @see org.hyperic.hq.autoinventory.RuntimeScanner#getScanInterval */ public long getScanInterval() { return _currentScanInterval; } /** @see org.hyperic.hq.autoinventory.RuntimeScanner#getDefaultScanInterval */ public long getDefaultScanInterval() { return _currentDefaultScanInterval; } public void triggerScan() { _currentScanInterval = 5000; } public void triggerDefaultScan() { _currentDefaultScanInterval = 5000; } /** @see org.hyperic.hq.autoinventory.RuntimeScanner#scheduleDefaultScan */ public void scheduleDefaultScan () { _aicmd.scheduleDefaultScan(); // Reset scan interval to the default _currentDefaultScanInterval = _normalDefaultScanInterval; } /** @see org.hyperic.hq.autoinventory.RuntimeScanner#doRuntimeScan */ public void doRuntimeScan() throws AutoinventoryException { //This Map is a copy, we can do with it as we please. Map<Key, ConfigResponse> configs = _storage.load(); _isRuntimeScanning = (configs.size() > 0); while (_isRuntimeScanning) { doRuntimeScan_internal(configs); // If any configs were inserted while we were scanning // go and scan those now. synchronized (_insertsDuringScan) { int size = _insertsDuringScan.size(); if (size == 0) { _isRuntimeScanning = false; break; } else { if (_log.isDebugEnabled()) { _log.debug("Processing " + size + " configs inserted while scan was running"); } // reset flag, scan again configs.clear(); configs.putAll(_insertsDuringScan); _insertsDuringScan.clear(); } } } } @SuppressWarnings("deprecation") private void doRuntimeScan_internal(Map<ConfigStorage.Key, ConfigResponse> configs) throws AutoinventoryException { Map<ConfigStorage.Key, ConfigResponse> serviceConfigs = _serviceStorage.load(); //drop service configs into the plugin manager so they can //be used by plugins to discover cprops for services for (Entry<ConfigStorage.Key, ConfigResponse> entry : serviceConfigs.entrySet()) { ConfigStorage.Key key = entry.getKey(); ConfigResponse config = entry.getValue(); String type = key.getTypeName(); _apm.addServiceConfig(type, config); } CompositeRuntimeResourceReport compositeReport = new CompositeRuntimeResourceReport(); for (Entry<ConfigStorage.Key, ConfigResponse> entry : configs.entrySet()) { ConfigStorage.Key key = entry.getKey(); ConfigResponse config = entry.getValue(); String type = key.getTypeName(); ServerDetector detector; RuntimeDiscoverer discoverer; try { detector = (ServerDetector)_apm.getPlugin(type); } catch (PluginNotFoundException e) { //plugins are not required to support AI if (_log.isDebugEnabled()) { _log.debug("Plugin does not support server detection: " + type, e); } continue; } if (!detector.isRuntimeDiscoverySupported()) { if (_log.isDebugEnabled()) { _log.debug("Plugin does not support runtime discovery: " + type); } continue; } PluginLoader.setClassLoader(detector); try { discoverer = detector.getRuntimeDiscoverer(); _log.info("Running runtime autodiscovery for " + type); PlatformResource platform = Scanner.detectPlatform(_apm, config); StopWatch timer = new StopWatch(); RuntimeResourceReport report = discoverer.discoverResources(key.getId(), platform, config); _log.info(key.getTypeName() + " discovery took " + timer); compositeReport.addServerReport(report); } catch (Exception e) { _log.error("Unexpected error running autodiscoverer for plugin: " + type + ": " + e, e); continue; } catch (NoClassDefFoundError e) { _log.error("Unable to run autodiscoverer for plugin: " + type + " (consult product setup help): " + e, e); _log.debug("Current ClassLoader=" + PluginLoader.getClassLoader()); continue; } finally { PluginLoader.resetClassLoader(detector); } } compositeReport = loadReportFilter().filterReport(compositeReport); if (compositeReport.isSameReport(_lastReport.get())) { _log.debug("No changes detected, not sending runtime report"); _writeLastReportDiag.set(false); } else { final String errMsg = "Error sending runtime report to server: "; _lastReport.set(compositeReport); _writeLastReportDiag.set(true); try { if (_log.isDebugEnabled()) { _log.debug("Sending RuntimeReport: " + compositeReport.simpleSummary()); } _client.aiSendRuntimeReport(compositeReport); } catch (Exception e) { _log.error(errMsg + e, e); } } // Reset scan interval to the default _currentScanInterval = _normalScanInterval; } private long loadScanInterval(String type) { // get scan intervals from agent.properties. Properties bootProps = _agent.getBootConfig().getBootProperties(); String prop = "autoinventory." + type + ".interval.millis"; String value = bootProps.getProperty(prop); long interval; if (value == null) { interval = DEFAULT_SCAN_INTERVAL; } else { try { interval = Long.parseLong(value); } catch (NumberFormatException e) { String msg = prop + " value not a number '" + value + "'"; throw new IllegalArgumentException(msg); } // -1 means never scan unless told to. // while Long.MAX_VALUE is not infinite, would take years before // a scan would run. if (interval == -1) { interval = Long.MAX_VALUE; } } return interval; } private RuntimeReportFilter loadReportFilter() { RuntimeReportFilter defaultFilter = new RuntimeReportFilter() { public CompositeRuntimeResourceReport filterReport(CompositeRuntimeResourceReport r) { return r; } }; String val = _agent.getBootConfig().getBootProperties().getProperty("autoinventory.reportFilter"); if (val == null) { return defaultFilter; } try { Class<?> c = Class.forName(val); return (RuntimeReportFilter)c.newInstance(); } catch(Throwable t) { _log.error("Unable to create autoinventory.reportFilter [" + val + "], using default", t); return defaultFilter; } } public String getDiagStatus() { if (!_writeLastReportDiag.get() || _lastReport.get() == null) { return "Discovery report has not changed"; } _writeLastReportDiag.set(false); return _lastReport.get().fullSummary(); } public String getDiagName() { return "Runtime Report Diagnostic"; } }