/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.server; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.StringTokenizer; import com.sleepycat.je.DatabaseException; import com.slamd.admin.AdminServlet; import com.slamd.db.SLAMDDB; import com.slamd.job.Job; import com.slamd.job.JobClass; import com.slamd.job.OptimizationAlgorithm; import com.slamd.job.SingleStatisticOptimizationAlgorithm; import com.slamd.job.UnknownJobClass; import com.slamd.common.Constants; import com.slamd.common.DynamicConstants; import com.slamd.common.JobClassLoader; import com.slamd.common.RefCountMutex; import com.slamd.common.SLAMDException; import com.slamd.parameter.BooleanParameter; import com.slamd.parameter.MultiLineTextParameter; import com.slamd.parameter.MultiValuedParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; import com.slamd.stat.StatTracker; /** * This class provides the entry point to the SLAMD server. It performs all the * necessary bootstrapping, starts the scheduler, starts the client listener, * and handles all necessary coordination between all other components of SLAMD. * * * @author Neil A. Wilson */ public class SLAMDServer implements ConfigSubscriber { /** * The version of the SLAMD server software being used. */ public static final String SLAMD_SERVER_VERSION = DynamicConstants.SLAMD_VERSION; /** * The name used to register the logger as a subscriber to the configuration * handler. */ public static final String CONFIG_SUBSCRIBER_NAME = "SLAMD Server"; // The administrative servlet used to control the SLAMD server. private AdminServlet adminServlet; // Indicates whether the server should operate in read-only mode. private boolean readOnlyMode; // Variables that hold information about the configuration database. private SLAMDDB configDB; private String configDBDirectory; // Variables used for SSL communication. private String sslKeyStore; private String sslKeyPassword; private String sslTrustStore; private String sslTrustPassword; // The various client listeners. private ClientListener clientListener; private ClientManagerListener clientManagerListener; private ResourceMonitorClientListener monitorClientListener; private StatListener statListener; // The SLAMD scheduler. private Scheduler scheduler; // The SLAMD mailer. private SMTPMailer mailer; // The SLAMD logger. private SimpleDateFormat dateFormatter; private boolean loggerInitialized; private boolean overrideConfigLogLevel; private int logLevel; private Logger logger; // The real-time stat handler. protected RealTimeStatHandler statHandler; // The job classes that have been defined for use in the configuration // directory. private RefCountMutex jobClassesMutex; private JobClass[] jobClasses; // The optimization algorithms that have been defined for use in the // configuration directory. private OptimizationAlgorithm[] optimizationAlgorithms; // A timestamp representing the SLAMD server startup time. private Date startupTime; // A flag that indicates whether we will use the standard or the custom class // loader. private boolean useCustomClassLoader; /** * Creates a new instance of the SLAMD server. All of the configuration will * be retrieved from an LDAP directory server using the information provided * as parameters to this function. * * @param adminServlet The admin servlet used to control the SLAMD * server. * @param readOnlyMode Indicates whether the server should operate in * read-only mode. If so, then it should not * create any listeners. * @param configDBDirectory The path to the configuration database. * @param sslKeyStore The JSSE key store file to use for SSL * connections (typically "~/.keystore"). * @param sslKeyPassword The password that should be used to access the * contents of the JSSE key store. * @param sslTrustStore The JSSE trust store file to use for SSL * connections (typically * "JAVA_HOME/jre/lib/security/cacerts") * @param sslTrustPassword The password that should be used to access the * contents of the JSSE trust store. * * @throws SLAMDServerException If an unrecoverable error occurs that * prevents the server from operating. */ public SLAMDServer(AdminServlet adminServlet, boolean readOnlyMode, String configDBDirectory, String sslKeyStore, String sslKeyPassword, String sslTrustStore, String sslTrustPassword) throws SLAMDServerException { this(adminServlet, readOnlyMode, configDBDirectory, sslKeyStore, sslKeyPassword, sslTrustStore, sslTrustPassword, Constants.LOG_LEVEL_USECONFIG); } /** * Creates a new instance of the SLAMD server. All of the configuration will * be retrieved from an LDAP directory server using the information provided * as parameters to this function. * * @param adminServlet The admin servlet used to control the SLAMD * server. * @param readOnlyMode Indicates whether the server should operate in * read-only mode. If so, then it should not * create any listeners. * @param configDBDirectory The path to the configuration database. * @param sslKeyStore The JSSE key store file to use for SSL * connections (typically "~/.keystore"). * @param sslKeyPassword The password that should be used to access the * contents of the JSSE key store. * @param sslTrustStore The JSSE trust store file to use for SSL * connections (typically * "JAVA_HOME/jre/lib/security/cacerts") * @param sslTrustPassword The password that should be used to access the * contents of the JSSE trust store. * @param logLevel The log level that should be used by the server. * If it is a valid log level, then it will * override whatever log level may be specified in * the configuration. * * @throws SLAMDServerException If an unrecoverable error occurs that * prevents the server from operating. */ public SLAMDServer(AdminServlet adminServlet, boolean readOnlyMode, String configDBDirectory, String sslKeyStore, String sslKeyPassword, String sslTrustStore, String sslTrustPassword, int logLevel) throws SLAMDServerException { // Set the admin servlet used for the SLAMD server. this.adminServlet = adminServlet; // Determine whether the server should operate in read-only mode. this.readOnlyMode = readOnlyMode; // Initialize variables related to accessing the configuration database. this.configDBDirectory = configDBDirectory; // Initialize variables related to SSL processing. this.sslKeyStore = sslKeyStore; this.sslKeyPassword = sslKeyPassword; this.sslTrustStore = sslTrustStore; this.sslTrustPassword = sslTrustPassword; // Initialize variables related to logging loggerInitialized = false; dateFormatter = new SimpleDateFormat("[MM/dd/yyyy:HH:mm:ss]"); if (logLevel == Constants.LOG_LEVEL_USECONFIG) { overrideConfigLogLevel = false; this.logLevel = Constants.LOG_LEVEL_DEFAULT; } else { overrideConfigLogLevel = true; this.logLevel = logLevel; logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer constructor"); logMessage(Constants.LOG_LEVEL_CONFIG, "Log level configured to be " + this.logLevel); } // Indicate that the server is starting up logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "SLAMD Starting up...."); // Create the configuration handler and load the SLAMD configuration from // the configuration directory. Also, register as a subscriber of the // configuration handler. try { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Opening the configuration database..."); configDB = new SLAMDDB(this, configDBDirectory, readOnlyMode, false); configDB.registerAsSubscriber(this); refreshSubscriberConfiguration(); } catch (DatabaseException de) { String message = "Unrecoverable error -- can't open the SLAMD database " + "-- " + de; logMessage(Constants.LOG_LEVEL_ANY, message); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(de)); throw new SLAMDServerException(message); } // Create the mailer for the SLAMD server. mailer = new SMTPMailer(this); // Retrieve a list of the job classes that have been defined in the // configuration directory try { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Obtaining the set of job classes...."); jobClassesMutex = new RefCountMutex(); jobClasses = configDB.getJobClasses(); sortJobClasses(); } catch (DatabaseException de) { String message = "Unrecoverable error -- can't get the job class " + "definitions: " + de; logMessage(Constants.LOG_LEVEL_ANY, message); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(de)); throw new SLAMDServerException(message, de); } // Retrieve the list of optimization algorithms that have been defined in // the configuration directory. logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Obtaining the set of optimization algorithms...."); String algorithmStr = configDB.getConfigParameter(Constants.PARAM_OPTIMIZATION_ALGORITHMS); if ((algorithmStr == null) || (algorithmStr.length() == 0)) { optimizationAlgorithms = new OptimizationAlgorithm[0]; } else { ArrayList<OptimizationAlgorithm> algorithmList = new ArrayList<OptimizationAlgorithm>(); StringTokenizer tokenizer = new StringTokenizer(algorithmStr, " \t\r\n"); while (tokenizer.hasMoreTokens()) { String algorithmClassName = tokenizer.nextToken(); try { Class<?> algorithmClass = Constants.classForName(algorithmClassName); OptimizationAlgorithm algorithm = (OptimizationAlgorithm) algorithmClass.newInstance(); algorithmList.add(algorithm); } catch (Exception e) { logMessage(Constants.LOG_LEVEL_ANY, "Unable to load optimization algorithm class \"" + algorithmClassName + "\" -- " + e); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e)); } } optimizationAlgorithms = new OptimizationAlgorithm[algorithmList.size()]; algorithmList.toArray(optimizationAlgorithms); } // Initialize the logging subsystem if (! readOnlyMode) { try { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Starting the logger...."); logger = new Logger(this); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "SLAMD logger successfully configured"); } catch (SLAMDServerException sse) { logMessage(Constants.LOG_LEVEL_ANY, "Can't initialize the logger - " + sse.getMessage() + " -- file-based logging will be disabled"); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(sse)); } } // Create the client manager listener to start listening for new connections if (! readOnlyMode) { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Starting the client manager listener...."); clientManagerListener = new ClientManagerListener(this); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Client manager listener started"); } // Create the client listener to start listening for new connections if (! readOnlyMode) { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Starting the client listener...."); clientListener = new ClientListener(this); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Client listener started"); } // Create the resource monitor client listener to start listening for new // connections if (! readOnlyMode) { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Starting the resource monitor client listener...."); monitorClientListener = new ResourceMonitorClientListener(this); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Monitor client listener started"); } // Create the stat client listener to start listening for new connections if (! readOnlyMode) { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Starting the stat client listener...."); statHandler = new RealTimeStatHandler(this); statListener = new StatListener(this); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Stat listener started"); } // Create the scheduler to handle scheduling of new jobs. Load a job list // from the configuration directory to see if there are any jobs that should // be scheduled for execution. if (! readOnlyMode) { try { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Starting the scheduler...."); scheduler = new Scheduler(this); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Scheduler initialized"); } catch (SLAMDServerException sse) { logMessage(Constants.LOG_LEVEL_ANY, "Unrecoverable error -- can't initialize the scheduler - " + sse.getMessage()); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(sse)); logger.closeLogger(); try { configDB.closeDatabases(true); } catch (Exception e) {} try { configDB.closeEnvironment(); } catch (Exception e) {} throw sse; } } // Start all the threads associated with SLAMD server components. if (clientManagerListener != null) { clientManagerListener.startListening(); } if (clientListener != null) { clientListener.startListening(); } if (monitorClientListener != null) { monitorClientListener.startListening(); } if (statListener != null) { statListener.startListening(); } if (scheduler != null) { scheduler.startScheduler(); } // Set this as the current startup time startupTime = new Date(); logMessage(Constants.LOG_LEVEL_ANY, "SLAMD Server Started"); logMessage(Constants.LOG_LEVEL_TRACE, "Leaving SLAMDServer constructor"); } /** * Stops the SLAMD server and all related components. This method will not * return until the shutdown has completed. */ public void stopSLAMD() { logMessage(Constants.LOG_LEVEL_ANY, "SLAMD server is shutting down...."); // Stop the client manager listener. if ((! readOnlyMode) && (clientManagerListener != null)) { clientManagerListener.stopListening(); } // Stop the client listener to give it a chance to disconnect all // the clients and stop handling new connections. if ((! readOnlyMode) && (clientListener != null)) { clientListener.stopListening(); } // Stop the monitor client listener to give it a chance to disconnect. if ((! readOnlyMode) && (monitorClientListener != null)) { monitorClientListener.stopListening(); } // Stop the stat client listener to give it a chance to disconnect. if ((! readOnlyMode) && (statListener != null)) { statListener.stopListening(); } // Next, stop the scheduler if ((! readOnlyMode) && (scheduler != null)) { scheduler.stopScheduler(); } // Next, the configuration handler try { configDB.closeDatabases(true); } catch (Exception e) {} try { configDB.closeEnvironment(); } catch (Exception e) {} // Give the monitor listener a chance to complete its shutdown. if ((! readOnlyMode) && (monitorClientListener != null)) { monitorClientListener.waitForStop(); } // Give the listener a chance to complete its shutdown if ((! readOnlyMode) && (clientListener != null)) { clientListener.waitForStop(); } // Give the stat listener a chance to complete its shutdown if ((! readOnlyMode) && (statListener != null)) { statListener.waitForStop(); } // Give the scheduler a chance to complete its shutdown if ((! readOnlyMode) && (scheduler != null)) { scheduler.waitForStop(); } // OK, so it's not really stopped, but by the time the user sees this // message, it will be. logMessage(Constants.LOG_LEVEL_ANY, "SLAMD server stopped."); // Finally, the logger. This won't return until it has stopped. if ((! readOnlyMode) && (logger != null)) { logger.closeLogger(); } } /** * Retrieves the admin servlet used to manage the SLAMD server. * * @return The admin servlet used to manage the SLAMD server. */ public AdminServlet getAdminServlet() { return adminServlet; } /** * Retrieves the location of the job class files on the local filesystem. * * @return The path to the job class files on the local filesystem. */ public String getClassPath() { return AdminServlet.getClassPath(); } /** * Retrieves the set of job classes defined for use in the configuration * directory. * * @return The set of job classes defined for use in the configuration * directory. */ public JobClass[] getJobClasses() { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.getJobClasses()"); return jobClasses; } /** * Retrieves the set of job classes defined for use in the configuration * directory that are in the specified category name. * * @param categoryName The name of the category for which to retrieve the * job classes. * * @return The job classes in the specified category. */ public JobClass[] getJobClasses(String categoryName) { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.getJobClasses(" + categoryName + ')'); jobClassesMutex.getReadLock(); ArrayList<JobClass> jobClassList = new ArrayList<JobClass>(); for (int i=0; i < jobClasses.length; i++) { if (jobClasses[i].getJobCategoryName().equalsIgnoreCase(categoryName)) { jobClassList.add(jobClasses[i]); } } jobClassesMutex.releaseReadLock(); JobClass[] classes = new JobClass[jobClassList.size()]; jobClassList.toArray(classes); return classes; } /** * Retrieves the names of the job class categories defined to the SLAMD * server. * * @return The names of the job class categories defined to the SLAMD server. */ public String[] getJobClassCategories() { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.getJobClassCategories()"); jobClassesMutex.getReadLock(); ArrayList<String> categoryList = new ArrayList<String>(); for (int i=0; i < jobClasses.length; i++) { String categoryName = jobClasses[i].getJobCategoryName(); boolean added = false; for (int j=0; j < categoryList.size(); j++) { String category = categoryList.get(j); if (category.equalsIgnoreCase(categoryName)) { added = true; break; } } if (! added) { categoryList.add(categoryName); } } jobClassesMutex.releaseReadLock(); String[] categoryNames = new String[categoryList.size()]; categoryList.toArray(categoryNames); return categoryNames; } /** * Retrieves the set of job classes defined for use in the configuration * directory, separated into the individual job class categories. * * @return The set of job classes defined for use in the configuration * directory, separated into the individual job class categories. */ public JobClass[][] getCategorizedJobClasses() { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.getCategorizedJobClasses()"); jobClassesMutex.getReadLock(); ArrayList<ArrayList<JobClass>> categoryList = new ArrayList<ArrayList<JobClass>>(); for (int i=0; i < jobClasses.length; i++) { String categoryName = jobClasses[i].getJobCategoryName(); boolean added = false; for (int j=0; j < categoryList.size(); j++) { ArrayList<JobClass> sublist = categoryList.get(j); JobClass jobClass = sublist.get(0); if (jobClass.getJobCategoryName() == null) { if (categoryName == null) { sublist.add(jobClasses[i]); added = true; break; } } else if (jobClass.getJobCategoryName().equalsIgnoreCase(categoryName)) { sublist.add(jobClasses[i]); added = true; break; } } if (! added) { ArrayList<JobClass> sublist = new ArrayList<JobClass>(); sublist.add(jobClasses[i]); categoryList.add(sublist); } } jobClassesMutex.releaseReadLock(); JobClass[][] categorizedClasses = new JobClass[categoryList.size()][]; for (int i=0; i < categorizedClasses.length; i++) { ArrayList<JobClass> sublist = categoryList.get(i); categorizedClasses[i] = new JobClass[sublist.size()]; sublist.toArray(categorizedClasses[i]); } // Sort the category names alphabetically. for (int i=0; i < categorizedClasses.length; i++) { String categoryName = categorizedClasses[i][0].getJobCategoryName(); if (categoryName == null) { // There is no category name. These go at the very end. int lastPos = categorizedClasses.length - 1; if (i < lastPos) { JobClass[] tempClasses = categorizedClasses[i]; categorizedClasses[i] = categorizedClasses[lastPos]; categorizedClasses[lastPos] = tempClasses; i--; } } else { String lowerName = categorizedClasses[i][0].getJobCategoryName().toLowerCase(); for (int j=i+1; j < categorizedClasses.length; j++) { categoryName = categorizedClasses[j][0].getJobCategoryName(); if (categoryName != null) { String lowerName2 = categorizedClasses[j][0].getJobCategoryName().toLowerCase(); if (lowerName2.compareTo(lowerName) < 0) { JobClass[] tempClasses = categorizedClasses[i]; categorizedClasses[i] = categorizedClasses[j]; categorizedClasses[j] = tempClasses; lowerName = lowerName2; } } } } } return categorizedClasses; } /** * Retrieves the SLAMD job class with the specified name. * * @param className The Java class name for the class to retrieve. * * @return The SLAMD job class with the specified name, or <CODE>null</CODE> * if the specified job class is not known to the SLAMD server. */ public JobClass getJobClass(String className) { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.getJobClass()"); JobClass jobClass = null; jobClassesMutex.getReadLock(); for (int i=0; i < jobClasses.length; i++) { if (jobClasses[i].getClass().getName().equals(className)) { jobClass = jobClasses[i]; break; } } jobClassesMutex.releaseReadLock(); return jobClass; } /** * Indicates whether a custom class loader should be used for loading job * classes. * * @return <CODE>true</CODE> if a custom class loader should be used for * loading job classes, or <CODE>false</CODE> if not. */ public boolean useCustomClassLoader() { return useCustomClassLoader; } /** * Loads the job class with the specified name and returns it to the caller. * * @param className The name of the job class to be loaded. * * @return The requested job class. * * @throws SLAMDException If a problem occurs while trying to load the * requested class. */ public JobClass loadJobClass(String className) throws SLAMDException { try { if (useCustomClassLoader) { JobClassLoader classLoader = new JobClassLoader(this.getClass().getClassLoader(), adminServlet.getClassPath()); return classLoader.getJobClass(className); } else { try { Class<?> jobClass = Constants.classForName(className); return (JobClass) jobClass.newInstance(); } catch (Exception e) { logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e)); throw new SLAMDException("Unable to load job class " + className + ": " + e, e); } } } catch (Exception e) { logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e)); return new UnknownJobClass(className); } } /** * Retrieves a cached copy of this job class if possible. Otherwise, attempts * to load the job class using the appropriate class loader. * * @param className The name of the Java class to be returned. * * @return The requested job class. * * @throws SLAMDException If it is necessary to load the job class and a * problem occurs while doing so. */ public JobClass getOrLoadJobClass(String className) throws SLAMDException { JobClass jobClass = getJobClass(className); if (jobClass == null) { jobClass = loadJobClass(className); } return jobClass; } /** * Refreshes the list of available job classes from the definitions stored in * the configuration directory. * * @throws SLAMDServerException If a problem is encountered while reading * from the configuration directory. */ public void reloadJobClasses() throws SLAMDServerException { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.reloadJobClasses()"); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "In SLAMDServer.reloadJobClasses()"); jobClassesMutex.getWriteLock(); try { jobClasses = configDB.getJobClasses(); } catch (DatabaseException de) { throw new SLAMDServerException("Unable to read job class definitions " + "from the configuration database: " + de, de); } sortJobClasses(); jobClassesMutex.releaseWriteLock(); } /** * Adds the specified class as a job thread that can be used to run jobs in * the SLAMD server. * * @param jobClass The job thread class that will be used to perform the * work for this job. * * @throws SLAMDServerException If a problem is encountered while adding the * job class. */ public void addJobClass(JobClass jobClass) throws SLAMDServerException { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.addJobClass(" + jobClass.getClass().getName() + ')'); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "In SLAMDServer.addJobClass(" + jobClass.getClass().getName() + ')'); // First, make sure it's not already defined String className = jobClass.getClass().getName(); jobClassesMutex.getWriteLock(); for (int i=0; i < jobClasses.length; i++) { if (jobClasses[i].getClass().getName().equals(className)) { jobClassesMutex.releaseWriteLock(); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "The job class " + className + " is already defined"); throw new SLAMDServerException("The job class " + className + " is already defined in the " + "configuration"); } } // Add it to the configuration directory try { configDB.addJobClass(className); } catch (DatabaseException de) { jobClassesMutex.releaseWriteLock(); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Unable to add job class " + className + " -- " + de); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(de)); throw new SLAMDServerException("Unable to add job class " + className + " -- " + de, de); } // If we got here, then it was added to the config directory successfully, // so update the job class array JobClass[] tmp = new JobClass[jobClasses.length + 1]; System.arraycopy(jobClasses, 0, tmp, 0, jobClasses.length); tmp[jobClasses.length] = jobClass; jobClasses = tmp; sortJobClasses(); jobClassesMutex.releaseWriteLock(); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Successfully added job class " + className); } /** * Removes the specified class from the list of job classes defined in the * SLAMD server. * * @param jobClass The class to remove from the set of defined job classes. * * @throws SLAMDServerException If a problem is encountered while removing * the job class. */ public void removeJobClass(JobClass jobClass) throws SLAMDServerException { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.removeJobClass(" + jobClass.getClass().getName() + ')'); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "In SLAMDServer.removeJobClass(" + jobClass.getClass().getName() + ')'); String className = jobClass.getClass().getName(); int removePos = -1; jobClassesMutex.getWriteLock(); for (int i=0; i < jobClasses.length; i++) { if (jobClasses[i].getClass().getName().equals(className)) { removePos = i; break; } } if (removePos >= 0) { try { configDB.removeJobClass(className); } catch (DatabaseException de) { throw new SLAMDServerException("Unable to remove the job class " + "definition -- " + de, de); } JobClass[] tmp = new JobClass[jobClasses.length - 1]; System.arraycopy(jobClasses, 0, tmp, 0, removePos); System.arraycopy(jobClasses, (removePos+1), tmp, removePos, (tmp.length - removePos)); jobClasses = tmp; sortJobClasses(); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Successfully removed job class " + className); } else { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "The job class " + className + " is not defined"); } jobClassesMutex.releaseWriteLock(); } /** * Sorts the list of job classes in alphabetical order on the basis of the * job name. The caller must hold the job class write lock. */ private void sortJobClasses() { for (int i=0; i < jobClasses.length; i++) { String name = jobClasses[i].getJobName().toLowerCase(); for (int j=i+1; j < jobClasses.length; j++) { String name2 = jobClasses[j].getJobName().toLowerCase(); if (name2.compareTo(name) < 0) { JobClass tmpClass = jobClasses[i]; jobClasses[i] = jobClasses[j]; jobClasses[j] = tmpClass; name = name2; } } } } /** * Retrieves the set of optimization algorithms defined for use in the SLAMD * server. * * @return The set of optimization algorithms defined for use in the SLAMD * server. */ public OptimizationAlgorithm[] getOptimizationAlgorithms() { return optimizationAlgorithms; } /** * Retrieves the optimization algorithm with the specified class name. * * @param algorithmClass The fully-qualified name of the Java class that * provides the optimization algorithm. * * @return The optimization algorithm with the specified class name, or * <CODE>null</CODE> if no such algorithm is defined. */ public OptimizationAlgorithm getOptimizationAlgorithm(String algorithmClass) { for (int i=0; i < optimizationAlgorithms.length; i++) { if (optimizationAlgorithms[i].getClass().getName().equals(algorithmClass)) { return optimizationAlgorithms[i].newInstance(); } } return null; } /** * Retrieves the time that the SLAMD server was started. * * @return The time that the SLAMD server was started. */ public Date getStartupTime() { return startupTime; } /** * Writes the specified message to the SLAMD log if appropriate (based on the * log level). The message will be formatted so that it contains a timestamp * and a log level indicator. If the logging system has not been initialized, * then the message will be logged to standard error. * * @param messageLogLevel The log level to use when determining whether to * actually write the message to the SLAMD log. * @param logMessage The message to be logged. */ public void logMessage(int messageLogLevel, String logMessage) { // First, see if the message is applicable to our configured log level. If // not, then just return immediately if ((logLevel & messageLogLevel) != messageLogLevel) { return; } // Prepend a timestamp and the log level onto this message String message; synchronized (this) { message = dateFormatter.format(new Date()) + " - " + Constants.logLevelToString(messageLogLevel) + " - " + logMessage; } // If the logger is initialized, then send the message to it. if (loggerInitialized) { logger.logMessage(message); } // If the logger is not yet initialized, or if it is a fatal error message, // then write it to standard error. if ((! loggerInitialized) || (messageLogLevel == Constants.LOG_LEVEL_ANY)) { if (! readOnlyMode) { System.err.println(message); } } } /** * Writes the specified message to the SLAMD log if appropriate (based on the * log level). The message will be logged as-is with no formatting applied, * so the timestamp and log level should be already included in the message. * If the logging system has not been initialized, then the message will be * logged to standard error. * * @param messageLogLevel The log level to use when determining whether to * actually write the message to the SLAMD log. * @param message The message to be logged. */ public void logWithoutFormatting(int messageLogLevel, String message) { // First, see if the message is applicable to our configured log level. If // not, then just return immediately if ((logLevel & messageLogLevel) != messageLogLevel) { return; } // If the logger is initialized, then send the message to it. if (loggerInitialized) { logger.logMessage(message); } // If the logger is not yet initialized, or if it is a fatal error message, // then write it to standard error. if ((! loggerInitialized) || (messageLogLevel == Constants.LOG_LEVEL_ANY)) { if (! readOnlyMode) { System.err.println(message); } } } /** * Retrieves a formatted timestamp containing the current time. * * @return A formatted timestamp containing the current time. */ public synchronized String getTimestamp() { return dateFormatter.format(new Date()); } /** * Retrieves the configuration handler associated with this SLAMD server. * * @return The configuration handler associated with this SLAMD server. */ public SLAMDDB getConfigDB() { return configDB; } /** * Retrieves the client listener associated with this SLAMD server. * * @return The client listener associated with this SLAMD server. */ public ClientListener getClientListener() { return clientListener; } /** * Retrieves the client manager listener associated with this SLAMD server. * * @return The client manager listener associated with this SLAMD server. */ public ClientManagerListener getClientManagerListener() { return clientManagerListener; } /** * Retrieves the resource monitor client listener associated with this SLAMD * server. * * @return The resource monitor client listener associated with this SLAMD * server. */ public ResourceMonitorClientListener getMonitorClientListener() { return monitorClientListener; } /** * Retrieves the stat listener associated with this SLAMD server. * * @return The stat listener associated with this SLAMD server. */ public StatListener getStatListener() { return statListener; } /** * Retrieves the real-time stat handler associated with this SLAMD server. * * @return The real-time stat handler associated with this SLAMD server. */ public RealTimeStatHandler getStatHandler() { return statHandler; } /** * Retrieves the mailer associated with this SLAMD server. * * @return The mailer associated with this SLAMD server. */ public SMTPMailer getMailer() { return mailer; } /** * Checks to see if an e-mail message should be sent to one or more users to * tell them that the specified job is complete. * * @param job The job for which to provide notification. */ public void sendCompletedJobNotification(Job job) { String[] notifyAddresses = job.getNotifyAddresses(); if ((notifyAddresses == null) || (notifyAddresses.length == 0) || (! mailer.isEnabled())) { return; } String EOL = Constants.SMTP_EOL; String subject = "SLAMD Job " + job.getJobID() + " completed"; StringBuilder message = new StringBuilder(); message.append("SLAMD Job " + job.getJobID() + " has completed." + EOL); String baseURI = mailer.getServletBaseURI(); if ((baseURI != null) && (baseURI.length() > 0)) { message.append("For full job details, go to: " + EOL); message.append(baseURI + '?' + Constants.SERVLET_PARAM_SECTION + '=' + Constants.SERVLET_SECTION_JOB + '&' + Constants.SERVLET_PARAM_SUBSECTION + '=' + Constants.SERVLET_SECTION_JOB_VIEW_GENERIC + '&' + Constants.SERVLET_PARAM_JOB_ID + '=' + job.getJobID() + EOL); message.append(EOL); } message.append("Job ID: " + job.getJobID() + EOL + "Job Description: " + job.getJobDescription() + EOL + "Job Type: " + job.getJobName() + EOL + "Job Class: " + job.getJobClass().getClass().getName() + EOL + "Job State: " + Constants.jobStateToString(job.getJobState()) + EOL); if (job.doneRunning()) { message.append(EOL); SimpleDateFormat displayFormat = new SimpleDateFormat(Constants.DISPLAY_DATE_FORMAT); Date actualStartTime = job.getActualStartTime(); if (actualStartTime != null) { message.append("Actual Start Time: " + displayFormat.format(actualStartTime) + EOL); } Date actualStopTime = job.getActualStopTime(); if (actualStopTime != null) { message.append("Actual Stop Time: " + displayFormat.format(actualStopTime) + EOL); } int actualDuration = job.getActualDuration(); if (actualDuration >= 0) { message.append("Actual Duration: " + actualDuration + EOL); } if (job.hasStats()) { message.append(EOL); String[] trackerNames = job.getStatTrackerNames(); for (int i=0; i< trackerNames.length; i++) { StatTracker[] trackers = job.getStatTrackers(trackerNames[i]); if ((trackers != null) && (trackers.length > 0)) { StatTracker tracker = trackers[0].newInstance(); tracker.aggregate(trackers); message.append(tracker.getSummaryString() + EOL); } } } } mailer.sendMessage(notifyAddresses, subject, message.toString()); } /** * Indicates whether the logging subsystem has been initialized. * * @return <CODE>true</CODE> if the logging system has been initialized, or * <CODE>false</CODE> if not. */ public boolean loggerInitialized() { return loggerInitialized; } /** * Specifies whether the logger is initialized or not. * * @param loggerInitialized Used to indicate whether the logger has been * initialized or not. */ public void setLoggerInitialized(boolean loggerInitialized) { logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Setting loggerInitialized to " + loggerInitialized); this.loggerInitialized = loggerInitialized; } /** * Retrieves the scheduler associated with this SLAMD server. * * @return The scheduler associated with this SLAMD server. */ public Scheduler getScheduler() { return scheduler; } /** * Retrieves the location of the SSL key store. * * @return The location of the SSL key store. */ public String getSSLKeyStore() { return sslKeyStore; } /** * Retrieves the password required to access the SSL key store. * * @return The password required to access the SSL key store. */ public String getSSLKeyStorePassword() { return sslKeyPassword; } /** * Retrieves the location of the SSL trust store. * * @return The location of the SSL trust store. */ public String getSSLTrustStore() { return sslTrustStore; } /** * Retrieves the password required to access the SSL trust store. * * @return The password required to access the SSL trust store. */ public String getSSLTrustStorePassword() { return sslTrustPassword; } /** * Retrieves the name that the server uses to subscribe to the configuration * handler in order to be notified of configuration changes. * * @return The name that the server uses to subscribe to the configuration * handler in order to be notified of configuration changes. */ public String getSubscriberName() { return CONFIG_SUBSCRIBER_NAME; } /** * Retrieves the set of configuration parameters associated with this * configuration subscriber. * * @return The set of configuration parameters associated with this * configuration subscriber. */ public ParameterList getSubscriberParameters() { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.getParameters())"); String[] algorithmClasses = new String[optimizationAlgorithms.length]; for (int i=0; i < algorithmClasses.length; i++) { algorithmClasses[i] = optimizationAlgorithms[i].getClass().getName(); } BooleanParameter customClassLoaderParameter = new BooleanParameter(Constants.PARAM_USE_CUSTOM_CLASS_LOADER, "Use Custom Class Loader", "Indicates whether to use the custom class " + "loader. If this class loader is used, it "+ "will be possible to dynamically reload the " + "job class definitions so that a job class " + "can be replaced without restarting the SLAMD " + "server. However, some users have reported " + "problems with this feature. If attempts to " + "interact with job classes fail, particularly " + "with illegal access exceptions, try disabling " + "the custom class loader.", useCustomClassLoader); MultiLineTextParameter optimizationAlgorithmsParameter = new MultiLineTextParameter(Constants.PARAM_OPTIMIZATION_ALGORITHMS, "Optimization Algorithms", "The fully-qualified names of the Java " + "classes that should serve as the " + "optimization algorithms available for " + "use in the SLAMD server.", algorithmClasses, false); optimizationAlgorithmsParameter.setVisibleColumns(80); MultiValuedParameter logLevelParameter = new MultiValuedParameter(Constants.PARAM_LOG_LEVEL, "Log Level", "The categories of information to include " + "in the SLAMD log.", Constants.getLogLevelStrings(), logLevel, false); Parameter[] params = new Parameter[] { logLevelParameter, customClassLoaderParameter, optimizationAlgorithmsParameter }; return new ParameterList(params); } /** * Re-reads all configuration information used by the core SLAMD server. In * this case, the only option is the log level to determine which messages are * written to the log file. If a specific log level was specified in a * constructor, then that will always be used, regardless of the log level * specified in the configuration. */ public void refreshSubscriberConfiguration() { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.refreshConfiguration()"); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "In SLAMDServer.refreshConfiguration()"); // Read the log level from the configuration if (! overrideConfigLogLevel) { String logLevelStr = configDB.getConfigParameter(Constants.PARAM_LOG_LEVEL); if ((logLevelStr != null) && (logLevelStr.length() > 0)) { try { logLevel = Integer.parseInt(logLevelStr); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Setting logLevel to " + logLevel); } catch (NumberFormatException nfe) { logMessage(Constants.LOG_LEVEL_CONFIG, "Config parameter " + Constants.PARAM_LOG_LEVEL + " requires a numeric value"); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { logLevel = Constants.LOG_LEVEL_DEFAULT; } } // Determine whether to use the custom class loader. String propertyStr = System.getProperty(Constants.PROPERTY_DISABLE_CUSTOM_CLASS_LOADER); if ((propertyStr != null) && (propertyStr.equalsIgnoreCase("true") || propertyStr.equalsIgnoreCase("yes") || propertyStr.equalsIgnoreCase("on") || propertyStr.equalsIgnoreCase("1"))) { useCustomClassLoader = false; } else { useCustomClassLoader = false; String classLoaderStr = configDB.getConfigParameter(Constants.PARAM_USE_CUSTOM_CLASS_LOADER); if (classLoaderStr != null) { useCustomClassLoader = (classLoaderStr.equalsIgnoreCase("true") || classLoaderStr.equalsIgnoreCase("yes") || classLoaderStr.equalsIgnoreCase("on") || classLoaderStr.equalsIgnoreCase("1")); } } // Retrieve the set of optimization algorithms to use. String algorithmStr = configDB.getConfigParameter(Constants.PARAM_OPTIMIZATION_ALGORITHMS); if ((algorithmStr == null) || (algorithmStr.length() == 0)) { optimizationAlgorithms = new OptimizationAlgorithm[] { new SingleStatisticOptimizationAlgorithm() }; } else { ArrayList<OptimizationAlgorithm> algorithmList = new ArrayList<OptimizationAlgorithm>(); StringTokenizer tokenizer = new StringTokenizer(algorithmStr, " \t\r\n"); while (tokenizer.hasMoreTokens()) { String className = tokenizer.nextToken(); try { Class<?> algorithmClass = Constants.classForName(className); OptimizationAlgorithm algorithm = (OptimizationAlgorithm) algorithmClass.newInstance(); algorithmList.add(algorithm); } catch (Exception e) { logMessage(Constants.LOG_LEVEL_CONFIG, "Unable to load optimization algorithm \"" + className + '"' + e); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e)); } } optimizationAlgorithms = new OptimizationAlgorithm[algorithmList.size()]; algorithmList.toArray(optimizationAlgorithms); } } /** * Re-reads the configuration information for the specified parameter. In * this case, the only option is the log level to determine which messages are * written to the log file. If a specific log level was specified in a * constructor, then that will always be used, regardless of the log level * specified in the configuration. * * @param parameterName The name of the parameter to be re-read from the * configuration. */ public void refreshSubscriberConfiguration(String parameterName) { logMessage(Constants.LOG_LEVEL_TRACE, "In SLAMDServer.refreshConfiguration(" + parameterName + ')'); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "In SLAMDServer.refreshConfiguration(" + parameterName + ')'); if (parameterName.equalsIgnoreCase(Constants.PARAM_LOG_LEVEL) && (! overrideConfigLogLevel)) { String logLevelStr = configDB.getConfigParameter(Constants.PARAM_LOG_LEVEL); if ((logLevelStr != null) && (logLevelStr.length() > 0)) { try { logLevel = Integer.parseInt(logLevelStr); logMessage(Constants.LOG_LEVEL_SERVER_DEBUG, "Setting logLevel to " + logLevel); } catch (NumberFormatException nfe) { logMessage(Constants.LOG_LEVEL_CONFIG, "Config parameter " + Constants.PARAM_LOG_LEVEL + " requires a numeric value"); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { logLevel = Constants.LOG_LEVEL_ANY; } } else if (parameterName.equalsIgnoreCase( Constants.PARAM_USE_CUSTOM_CLASS_LOADER)) { // Determine whether to use the custom class loader. String propertyStr = System.getProperty(Constants.PROPERTY_DISABLE_CUSTOM_CLASS_LOADER); if ((propertyStr != null) && (propertyStr.equalsIgnoreCase("true") || propertyStr.equalsIgnoreCase("yes") || propertyStr.equalsIgnoreCase("on") || propertyStr.equalsIgnoreCase("1"))) { useCustomClassLoader = false; } else { useCustomClassLoader = false; String classLoaderStr = configDB.getConfigParameter( Constants.PARAM_USE_CUSTOM_CLASS_LOADER); if (classLoaderStr != null) { useCustomClassLoader = (classLoaderStr.equalsIgnoreCase("true") || classLoaderStr.equalsIgnoreCase("yes") || classLoaderStr.equalsIgnoreCase("on") || classLoaderStr.equalsIgnoreCase("1")); } } } else if (parameterName.equalsIgnoreCase( Constants.PARAM_OPTIMIZATION_ALGORITHMS)) { // Retrieve the set of optimization algorithms to use. String algorithmStr = configDB.getConfigParameter( Constants.PARAM_OPTIMIZATION_ALGORITHMS); if ((algorithmStr == null) || (algorithmStr.length() == 0)) { optimizationAlgorithms = new OptimizationAlgorithm[] { new SingleStatisticOptimizationAlgorithm() }; } else { ArrayList<OptimizationAlgorithm> algorithmList = new ArrayList<OptimizationAlgorithm>(); StringTokenizer tokenizer = new StringTokenizer(algorithmStr, " \t\r\n"); while (tokenizer.hasMoreTokens()) { String className = tokenizer.nextToken(); try { Class<?> algorithmClass = Constants.classForName(className); OptimizationAlgorithm algorithm = (OptimizationAlgorithm) algorithmClass.newInstance(); algorithmList.add(algorithm); } catch (Exception e) { logMessage(Constants.LOG_LEVEL_CONFIG, "Unable to load optimization algorithm \"" + className + '"' + e); logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e)); } } optimizationAlgorithms = new OptimizationAlgorithm[algorithmList.size()]; algorithmList.toArray(optimizationAlgorithms); } } } }