/*******************************************************************************
* 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.vulnscand;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import org.opennms.core.fiber.PausableFiber;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.core.utils.ThreadCategory;
/**
* This class implements a simple scheduler to ensure that Vulnscand rescans
* occurs at the expected intervals.
*
* @author <a href="mailto:mike@opennms.org">Mike Davidson </a>
* @author <a href="http://www.opennms.org/">OpenNMS </a>
*
*/
final class Scheduler implements Runnable, PausableFiber {
/**
* The prefix for the fiber name.
*/
private static final String FIBER_NAME = "Vulnscand Scheduler";
/**
* The status for this fiber.
*/
private int m_status;
/**
* The worker thread that executes this instance.
*/
private Thread m_worker;
/**
* List of NessusScanConfiguration objects representing the IP addresses
* that will be scheduled.
*/
private LinkedHashMap<Object, ScheduleTrigger<Runnable>> m_triggers;
/**
* The configured initial sleep (in milliseconds) prior to scheduling
* rescans
*/
private long m_initialSleep;
/**
* The rescan queue where new NessusScan objects are enqueued for execution.
*/
private ExecutorService m_scheduledScan;
/**
* Constructs a new instance of the scheduler.
* @param vulnscand TODO
*
*/
Scheduler(ExecutorService rescanQ) throws SQLException {
m_scheduledScan = rescanQ;
m_status = START_PENDING;
m_worker = null;
m_triggers = new LinkedHashMap<Object, ScheduleTrigger<Runnable>>();
}
void schedule(Object key, ScheduleTrigger<Runnable> trigger) {
synchronized (getSchedulerLock()) {
m_triggers.put(key, trigger);
}
}
private void unschedule(Object key) {
synchronized (getSchedulerLock()) {
ScheduleTrigger<Runnable> addressInfo = m_triggers.remove(key);
log().debug("unscheduleAddress: removing node " + addressInfo + " from the scheduler.");
}
}
ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
/**
* Removes the specified node from the known node list.
*
* @param address
* Address of interface to be removed.
*/
void unscheduleAddress(InetAddress address) {
unschedule(address);
}
/**
* <p>toInetAddress</p>
*
* @param address a long.
* @return a {@link java.net.InetAddress} object.
* @throws java.net.UnknownHostException if any.
*/
public static InetAddress toInetAddress(long address) throws UnknownHostException {
StringBuffer buf = new StringBuffer();
buf.append((int) ((address >>> 24) & 0xff)).append('.');
buf.append((int) ((address >>> 16) & 0xff)).append('.');
buf.append((int) ((address >>> 8) & 0xff)).append('.');
buf.append((int) (address & 0xff));
return InetAddressUtils.addr(buf.toString());
}
/**
* Starts the fiber.
*
* @throws java.lang.IllegalStateException
* Thrown if the fiber is already running.
*/
public synchronized void start() {
if (m_worker != null)
throw new IllegalStateException("The fiber has already run or is running");
m_worker = new Thread(this, getName());
m_worker.start();
m_status = STARTING;
log().debug("Scheduler.start: scheduler started");
}
/**
* Stops the fiber. If the fiber has never been run then an exception is
* generated.
*
* @throws java.lang.IllegalStateException
* Throws if the fiber has never been started.
*/
public synchronized void stop() {
if (m_worker == null)
throw new IllegalStateException("The fiber has never been started");
m_status = STOP_PENDING;
m_worker.interrupt();
log().debug("Scheduler.stop: scheduler stopped");
}
/**
* Pauses the scheduler if it is current running. If the fiber has not been
* run or has already stopped then an exception is generated.
*
* @throws java.lang.IllegalStateException
* Throws if the operation could not be completed due to the
* fiber's state.
*/
public synchronized void pause() {
if (m_worker == null)
throw new IllegalStateException("The fiber has never been started");
if (m_status == STOPPED || m_status == STOP_PENDING)
throw new IllegalStateException("The fiber is not running or a stop is pending");
if (m_status == PAUSED)
return;
m_status = PAUSE_PENDING;
notifyAll();
}
/**
* Resumes the scheduler if it has been paused. If the fiber has not been
* run or has already stopped then an exception is generated.
*
* @throws java.lang.IllegalStateException
* Throws if the operation could not be completed due to the
* fiber's state.
*/
public synchronized void resume() {
if (m_worker == null)
throw new IllegalStateException("The fiber has never been started");
if (m_status == STOPPED || m_status == STOP_PENDING)
throw new IllegalStateException("The fiber is not running or a stop is pending");
if (m_status == RUNNING)
return;
m_status = RESUME_PENDING;
notifyAll();
}
/**
* Returns the current of this fiber.
*
* @return The current status.
*/
public synchronized int getStatus() {
if (m_worker != null && m_worker.isAlive() == false)
m_status = STOPPED;
return m_status;
}
/**
* Returns the name of this fiber.
*
* @return a {@link java.lang.String} object.
*/
public String getName() {
return FIBER_NAME;
}
/**
* The main method of the scheduler. This method is responsible for checking
* the runnable queues for ready objects and then enqueuing them into the
* thread pool for execution.
*/
public void run() {
synchronized (this) {
m_status = RUNNING;
}
log().debug("Scheduler.run: scheduler running");
// Loop until a fatal exception occurs or until
// the thread is interrupted.
//
boolean firstPass = true;
while (true) {
// Status check
//
synchronized (this) {
if (m_status != RUNNING && m_status != PAUSED && m_status != PAUSE_PENDING && m_status != RESUME_PENDING) {
log().debug("Scheduler.run: status = " + m_status + ", time to exit");
break;
}
}
// If this is the first pass we want to pause momentarily
// This allows the rest of the background processes to come
// up and stabilize before we start generating events from rescans.
//
if (firstPass) {
firstPass = false;
synchronized (this) {
try {
log().debug("Scheduler.run: initial sleep configured for " + getInitialSleep() + "ms...sleeping...");
wait(getInitialSleep());
} catch (InterruptedException ex) {
log().debug("Scheduler.run: interrupted exception during initial sleep...exiting.");
break; // exit for loop
}
}
}
// iterate over the known node list, add any
// nodes ready for rescan to the rescan queue
// for processing.
//
int added = 0;
synchronized (getSchedulerLock()) {
log().debug("Scheduler.run: iterating over known nodes list to schedule...");
for (ScheduleTrigger<Runnable> addressInfo : getTriggerList()) {
log().debug("Scheduler.run: working on " + addressInfo );
// Don't schedule if already scheduled
if (addressInfo.isScheduled())
continue;
// Don't schedule if its not time for rescan yet
if (!addressInfo.isTimeForRescan())
continue;
// Must be time for a rescan!
//
try {
addressInfo.setScheduled(true); // Mark node as
// scheduled
// Create a new NessusScan object
// and add it to the rescan queue for execution
//
log().debug("Scheduler.run: adding node " + addressInfo + " to the rescan queue.");
m_scheduledScan.execute(addressInfo.getJob());
added++;
} catch (RejectedExecutionException ex) {
log().info("Scheduler.schedule: failed to add new node to rescan queue", ex);
throw new UndeclaredThrowableException(ex);
}
}
}
// Wait for 60 seconds if there were no nodes
// added to the rescan queue during this loop,
// otherwise just start over.
//
synchronized (this) {
if (added == 0) {
try {
wait(60000);
} catch (InterruptedException ex) {
break; // exit for loop
}
}
}
} // end while(true)
log().debug("Scheduler.run: scheduler exiting, state = STOPPED");
synchronized (this) {
m_status = STOPPED;
}
} // end run
private Object getSchedulerLock() {
return m_triggers;
}
private Collection<ScheduleTrigger<Runnable>> getTriggerList() {
return m_triggers.values();
}
void setInitialSleep(long initialSleep) {
m_initialSleep = initialSleep;
}
long getInitialSleep() {
return m_initialSleep;
}
}