/* * 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.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import javax.net.ssl.SSLServerSocketFactory; import com.slamd.admin.AccessManager; import com.slamd.admin.AdminAccess; import com.slamd.asn1.ASN1Writer; import com.slamd.common.Constants; import com.slamd.common.RefCountMutex; import com.slamd.common.SLAMDException; import com.slamd.db.SLAMDDB; import com.slamd.job.Job; import com.slamd.job.JobClass; import com.slamd.message.HelloResponseMessage; import com.slamd.message.JobCompletedMessage; import com.slamd.message.StatusRequestMessage; import com.slamd.parameter.BooleanParameter; import com.slamd.parameter.IntegerParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; import com.slamd.stat.StatTracker; /** * This class implements the client listener that the SLAMD server uses to * listen for connections from clients. If fewer than the maximum connections * are established, then the connection will be accepted and a new connection * thread will be spawned to handle operations on that connection. If the * maximum number of connections are already in use, then the new connection * will be rejected. * * * @author Neil A. Wilson */ public class ClientListener extends Thread implements ConfigSubscriber { // The set of connections that are available for use in job processing private ArrayList<ClientConnection> availableConnections; // The list of client connections that are currently established private ArrayList<ClientConnection> connectionList; // Indicates whether this listener should continue accepting client // connections private boolean keepListening; // Indicates whether this listener has actually stopped. private boolean hasStopped; // Indicates whether this listener requires clients to authenticate. private boolean requireAuthentication; // Indicates whether this listener should use SSL. private boolean useSSL; // The length of time in seconds that should pass between keepalive messages private int keepaliveInterval; // The port on which the server is listening for new connections protected int listenPort; // The maximum number of simultaneous clients that may be connected to the // server at once. private int maxClients; // The maximum length of time in seconds that the client connection will wait // for a response from a solicited message private int maxResponseWaitTime; // A mutex used to protect multithreaded access to the connection list private RefCountMutex connectionListMutex; // The server socket used to listen for new connections private ServerSocket serverSocket; // The configuration database associated with the SLAMD server private SLAMDDB configDB; // The SLAMD server with which this client listener is associated private SLAMDServer slamdServer; /** * Creates a new listener to accept client connections. * * @param slamdServer The SLAMD server with which this listener is * associated. */ public ClientListener(SLAMDServer slamdServer) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "Entering ClientListener constructor"); setName("Client Listener"); // Initialize all the instance variables this.slamdServer = slamdServer; maxClients = Constants.NO_MAX_CLIENTS; availableConnections = new ArrayList<ClientConnection>(); connectionList = new ArrayList<ClientConnection>(); connectionListMutex = new RefCountMutex(); keepListening = true; hasStopped = true; // Read the appropriate information from the configuration configDB = slamdServer.getConfigDB(); configDB.registerAsSubscriber(this); String maxClientsStr = configDB.getConfigParameter(Constants.PARAM_LISTENER_MAX_CLIENTS); if ((maxClientsStr != null) && (maxClientsStr.length() > 0)) { try { maxClients = Integer.parseInt(maxClientsStr); } catch (NumberFormatException nfe) {} } keepaliveInterval = Constants.DEFAULT_LISTENER_KEEPALIVE_INTERVAL; String keepaliveStr = configDB.getConfigParameter( Constants.PARAM_LISTENER_KEEPALIVE_INTERVAL); if ((keepaliveStr != null) && (keepaliveStr.length() != 0)) { try { keepaliveInterval = Integer.parseInt(keepaliveStr); } catch (NumberFormatException nfe) {} } listenPort = Constants.DEFAULT_LISTENER_PORT_NUMBER; String portStr = configDB.getConfigParameter(Constants.PARAM_LISTENER_PORT); if ((portStr != null) && (portStr.length() != 0)) { try { listenPort = Integer.parseInt(portStr); } catch (NumberFormatException nfe) {} } maxResponseWaitTime = Constants.DEFAULT_MAX_RESPONSE_WAIT_TIME; String waitStr = configDB.getConfigParameter(Constants.PARAM_MAX_RESPONSE_WAIT_TIME); if ((waitStr != null) && (waitStr.length() > 0)) { try { maxResponseWaitTime = Integer.parseInt(waitStr); } catch (NumberFormatException nfe) {} } requireAuthentication = Constants.DEFAULT_REQUIRE_AUTHENTICATION; String requireAuthStr = configDB.getConfigParameter(Constants.PARAM_REQUIRE_AUTHENTICATION); if ((requireAuthStr != null) && (requireAuthStr.length() > 0)) { requireAuthentication = requireAuthStr.equals(Constants.CONFIG_VALUE_TRUE); if (requireAuthentication) { AccessManager accessManager = AdminAccess.getAccessManager(); if (accessManager == null) { slamdServer.logMessage(Constants.LOG_LEVEL_ADMIN, "Could not enable client authentication -- " + "access control is not enabled in the " + "admin interface."); requireAuthentication = false; } } } useSSL = Constants.DEFAULT_LISTENER_USE_SSL; String sslStr = configDB.getConfigParameter(Constants.PARAM_LISTENER_USE_SSL); if ((sslStr != null) && (sslStr.length() > 0)) { useSSL = sslStr.equals(Constants.CONFIG_VALUE_TRUE); if (useSSL) { String keyStore = slamdServer.getSSLKeyStore(); if ((keyStore != null) && (keyStore.length() > 0)) { System.setProperty(Constants.SSL_KEY_STORE_PROPERTY, keyStore); } String keyStorePassword = slamdServer.getSSLKeyStorePassword(); if ((keyStorePassword != null) && (keyStorePassword.length() > 0)) { System.setProperty(Constants.SSL_KEY_PASSWORD_PROPERTY, keyStorePassword); } String trustStore = slamdServer.getSSLTrustStore(); if ((trustStore != null) && (trustStore.length() > 0)) { System.setProperty(Constants.SSL_TRUST_STORE_PROPERTY, trustStore); } String trustStorePassword = slamdServer.getSSLTrustStorePassword(); if ((trustStorePassword != null) && (trustStorePassword.length() > 0)) { System.setProperty(Constants.SSL_TRUST_PASSWORD_PROPERTY, trustStorePassword); } } } slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "Leaving ClientListener constructor"); } /** * Indicates that the listener should start listening for client connections. */ public void startListening() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientListener.startListening()"); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ClientListener.startListening()"); keepListening = true; start(); } /** * Indicates that the listener should stop listening for client connections. * It will also notify all connected clients that the listener is shutting * down. */ public void stopListening() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientListener.stopListening()"); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ClientListener.stopListening()"); // Set the flag that indicates the server should no longer be listening keepListening = false; // Close the server socket so that no more connections will be accepted try { serverSocket.close(); } catch (Exception e) {} // Iterate through all the connections and inform them that the server is // shutting down. The sendServerShutdownMessage() method will also close // the connections. connectionListMutex.getWriteLock(); for (int i=0; i < connectionList.size(); i++) { ClientConnection clientConnection = connectionList.get(i); clientConnection.sendServerShutdownMessage(true); } connectionList.clear(); availableConnections.clear(); connectionListMutex.releaseWriteLock(); } /** * This method will not return until the client listener has actually stopped. * Note that it does not stop the listener -- you should first call the * <CODE>stopListening</CODE> method to signal the listener that it needs to * stop. */ public void waitForStop() { while (! hasStopped) { try { Thread.sleep(Constants.THREAD_BLOCK_SLEEP_TIME); } catch (InterruptedException ie) {} } } /** * Creates the server socket and listens for new connections. If the * connection will be accepted, then a new connection thread will be spawned * to handle it. If the connection will not be accepted, then it will be * rejected here. */ @Override() public void run() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientLister.run()"); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ClientListener.run()"); hasStopped = false; if (useSSL) { try { SSLServerSocketFactory socketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); serverSocket = socketFactory.createServerSocket(listenPort); slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Listening for SSL-based client connections " + "on port " + listenPort); } catch (Exception e) { e.printStackTrace(); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e)); slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to create SSL server socket: " + e); hasStopped = true; return; } } else { try { serverSocket = new ServerSocket(listenPort); slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Listening for client connections on port " + listenPort); } catch (IOException ioe) { ioe.printStackTrace(); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to create server socket: " + ioe); hasStopped = true; return; } } while (keepListening) { Socket clientSocket = null; try { clientSocket = serverSocket.accept(); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "New client connection received from " + clientSocket.getInetAddress().toString()); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Exception accepting client connection: " + ioe); continue; } try { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Going to get a write lock on the " + "connection list."); try { connectionListMutex.getWriteLock(5000); } catch (InterruptedException ie) { slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ie)); slamdServer.logMessage(Constants.LOG_LEVEL_ANY, "Unable to obtain write lock on connection " + "list. Refusing client connection."); HelloResponseMessage helloResp = new HelloResponseMessage(0, Constants.MESSAGE_RESPONSE_SERVER_ERROR, "Unable to obtain a write lock on the connection list.", -1); ASN1Writer writer = new ASN1Writer(clientSocket.getOutputStream()); writer.writeElement(helloResp.encode()); clientSocket.close(); continue; } slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Acquired the write lock."); if ((maxClients > 0) && (connectionList.size() >= maxClients)) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT, "Refusing connection from " + clientSocket.getInetAddress().toString() + " -- too many concurrent connections"); // Normally, messages that the server originates are odd-numbered. // However, in this case the client should always send a hello as // the first operation that deserves a response, so we can use // message ID 0 for that. HelloResponseMessage helloResp = new HelloResponseMessage(0, Constants.MESSAGE_RESPONSE_CONNECTION_LIMIT_REACHED, "The maximum number of simultaneous connections has " + "been reached", -1); ASN1Writer writer = new ASN1Writer(clientSocket.getOutputStream()); writer.writeElement(helloResp.encode()); clientSocket.close(); } else { String connectionID = getNewConnectionID(); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Assigned a connection ID of " + connectionID); ClientConnection clientConnection = new ClientConnection(slamdServer, this, clientSocket, connectionID); availableConnections.add(clientConnection); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Added connection to the list of available " + "connections"); connectionList.add(clientConnection); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Added connection to the list of " + "established connections"); clientConnection.start(); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Started the client thread"); StatusRequestMessage statusRequest = new StatusRequestMessage(clientConnection.getMessageID()); clientConnection.sendMessage(statusRequest); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Sent a status request message to the client"); } slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Releasing the write lock on the client list."); connectionListMutex.releaseWriteLock(); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Releasing the write lock on the client list " + " -- I/O exception"); connectionListMutex.releaseWriteLock(); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Exception sending message to client: " + ioe); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); ioe.printStackTrace(); } catch (SLAMDException se) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Releasing the write lock on the client list " + " -- Client hello exception"); connectionListMutex.releaseWriteLock(); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Exception processing the client hello: " + se); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(se)); se.printStackTrace(); } } hasStopped = true; slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "Leaving ClientLister.run()"); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Leaving ClientListener.run()"); } /** * Indicates whether the client listener currently has connections available * that satisfy the requirements for the indicated job. * * @param job The job for which to make the determination. * * @return <CODE>true</CODE> if this client listener has an appropriate set * of clients available, or <CODE>false</CODE> if not. */ public boolean connectionsAvailable(Job job) { int clientsNeeded = job.getNumberOfClients(); if (clientsNeeded > availableConnections.size()) { return false; } connectionListMutex.getReadLock(); // Create a clone of the available connection list. This is necessary // because it is possible for the same client address to appear multiple // times in the list of requested clients, and we don't want the same // client connection to be counted multiple times. ArrayList<ClientConnection> availableClone = new ArrayList<ClientConnection>(availableConnections.size()); for (int i=0; i < availableConnections.size(); i++) { availableClone.add(availableConnections.get(i)); } String[] requestedClients = job.getRequestedClients(); if (requestedClients != null) { for (int i=0; i < requestedClients.length; i++) { boolean matchFound = false; for (int j=0; j < availableClone.size(); j++) { ClientConnection connection = availableClone.get(j); if (connection.getClientIPAddress().equals(requestedClients[i])) { matchFound = true; availableClone.remove(j); clientsNeeded--; break; } } if (! matchFound) { connectionListMutex.releaseReadLock(); return false; } } } int lookPos = 0; while ((clientsNeeded > 0) && (lookPos < availableClone.size())) { ClientConnection connection = availableClone.get(lookPos); if (connection.restrictedMode) { lookPos++; } else { availableClone.remove(lookPos); clientsNeeded--; } } connectionListMutex.releaseReadLock(); return (clientsNeeded == 0); } /** * Retrieves the set of client connections for processing the specified job. * * @param job The job for which the connections are to be retrieved. * * @return The appropriate set of clients for the specified job, or * <CODE>null</CODE> if there is not an appropriate set of * connections available. */ public ClientConnection[] getClientConnections(Job job) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientLister.getClientConnections()"); ArrayList<ClientConnection> jobConnections = new ArrayList<ClientConnection>(); int numConnections = job.getNumberOfClients(); if (availableConnections.size() < numConnections) { return null; } connectionListMutex.getWriteLock(); String[] requestedClients = job.getRequestedClients(); if (requestedClients != null) { for (int i=0; i < requestedClients.length; i++) { boolean matchFound = false; for (int j=0; j < availableConnections.size(); j++) { ClientConnection conn = availableConnections.get(j); if (conn.getClientIPAddress().equals(requestedClients[i])) { jobConnections.add(conn); availableConnections.remove(j); matchFound = true; break; } } if (! matchFound) { // The requested client could not be found. Return the connections // allocated so far back to the set of available connections and // return null. for (int j=0; j < jobConnections.size(); j++) { availableConnections.add(jobConnections.get(j)); } connectionListMutex.releaseWriteLock(); return null; } } } // If we still don't have enough clients allocated for the job (i.e., either // no specific clients were requested, or there were not enough specific // clients requested) then try to add the appropriate number of remaining // clients, balancing across the set of client addresses. if (jobConnections.size() < numConnections) { int numAvailable = 0; HashMap<String,LinkedList<ClientConnection>> connHash = new HashMap<String,LinkedList<ClientConnection>>(); for (int i=0; i < availableConnections.size(); i++) { ClientConnection connection = availableConnections.get(i); if (connection.restrictedMode()) { continue; } String clientIP = connection.getClientIPAddress(); LinkedList<ClientConnection> clientList = connHash.get(clientIP); if (clientList == null) { clientList = new LinkedList<ClientConnection>(); } clientList.add(connection); connHash.put(clientIP, clientList); numAvailable++; } if ((jobConnections.size() + numAvailable) >= numConnections) { Iterator iterator = connHash.keySet().iterator(); while (jobConnections.size() < numConnections) { String clientIP; if (! iterator.hasNext()) { iterator = connHash.keySet().iterator(); } clientIP = (String) iterator.next(); LinkedList<ClientConnection> clientList = connHash.get(clientIP); ClientConnection connection = clientList.removeFirst(); jobConnections.add(connection); availableConnections.remove(connection); if (clientList.isEmpty()) { iterator.remove(); connHash.remove(clientIP); } } } } // If we don't have enough clients available (e.g., some of them were in // restricted mode), then return them all back to the pool and return null. if (jobConnections.size() < numConnections) { for (int j=0; j < jobConnections.size(); j++) { availableConnections.add(jobConnections.get(j)); } connectionListMutex.releaseWriteLock(); return null; } // Return the set of allocated jobs. connectionListMutex.releaseWriteLock(); ClientConnection[] conns = new ClientConnection[jobConnections.size()]; jobConnections.toArray(conns); return conns; } /** * Retrieves the set of connections that are currently established. This is * only for use for status info because not all of the connections may be * available for processing a new job. * * @return The set of connections that are currently established. */ public ClientConnection[] getConnectionList() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientLister.getConnectionList()"); connectionListMutex.getReadLock(); ClientConnection[] conns = new ClientConnection[connectionList.size()]; connectionList.toArray(conns); connectionListMutex.releaseReadLock(); return conns; } /** * Retrieves the set of connections that are currently established, sorted by * client ID. This is only for use for status info because not all of the * connections may be available for processing a new job. * * @return The set of connections that are currently established. */ public ClientConnection[] getSortedConnectionList() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientLister.getConnectionList()"); connectionListMutex.getReadLock(); ClientConnection[] conns = new ClientConnection[connectionList.size()]; connectionList.toArray(conns); connectionListMutex.releaseReadLock(); Arrays.sort(conns); return conns; } /** * Retrieves a connection ID that can be used to uniquely identify each * client connection. * * @return A connection ID that can be used to uniquely identify each client * connection. */ public String getNewConnectionID() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientLister.getNewConnectionID()"); // Cheat -- use the unique ID generator in the scheduler return slamdServer.getScheduler().generateUniqueID(); } /** * Indicates whether this client listener requires clients to authenticate. * * @return <CODE>true</CODE> if clients are required to authenticate, or * <CODE>false</CODE> if they are not. */ public boolean requireAuthentication() { return requireAuthentication; } /** * Sends a message to the specified client indicating that it should * disconnect from the SLAMD server. If it is currently processing a job, * then the client will be given an opportunity to send its results to the * SLAMD server. * * @param clientID The client ID of the client to which the request is to be * sent. * * @return <CODE>true</CODE> if a request was sent to the specified client, * or <CODE>false</CODE> if not. */ public boolean requestDisconnect(String clientID) { ClientConnection clientToDisconnect = null; connectionListMutex.getWriteLock(); for (int i=0; i < connectionList.size(); i++) { ClientConnection clientConnection = connectionList.get(i); if (clientConnection.getClientID().equals(clientID)) { clientToDisconnect = clientConnection; break; } } connectionListMutex.releaseWriteLock(); if (clientToDisconnect == null) { return false; } clientToDisconnect.sendServerShutdownMessage(false); availableConnections.remove(clientToDisconnect); return true; } /** * Sends a message to each client indicating that it should disconnect * itself from the SLAMD server. If any clients are currently processing * jobs, then those clients will be given an opportunity to send their results * to the SLAMD server. */ public void requestDisconnectAll() { connectionListMutex.getWriteLock(); availableConnections.clear(); ClientConnection[] connectionArray = new ClientConnection[connectionList.size()]; connectionList.toArray(connectionArray); connectionListMutex.releaseWriteLock(); for (int i=0; i < connectionArray.length; i++) { connectionArray[i].sendServerShutdownMessage(false); } } /** * Forcefully closes the connection to the specified client and removes all * references to it from the SLAMD server. If it is currently processing a * job, then the job will be notified that the client has disconnected and * should not expect any results from that client. * * @param clientID The client ID of the client to be disconnected. * * @return <CODE>true</CODE> if the client was disconnected, or * <CODE>false</CODE> if not. */ public boolean forceDisconnect(String clientID) { ClientConnection clientToDisconnect = null; connectionListMutex.getWriteLock(); for (int i=0; i < connectionList.size(); i++) { ClientConnection clientConnection = connectionList.get(i); if (clientConnection.getClientID().equals(clientID)) { clientToDisconnect = clientConnection; break; } } connectionListMutex.releaseWriteLock(); if (clientToDisconnect == null) { return false; } try { clientToDisconnect.socket.close(); } catch (Exception e) {} return true; } /** * Forcefully closes the connections for all clients connected to the SLAMD * server. If any clients are currently processing a job, then no information * will be available for that job from those clients. */ public void forcefullyDisconnectAll() { connectionListMutex.getWriteLock(); availableConnections.clear(); ClientConnection[] connectionArray = new ClientConnection[connectionList.size()]; connectionList.toArray(connectionArray); connectionList.clear(); connectionListMutex.releaseWriteLock(); for (int i=0; i < connectionArray.length; i++) { try { connectionArray[i].socket.close(); } catch (Exception e) {} connectionLost(connectionArray[i]); } } /** * Indicates that the specified connection is closing and all references to it * should be removed. * * @param clientConnection The connection that is shutting down. */ public void connectionLost(ClientConnection clientConnection) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientLister.connectionLost()"); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT, "Lost connection to client " + clientConnection.toString()); connectionListMutex.getWriteLock(); // First, iterate through the list of connections for (int i=0; i < connectionList.size(); i++) { ClientConnection conn = connectionList.get(i); if (conn.getConnectionID().equals(clientConnection.getConnectionID())) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Removing conn " + clientConnection.toString() + " from connection list"); connectionList.remove(i); break; } } // Next, iterate through the list of available connections for (int i=0; i < availableConnections.size(); i++) { ClientConnection conn = availableConnections.get(i); if (conn.getConnectionID().equals(clientConnection.getConnectionID())) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Removing conn " + clientConnection.toString() + " from available connections"); availableConnections.remove(i); break; } } connectionListMutex.releaseWriteLock(); // If there was a job in progress, it needs to be updated to indicate this // client won't be providing any results if (clientConnection.getJob() != null) { Job job = clientConnection.getJob(); String[] messages = new String[] { "The job was cancelled on client " + clientConnection.getClientID() + " because the connection to the client was lost." }; JobCompletedMessage completedMessage = new JobCompletedMessage(clientConnection.getMessageID(), job.getJobID(), Constants.JOB_STATE_STOPPED_DUE_TO_ERROR, job.getActualStartTime(), new Date(), -1, new StatTracker[0], messages); clientConnection.getJob().clientDone(clientConnection, completedMessage); } // See if this client was associated with a client manager and if so, update // it. slamdServer.getClientManagerListener().clientConnectionLost( clientConnection); } /** * Indicates that the specified client connection is available for processing * new jobs. This will be called by the client connection when the client * indicates that it has finished processing a job that had been assigned to * it. * * @param clientConnection The connection that is announcing its * availability. */ public void setAvailableForProcessing(ClientConnection clientConnection) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientLister.setAvailableForProcessing()"); connectionListMutex.getWriteLock(); // First, make sure that the connection is not already on the available // list. This shouldn't happen, but if it does then it could cause a busy // client to be assigned more work (which it will reject). for (int i=0; i < availableConnections.size(); i++) { ClientConnection conn = availableConnections.get(i); if (conn.getClientID().equals(clientConnection.getClientID())) { connectionListMutex.releaseWriteLock(); return; } } // Add this connection to the list of available connections slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Adding conn " + clientConnection.toString() + " to available connection list"); availableConnections.add(clientConnection); connectionListMutex.releaseWriteLock(); } /** * Retrieves the length of time that should pass between keepalive messages. * A keepalive message will be sent if there has been no interaction with the * client for the specified period of time. * * @return The length of time that should pass between keepalive messages. */ public int getKeepAliveInterval() { return keepaliveInterval; } /** * Retrieves the maximum number of concurrent client connections that should * be allowed for this listener. * * @return The maximum number of concurrent connections that should be * allowed for this listener. */ public int getMaxClients() { return maxClients; } /** * Retrieves the maximum amount of time in seconds that a client connection * should wait for a response to a solicited message before returning an * error. * * @return The maximum amount of time in seconds that a client connection * should wait for a response to a solicited message before returning * an error. */ public int getMaxResponseWaitTime() { return maxResponseWaitTime; } /** * Indicates whether this client listener is configured to use SSL. * * @return <CODE>true</CODE> if this client listener is configured to use * SSL, or <CODE>false</CODE> if it is not. */ public boolean useSSL() { return useSSL; } /** * Retrieves the name that the client listener uses to subscribe to the * configuration handler in order to be notified of configuration changes. * * @return The name that the client listener uses to subscribe to the * configuration handler in order to be notified of configuration * changes. */ public String getSubscriberName() { return "SLAMD Client Listener"; } /** * 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() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientListener.getParameters()"); BooleanParameter requireAuthParameter = new BooleanParameter(Constants.PARAM_REQUIRE_AUTHENTICATION, "Require Authentication", "Indicates whether the client listener will " + "require clients to authenticate.", requireAuthentication); BooleanParameter useSSLParameter = new BooleanParameter(Constants.PARAM_LISTENER_USE_SSL, "Use SSL", "Indicates whether the client listener will " + "use SSL to encrypt communication between the " + "clients and the SLAMD server.", useSSL); IntegerParameter portParameter = new IntegerParameter(Constants.PARAM_LISTENER_PORT, "Client Listener Port", "The port on which the SLAMD server listens " + "for client connections.", true, listenPort, true, 1, true, 65535); IntegerParameter keepaliveParameter = new IntegerParameter(Constants.PARAM_LISTENER_KEEPALIVE_INTERVAL, "Keepalive Interval", "The length of time (in seconds) that will " + "pass between keepalive messages. A value of " + "0 will disable keepalive messages.", true, keepaliveInterval, true, 0, false, 0); IntegerParameter maxClientsParameter = new IntegerParameter(Constants.PARAM_LISTENER_MAX_CLIENTS, "Maximum Number of Clients", "The maximum number of clients that may be " + "connected to the SLAMD server at any time. " + "A value of 0 indicates that there will be no " + "limit.", true, maxClients, true, 0, false, 0); IntegerParameter maxWaitTimeParameter = new IntegerParameter(Constants.PARAM_MAX_RESPONSE_WAIT_TIME, "Maximum Wait Time", "The maximum length of time (in seconds) that " + "the SLAMD server will wait for a response " + "to a request issued to a client.", true, maxResponseWaitTime, true, 0, false, 0); Parameter[] params = new Parameter[] { portParameter, keepaliveParameter, maxClientsParameter, maxWaitTimeParameter, useSSLParameter, requireAuthParameter }; return new ParameterList(params); } /** * Re-reads all configuration information used by the client listener. In * this case, the only options are the keepalive interval, the maximum number * of clients, and the maximum response wait time. The listen port is not * dynamically reconfigurable. Additionally, changes made to the keepalive * interval, maximum number of connections, and maximum wait time will only * apply to new connections established after this point. Existing * connections will remain unchanged. * * @throws SLAMDServerException If there is a problem reading or applying * the changes. */ public void refreshSubscriberConfiguration() throws SLAMDServerException { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientListener.refreshConfiguration()"); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ClientListener.refreshConfiguration()"); // Get the keepalive interval. Note that this will only apply to new client // connections -- existing client connections will not be impacted String keepAliveStr = configDB.getConfigParameter( Constants.PARAM_LISTENER_KEEPALIVE_INTERVAL); if ((keepAliveStr != null) && (keepAliveStr.length() > 0)) { try { keepaliveInterval = Integer.parseInt(keepAliveStr); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set keepaliveInterval to " + keepaliveInterval); } catch (NumberFormatException nfe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Keepalive Interval String " + keepAliveStr + " not an integer"); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { keepaliveInterval = Constants.NO_KEEPALIVE_INTERVAL; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Keepalive Messages Disabled"); } // Get the maximum number of client connections. This will only apply to // new client connections. String maxConnStr = configDB.getConfigParameter(Constants.PARAM_LISTENER_MAX_CLIENTS); if ((maxConnStr != null) && (maxConnStr.length() > 0)) { try { maxClients = Integer.parseInt(maxConnStr); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set max clients to " + maxClients); } catch (NumberFormatException nfe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Max Clients String " + maxConnStr + " not an integer"); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { maxClients = Constants.NO_MAX_CLIENTS; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Now allowing unlimited client connections"); } // Get the maximum response wait time, also applicable only to new // connections. String waitTimeStr = configDB.getConfigParameter(Constants.PARAM_MAX_RESPONSE_WAIT_TIME); if ((waitTimeStr != null) && (waitTimeStr.length() > 0)) { try { maxResponseWaitTime = Integer.parseInt(waitTimeStr); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set max response wait time to " + maxResponseWaitTime); } catch (NumberFormatException nfe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Max Wait Time String " + waitTimeStr + " not an integer"); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { maxResponseWaitTime = Constants.DEFAULT_MAX_RESPONSE_WAIT_TIME; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set max response wait time to default"); } // Determine whether the client listener will require clients to // authenticate. String requireAuthStr = configDB.getConfigParameter(Constants.PARAM_REQUIRE_AUTHENTICATION); if ((requireAuthStr != null) && (requireAuthStr.length() > 0)) { requireAuthentication = requireAuthStr.equals(Constants.CONFIG_VALUE_TRUE); if (requireAuthentication) { AccessManager accessManager = AdminAccess.getAccessManager(); if (accessManager == null) { slamdServer.logMessage(Constants.LOG_LEVEL_ADMIN, "Could not enable client authentication -- " + "access control is not enabled in the " + "admin interface."); requireAuthentication = false; } } } // Determine whether the client listener will require clients to // authenticate. String sslStr = configDB.getConfigParameter(Constants.PARAM_LISTENER_USE_SSL); if ((sslStr != null) && (sslStr.length() > 0)) { useSSL = sslStr.equals(Constants.CONFIG_VALUE_TRUE); if (useSSL) { String keyStore = slamdServer.getSSLKeyStore(); if ((keyStore != null) && (keyStore.length() > 0)) { System.setProperty(Constants.SSL_KEY_STORE_PROPERTY, keyStore); } String keyStorePassword = slamdServer.getSSLKeyStorePassword(); if ((keyStorePassword != null) && (keyStorePassword.length() > 0)) { System.setProperty(Constants.SSL_KEY_PASSWORD_PROPERTY, keyStorePassword); } String trustStore = slamdServer.getSSLTrustStore(); if ((trustStore != null) && (trustStore.length() > 0)) { System.setProperty(Constants.SSL_TRUST_STORE_PROPERTY, trustStore); } String trustStorePassword = slamdServer.getSSLTrustStorePassword(); if ((trustStorePassword != null) && (trustStorePassword.length() > 0)) { System.setProperty(Constants.SSL_TRUST_PASSWORD_PROPERTY, trustStorePassword); } } } } /** * Re-reads the configuration for the specified parameter if the parameter is * applicable to the client listener. In this case, only the keepalive * interval, maximum number of client connections, and maximum response wait * time are reconfigurable. * * @param parameterName The name of the parameter for which to reread the * configuration. * * @throws SLAMDServerException If there is a problem reading or applying * the specified change. */ public void refreshSubscriberConfiguration(String parameterName) throws SLAMDServerException { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ClientListener.refreshConfiguration(" + parameterName + ')'); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ClientListener.refreshConfiguration(" + parameterName + ')'); if (parameterName.equalsIgnoreCase( Constants.PARAM_LISTENER_KEEPALIVE_INTERVAL)) { String keepAliveStr = configDB.getConfigParameter( Constants.PARAM_LISTENER_KEEPALIVE_INTERVAL); if ((keepAliveStr != null) && (keepAliveStr.length() > 0)) { try { keepaliveInterval = Integer.parseInt(keepAliveStr); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set keepaliveInterval to " + keepaliveInterval); } catch (NumberFormatException nfe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Keepalive Interval String " + keepAliveStr + " not an integer"); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { keepaliveInterval = Constants.NO_KEEPALIVE_INTERVAL; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Keepalive Messages Disabled"); } } else if (parameterName.equalsIgnoreCase( Constants.PARAM_LISTENER_MAX_CLIENTS)) { String maxConnStr = configDB.getConfigParameter(Constants.PARAM_LISTENER_MAX_CLIENTS); if ((maxConnStr != null) && (maxConnStr.length() > 0)) { try { maxClients = Integer.parseInt(maxConnStr); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set max clients to " + maxClients); } catch (NumberFormatException nfe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Max Clients String " + maxConnStr + " not an integer"); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { maxClients = Constants.NO_MAX_CLIENTS; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Now allowing unlimited client connections"); } } else if (parameterName.equalsIgnoreCase( Constants.PARAM_MAX_RESPONSE_WAIT_TIME)) { String waitTimeStr = configDB.getConfigParameter(Constants.PARAM_MAX_RESPONSE_WAIT_TIME); if ((waitTimeStr != null) && (waitTimeStr.length() > 0)) { try { maxResponseWaitTime = Integer.parseInt(waitTimeStr); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set max response wait time to " + maxResponseWaitTime); } catch (NumberFormatException nfe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Max Wait Time String " + waitTimeStr + " not an integer"); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(nfe)); } } else { maxResponseWaitTime = Constants.DEFAULT_MAX_RESPONSE_WAIT_TIME; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set max response wait time to default"); } } else if (parameterName.equalsIgnoreCase( Constants.PARAM_REQUIRE_AUTHENTICATION)) { // Determine whether the client listener will require clients to // authenticate. String requireAuthStr = configDB.getConfigParameter(Constants.PARAM_REQUIRE_AUTHENTICATION); if ((requireAuthStr != null) && (requireAuthStr.length() > 0)) { requireAuthentication = requireAuthStr.equals(Constants.CONFIG_VALUE_TRUE); if (requireAuthentication) { AccessManager accessManager = AdminAccess.getAccessManager(); if (accessManager == null) { slamdServer.logMessage(Constants.LOG_LEVEL_ADMIN, "Could not enable client authentication " + "-- access control is not enabled in the " + "admin interface."); requireAuthentication = false; } } } } else if (parameterName.equalsIgnoreCase(Constants.PARAM_LISTENER_USE_SSL)) { String sslStr = configDB.getConfigParameter(Constants.PARAM_LISTENER_USE_SSL); if ((sslStr != null) && (sslStr.length() > 0)) { useSSL = sslStr.equals(Constants.CONFIG_VALUE_TRUE); if (useSSL) { String keyStore = slamdServer.getSSLKeyStore(); if ((keyStore != null) && (keyStore.length() > 0)) { System.setProperty(Constants.SSL_KEY_STORE_PROPERTY, keyStore); } String keyStorePassword = slamdServer.getSSLKeyStorePassword(); if ((keyStorePassword != null) && (keyStorePassword.length() > 0)) { System.setProperty(Constants.SSL_KEY_PASSWORD_PROPERTY, keyStorePassword); } String trustStore = slamdServer.getSSLTrustStore(); if ((trustStore != null) && (trustStore.length() > 0)) { System.setProperty(Constants.SSL_TRUST_STORE_PROPERTY, trustStore); } String trustStorePassword = slamdServer.getSSLTrustStorePassword(); if ((trustStorePassword != null) && (trustStorePassword.length() > 0)) { System.setProperty(Constants.SSL_TRUST_PASSWORD_PROPERTY, trustStorePassword); } } } } } }