/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) 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, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) 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 OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.collectd; import java.io.File; import java.util.Map; import org.opennms.core.utils.InetAddressUtils; import org.opennms.core.utils.ThreadCategory; import org.opennms.netmgt.EventConstants; import org.opennms.netmgt.collectd.Collectd.SchedulingCompletedFlag; import org.opennms.netmgt.config.DataCollectionConfigFactory; import org.opennms.netmgt.config.collector.CollectionSet; import org.opennms.netmgt.config.collector.ServiceParameters; import org.opennms.netmgt.dao.CollectorConfigDao; import org.opennms.netmgt.dao.IpInterfaceDao; import org.opennms.netmgt.eventd.EventIpcManagerFactory; import org.opennms.netmgt.model.OnmsIpInterface; import org.opennms.netmgt.model.RrdRepository; import org.opennms.netmgt.model.events.EventBuilder; import org.opennms.netmgt.scheduler.ReadyRunnable; import org.opennms.netmgt.scheduler.Scheduler; import org.opennms.netmgt.threshd.ThresholdingVisitor; import org.springframework.transaction.PlatformTransactionManager; /** * <P> * The CollectableService class ... * </P> * * @author <A HREF="mailto:mike@opennms.org">Mike Davidson </A> * @author <A HREF="http://www.opennms.org/">OpenNMS </A> * */ final class CollectableService implements ReadyRunnable { /** * Interface's parent node identifier */ private volatile int m_nodeId; /** * Last known/current status */ private volatile int m_status; /** * The last time the collector was scheduled for collection. */ private volatile long m_lastScheduledCollectionTime; /** * The scheduler for collectd */ private final Scheduler m_scheduler; /** * Service updates */ private final CollectorUpdates m_updates; /** * The thresholdvisitor for this collectable service; called */ private final ThresholdingVisitor m_thresholdVisitor; /** * */ private static final boolean ABORT_COLLECTION = true; private final CollectionSpecification m_spec; private final SchedulingCompletedFlag m_schedulingCompletedFlag; private volatile CollectionAgent m_agent; private final PlatformTransactionManager m_transMgr; private final IpInterfaceDao m_ifaceDao; private final ServiceParameters m_params; private final RrdRepository m_repository; /** * Constructs a new instance of a CollectableService object. * * @param iface The interface on which to collect data * @param spec * The package containing parms for this collectable service. * @param ifaceDao a {@link org.opennms.netmgt.dao.IpInterfaceDao} object. * @param scheduler a {@link org.opennms.netmgt.scheduler.Scheduler} object. * @param schedulingCompletedFlag a {@link org.opennms.netmgt.collectd.Collectd.SchedulingCompletedFlag} object. * @param transMgr a {@link org.springframework.transaction.PlatformTransactionManager} object. */ protected CollectableService(OnmsIpInterface iface, IpInterfaceDao ifaceDao, CollectionSpecification spec, Scheduler scheduler, SchedulingCompletedFlag schedulingCompletedFlag, PlatformTransactionManager transMgr) throws CollectionInitializationException { m_agent = DefaultCollectionAgent.create(iface.getId(), ifaceDao, transMgr); m_spec = spec; m_scheduler = scheduler; m_schedulingCompletedFlag = schedulingCompletedFlag; m_ifaceDao = ifaceDao; m_transMgr = transMgr; m_nodeId = iface.getNode().getId().intValue(); m_status = ServiceCollector.COLLECTION_SUCCEEDED; m_updates = new CollectorUpdates(); m_lastScheduledCollectionTime = 0L; m_spec.initialize(m_agent); Map<String, Object> roProps=m_spec.getReadOnlyPropertyMap(); m_params=new ServiceParameters(roProps); m_repository=m_spec.getRrdRepository(m_params.getCollectionName()); m_thresholdVisitor = ThresholdingVisitor.create(m_nodeId, getHostAddress(), m_spec.getServiceName(), m_repository, roProps); } /** * <p>getAddress</p> * * @return a {@link java.lang.Object} object. */ public Object getAddress() { return m_agent.getAddress(); } /** * <p>getSpecification</p> * * @return a {@link org.opennms.netmgt.collectd.CollectionSpecification} object. */ public CollectionSpecification getSpecification() { return m_spec; } /** * Returns node identifier * * @return a int. */ public int getNodeId() { return m_nodeId; } /** * Returns the service name * * @return a {@link java.lang.String} object. */ public String getServiceName() { return m_spec.getServiceName(); } /** * Returns the package name * * @return a {@link java.lang.String} object. */ public String getPackageName() { return m_spec.getPackageName(); } /** * Returns updates object * * @return a {@link org.opennms.netmgt.collectd.CollectorUpdates} object. */ public CollectorUpdates getCollectorUpdates() { return m_updates; } /** * Uses the existing package name to try and re-obtain the package from the collectd config factory. * Should be called when the collect config has been reloaded. * * @param collectorConfigDao a {@link org.opennms.netmgt.dao.CollectorConfigDao} object. */ public void refreshPackage(CollectorConfigDao collectorConfigDao) { m_spec.refresh(collectorConfigDao); if (m_thresholdVisitor != null) m_thresholdVisitor.reloadScheduledOutages(); } /** {@inheritDoc} */ @Override public String toString() { return "CollectableService for service "+m_nodeId+':'+getAddress()+':'+getServiceName(); } /** * This method is used to evaluate the status of this interface and service * pair. If it is time to run the collection again then a value of true is * returned. If the interface is not ready then a value of false is * returned. * * @return a boolean. */ public boolean isReady() { boolean ready = false; if (!isSchedulingComplete()) return false; if (m_spec.getInterval() < 1) { ready = true; } else { ready = ((m_spec.getInterval() - (System.currentTimeMillis() - m_lastScheduledCollectionTime)) < 1); } return ready; } private boolean isSchedulingComplete() { return m_schedulingCompletedFlag.isSchedulingCompleted(); } /** * Generate event and send it to eventd via the event proxy. * * uei Universal event identifier of event to generate. */ private void sendEvent(String uei, String reason) { EventBuilder builder = new EventBuilder(uei, "OpenNMS.Collectd"); builder.setNodeid(m_nodeId); builder.setInterface(m_agent.getInetAddress()); builder.setService(m_spec.getServiceName()); builder.setHost(InetAddressUtils.getLocalHostName()); if (reason != null) { builder.addParam("reason", reason); } // Send the event try { EventIpcManagerFactory.getIpcManager().sendNow(builder.getEvent()); if (log().isDebugEnabled()) { log().debug("sendEvent: Sent event " + uei + " for " + m_nodeId + "/" + getHostAddress() + "/" + getServiceName()); } } catch (Throwable e) { log().error("Failed to send the event " + uei + " for interface " + getHostAddress() + ": " + e, e); } } private String getHostAddress() { return m_agent.getHostAddress(); } /** * This is the main method of the class. An instance is normally enqueued on * the scheduler which checks its <code>isReady</code> method to determine * execution. If the instance is ready for execution then it is started with * it's own thread context to execute the query. The last step in the method * before it exits is to reschedule the interface. */ public void run() { // Process any outstanding updates. if (processUpdates() == ABORT_COLLECTION) { log().debug("run: Aborting because processUpdates returned ABORT_COLLECTION (probably marked for deletion) for "+this); return; } // Update last scheduled poll time m_lastScheduledCollectionTime = System.currentTimeMillis(); /* * Check scheduled outages to see if any apply indicating * that the collection should be skipped. */ if (!m_spec.scheduledOutage(m_agent)) { try { doCollection(); updateStatus(ServiceCollector.COLLECTION_SUCCEEDED, null); } catch (CollectionException e) { if (e instanceof CollectionWarning) { log().warn(e.getMessage(), e); } else { log().error(e.getMessage(), e); } updateStatus(ServiceCollector.COLLECTION_FAILED, e); } catch (Throwable e) { log().error(e.getMessage(), e); updateStatus(ServiceCollector.COLLECTION_FAILED, new CollectionException("Collection failed unexpectedly: " + e.getClass().getSimpleName() + ": " + e.getMessage(), e)); } } // Reschedule the service m_scheduler.schedule(m_spec.getInterval(), getReadyRunnable()); } private void updateStatus(int status, CollectionException e) { // Any change in status? if (status != m_status) { // Generate data collection transition events if (log().isDebugEnabled()) { log().debug("run: change in collection status, generating event."); } String reason = null; if (e != null) { reason = e.getMessage(); } // Send the appropriate event switch (status) { case ServiceCollector.COLLECTION_SUCCEEDED: sendEvent(EventConstants.DATA_COLLECTION_SUCCEEDED_EVENT_UEI, null); break; case ServiceCollector.COLLECTION_FAILED: sendEvent(EventConstants.DATA_COLLECTION_FAILED_EVENT_UEI, reason); break; default: break; } } // Set the new status m_status = status; } private BasePersister createPersister(ServiceParameters params, RrdRepository repository) { if (Boolean.getBoolean("org.opennms.rrd.storeByGroup")) { return new GroupPersister(params, repository); } else { return new OneToOnePersister(params, repository); } } /** * Perform data collection. */ private void doCollection() throws CollectionException { log().info("run: starting new collection for " + getHostAddress() + "/" + m_spec.getServiceName() + "/" + m_spec.getPackageName()); CollectionSet result = null; try { result = m_spec.collect(m_agent); if (result != null) { Collectd.instrumentation().beginPersistingServiceData(m_nodeId, getHostAddress(), m_spec.getServiceName()); try { BasePersister persister = createPersister(m_params, m_repository); persister.setIgnorePersist(result.ignorePersist()); result.visit(persister); } finally { Collectd.instrumentation().endPersistingServiceData(m_nodeId, getHostAddress(), m_spec.getServiceName()); } /* * Do the thresholding; this could be made more generic (listeners being passed the collectionset), but frankly, why bother? * The first person who actually needs to configure that sort of thing on the fly can code it up. */ if (m_thresholdVisitor != null) { if (m_thresholdVisitor.isNodeInOutage()) { log().info("run: the threshold processing will be skipped because the node " + m_nodeId + " is on a scheduled outage."); } else if (m_thresholdVisitor.hasThresholds()) { result.visit(m_thresholdVisitor); } } if (result.getStatus() == ServiceCollector.COLLECTION_SUCCEEDED) { return; } else { throw new CollectionFailed(result.getStatus()); } } } catch (CollectionException e) { log().warn("run: failed collection for " + getHostAddress() + "/" + m_spec.getServiceName() + "/" + m_spec.getPackageName()); throw e; } catch (Throwable t) { log().warn("run: failed collection for " + getHostAddress() + "/" + m_spec.getServiceName() + "/" + m_spec.getPackageName()); throw new CollectionException("An undeclared throwable was caught during data collection for interface " + getHostAddress() +"/"+ m_spec.getServiceName(), t); } log().info("run: finished collection for " + getHostAddress() + "/" + m_spec.getServiceName() + "/" + m_spec.getPackageName()); } /** * Process any outstanding updates. * * @return true if update indicates that collection should be aborted (for * example due to deletion flag being set), false otherwise. */ private boolean processUpdates() { // All update processing takes place within synchronized block // to ensure that no updates are missed. // synchronized (this) { if (!m_updates.hasUpdates()) return !ABORT_COLLECTION; // Update: deletion flag // if (m_updates.isDeletionFlagSet()) { // Deletion flag is set, simply return without polling // or rescheduling this collector. // if (log().isDebugEnabled()) log().debug("Collector for " + getHostAddress() + " is marked for deletion...skipping collection, will not reschedule."); return ABORT_COLLECTION; } OnmsIpInterface newIface = m_updates.isReinitializationNeeded(); // Update: reinitialization flag // if (newIface != null) { // Reinitialization flag is set, call initialize() to // reinit the collector for this interface // if (log().isDebugEnabled()) log().debug("ReinitializationFlag set for " + getHostAddress()); try { reinitialize(newIface); if (log().isDebugEnabled()) log().debug("Completed reinitializing "+this.getServiceName()+" collector for " + getHostAddress() +"/"+ m_spec.getServiceName()); } catch (CollectionInitializationException rE) { log().warn("Unable to initialize " + getHostAddress() + " for " + m_spec.getServiceName() + " collection, reason: " + rE.getMessage()); } catch (Throwable t) { log().error("Uncaught exception, failed to intialize interface " + getHostAddress() + " for " + m_spec.getServiceName() + " data collection", t); } } // Update: reparenting flag // if (m_updates.isReparentingFlagSet()) { if (log().isDebugEnabled()) log().debug("ReparentingFlag set for " + getHostAddress()); // The interface has been reparented under a different node // (with // a different nodeId). // // If the new directory doesn't already exist simply need to // rename the old // directory: // /opt/OpenNMS/share/rrd/snmp/<oldNodeId> // to the new directory: // /opt/OpenNMS/share/rrd/snmp/<newNodeId> // // Otherwise must iterate over each of the files/dirs in the // <oldNodeId> // directory and move/rename them under the <newNodeId> // directory. // Get path to RRD repository // String rrdPath = DataCollectionConfigFactory.getInstance().getRrdPath(); // Does the <newNodeId> directory already exist? File newNodeDir = new File(rrdPath + File.separator + m_updates.getReparentNewNodeId()); if (!newNodeDir.isDirectory()) { // New directory does not exist yet so simply rename the old // directory to // the new directory. // // <oldNodeId> directory File oldNodeDir = new File(rrdPath + File.separator + m_updates.getReparentOldNodeId()); try { // Rename <oldNodeId> dir to <newNodeId> dir. if (log().isDebugEnabled()) log().debug("Attempting to rename " + oldNodeDir + " to " + newNodeDir); oldNodeDir.renameTo(newNodeDir); if (log().isDebugEnabled()) log().debug("Rename successful!!"); } catch (SecurityException se) { log().error("Insufficient authority to rename RRD directory.", se); } catch (Throwable t) { log().error("Unexpected exception while attempting to rename RRD directory.", t); } } else { // New node directory already exists so we must move/rename // each of the // old node directory contents under the new node directory. // // Get list of files to be renamed/moved File oldNodeDir = new File(rrdPath + File.separator + m_updates.getReparentOldNodeId()); String[] filesToMove = oldNodeDir.list(); if (filesToMove != null) { // Iterate over the file list and rename/move each one for (int i = 0; i < filesToMove.length; i++) { File srcFile = new File(oldNodeDir.toString() + File.separator + filesToMove[i]); File destFile = new File(newNodeDir.toString() + File.separator + filesToMove[i]); try { if (log().isDebugEnabled()) log().debug("Attempting to move " + srcFile + " to " + destFile); srcFile.renameTo(destFile); } catch (SecurityException se) { log().error("Insufficient authority to move RRD files.", se); break; } catch (Throwable t) { log().warn("Unexpected exception while attempting to move " + srcFile + " to " + destFile, t); } } } } // Convert new nodeId to integer value int newNodeId = -1; try { newNodeId = Integer.parseInt(m_updates.getReparentNewNodeId()); } catch (NumberFormatException nfE) { log().warn("Unable to convert new nodeId value to an int while processing reparenting update: " + m_updates.getReparentNewNodeId()); } // Set this collector's nodeId to the value of the interface's // new parent nodeid. m_nodeId = newNodeId; // We must now reinitialize the collector for this interface, // in order to update the NodeInfo object to reflect changes // to the interface's parent node among other things. // try { if (log().isDebugEnabled()) log().debug("Reinitializing collector for " + getHostAddress() +"/"+ m_spec.getServiceName()); reinitialize(m_updates.getUpdatedInterface()); if (log().isDebugEnabled()) log().debug("Completed reinitializing collector for " + getHostAddress() +"/"+ m_spec.getServiceName()); } catch (CollectionInitializationException rE) { log().warn("Unable to initialize " + getHostAddress() + " for " + m_spec.getServiceName() + " collection, reason: " + rE.getMessage()); } catch (Throwable t) { log().error("Uncaught exception, failed to initialize interface " + getHostAddress() + " for " + m_spec.getServiceName() + " data collection", t); } } // Updates have been applied. Reset CollectorUpdates object. // . m_updates.reset(); } // end synchronized return !ABORT_COLLECTION; } ThreadCategory log() { return ThreadCategory.getInstance(getClass()); } private void reinitialize(OnmsIpInterface newIface) throws CollectionInitializationException { m_spec.release(m_agent); m_agent = DefaultCollectionAgent.create(newIface.getId(), m_ifaceDao, m_transMgr); m_spec.initialize(m_agent); } /** * <p>reinitializeThresholding</p> */ public void reinitializeThresholding() { if(m_thresholdVisitor!=null) { log().debug("reinitializeThresholding on "+this); m_thresholdVisitor.reload(); } } /** * <p>getReadyRunnable</p> * * @return a {@link org.opennms.netmgt.scheduler.ReadyRunnable} object. */ public ReadyRunnable getReadyRunnable() { return this; } }