/* * 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.io.InterruptedIOException; import java.net.Socket; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import com.slamd.admin.AccessManager; import com.slamd.admin.AdminAccess; import com.slamd.asn1.ASN1Element; import com.slamd.asn1.ASN1Exception; import com.slamd.asn1.ASN1Reader; import com.slamd.asn1.ASN1Writer; import com.slamd.common.Constants; import com.slamd.common.SLAMDException; import com.slamd.job.Job; import com.slamd.job.JobClass; import com.slamd.message.ClientHelloMessage; import com.slamd.message.HelloResponseMessage; import com.slamd.message.JobCompletedMessage; import com.slamd.message.JobControlRequestMessage; import com.slamd.message.JobControlResponseMessage; import com.slamd.message.JobRequestMessage; import com.slamd.message.JobResponseMessage; import com.slamd.message.KeepAliveMessage; import com.slamd.message.Message; import com.slamd.message.ServerShutdownMessage; import com.slamd.message.StatusRequestMessage; import com.slamd.message.StatusResponseMessage; /** * This class defines a thread that is spawned by the server to handle each * resource monitor client connection. It takes care of reading messages in * from the client and provides methods for sending messages to the client. * * * @author Neil A. Wilson */ public class ResourceMonitorClientConnection extends Thread implements Comparable { // The list used to hold responses to solicited messages private ArrayList<Message> messageList; // The reader used to read ASN.1 elements from the client. private ASN1Reader reader; // The writer used to write ASN.1 elements to the client. private ASN1Writer writer; // Indicates whether this connection should keep listening for new messages // from the client. private boolean keepListening; // Indicates whether this client supports time synchronization. private boolean supportsTimeSync; // The client listener that accepted this connection. private ResourceMonitorClientListener clientListener; // The time that this connection was established. private Date establishedTime; // The set of jobs currently being processed by this monitor client. private HashMap<String,Job> jobHash; // The type of authentication performed by the client. private int authType; // The length of time in seconds between keepalive messages private int keepAliveTime; // The maximum length of time in seconds that the getResponse method will wait // for a matching message to show up in the receive queue before returning // null private int maxResponseWaitTime; // The next message ID that will be used in a message originated by the server private int messageID; // A mutex used to provide threadsafe access to the job hash. private final Object jobHashMutex; // A mutex used to provide threadsafe access to the message list private final Object messageListMutex; // The SLAMD server with which this client connection is associated private SLAMDServer slamdServer; // The network socket used to communicate with the client private Socket socket; // The authentication ID provided by the client. private String authID; // The authentication credentials provided by the client. private String authCredentials; // The client ID that the client has assigned to itself. private String clientID; // The IP address of the client. private String clientIPAddress; // The unique ID assigned by the server that identifies this connection to the // server. private String connectionID; /** * Creates a new resource monitor connection that the server will use to * communicate with the client. * * @param slamdServer The SLAMD server with which this connection is * associated. * @param clientListener The resource monitor client listener that accepted * this connection. * @param socket The socket that is used to communicate with the * client. * @param connectionID The unique identifier associated with the client. * * @throws SLAMDException If a problem occurs setting up the connection. */ public ResourceMonitorClientConnection(SLAMDServer slamdServer, ResourceMonitorClientListener clientListener, Socket socket, String connectionID) throws SLAMDException { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ResourceMonitorClientConnection constructor"); setName("Resource Monitor Client Connection " + connectionID); this.slamdServer = slamdServer; this.clientListener = clientListener; this.socket = socket; this.clientID = "(unknown)"; this.clientIPAddress = socket.getInetAddress().getHostAddress(); this.connectionID = connectionID; this.supportsTimeSync = false; this.establishedTime = new Date(); messageList = new ArrayList<Message>(); messageListMutex = new Object(); messageID = 1; keepAliveTime = slamdServer.getClientListener().getKeepAliveInterval(); jobHash = new HashMap<String,Job>(); jobHashMutex = new Object(); maxResponseWaitTime = slamdServer.getClientListener().getMaxResponseWaitTime(); // Send the hello response to the client try { reader = new ASN1Reader(socket.getInputStream()); writer = new ASN1Writer(socket.getOutputStream()); ClientHelloMessage helloRequest; String respMesg = ""; try { ASN1Element element = reader.readElement(Constants.MAX_BLOCKING_READ_TIME); Message message = Message.decode(element); if (message instanceof ClientHelloMessage) { helloRequest = (ClientHelloMessage) message; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Received hello request from resource " + "monitor client " + toString()); this.clientID = helloRequest.getClientID(); this.authID = helloRequest.getAuthID(); this.authCredentials = helloRequest.getAuthCredentials(); this.authType = helloRequest.getAuthType(); this.supportsTimeSync = helloRequest.supportsTimeSync(); if ((authID == null) || (authID.length() == 0) || (authCredentials == null) || (authCredentials.length() == 0)) { if (clientListener.requireAuthentication()) { String msg = "Authentication required but client did not " + "provide sufficient authentication data."; HelloResponseMessage helloResp = new HelloResponseMessage(0, Constants.MESSAGE_RESPONSE_SERVER_ERROR, msg, -1); writer.writeElement(helloResp.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT, "Rejected new monitor client connection " + clientID + " -- " + msg); try { socket.close(); } catch (Exception e) {} throw new SLAMDException(msg); } } else { if (authType != Constants.AUTH_TYPE_SIMPLE) { throw new SLAMDException("Invalid authentication type " + authType); } AccessManager accessManager = AdminAccess.getAccessManager(); if (accessManager == null) { String msg = "The SLAMD server is not properly configured " + "to perform authentication."; HelloResponseMessage helloResp = new HelloResponseMessage(0, Constants.MESSAGE_RESPONSE_SERVER_ERROR, msg, -1); writer.writeElement(helloResp.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT, "Rejected new monitor client connection " + clientID + " -- " + msg); try { socket.close(); } catch (Exception e) {} throw new SLAMDException(msg); } StringBuilder msgBuffer = new StringBuilder(); int resultCode = accessManager.authenticateClient(authID, authCredentials, msgBuffer); if (resultCode == Constants.MESSAGE_RESPONSE_SUCCESS) { respMesg = "Successfully authenticated as " + authID; } else { String msg = msgBuffer.toString(); HelloResponseMessage helloResp = new HelloResponseMessage(0, resultCode, msg, -1); writer.writeElement(helloResp.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT, "Rejected new monitor client connection " + clientID + " -- " + msg); try { socket.close(); } catch (Exception e) {} throw new SLAMDException(msg); } } } else { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Expected hello request from client but got " + "instance of " + message.getClass().getName()); throw new SLAMDException("Expected hello request from client but " + "got instance of " + message.getClass().getName()); } } catch (ASN1Exception ae) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Unable to parse hello request message from " + toString() + ": " + ae); ae.printStackTrace(); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ae)); throw new SLAMDException("Unable to parse hello request message from " + toString() + ": " + ae, ae); } catch (SLAMDException se) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Unable to obtain hello request message from " + toString() + ": " + se); se.printStackTrace(); throw se; } // Make sure we don't already have a resource monitor connection from the // same client system. ResourceMonitorClientConnection[] conns = clientListener.getMonitorClientList(); for (int i=0; i < conns.length; i++) { if (conns[i].getClientID().equals(clientID)) { try { HelloResponseMessage helloResponse = new HelloResponseMessage(helloRequest.getMessageID(), Constants.MESSAGE_RESPONSE_CLIENT_REJECTED, "A resource monitor client connection has already " + "been established with client ID \"" + clientID + "\".", -1); writer.writeElement(helloResponse.encode()); socket.close(); } catch (IOException ioe) { try { socket.close(); } catch (Exception e) {} slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); throw new SLAMDException("Unable to send the hello response to " + "the resource monitor client: " + ioe, ioe); } throw new SLAMDException("Rejected resource monitor client " + "connection due to duplicate client ID -- " + clientID + '.'); } } long serverTime = (supportsTimeSync ? System.currentTimeMillis() : -1); HelloResponseMessage helloResp = new HelloResponseMessage(0, Constants.MESSAGE_RESPONSE_SUCCESS, respMesg, serverTime); writer.writeElement(helloResp.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT, "Accepted new resource monitor client " + "connection " + clientID + " from " + socket.getInetAddress().toString()); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "I/O exception in client handshake for " + toString() + ": " + ioe); ioe.printStackTrace(); } slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "Leaving ResourceMonitorClientConnection " + "constructor"); } /** * Retrieves the connection ID assigned to this client connection by the * SLAMD server. * * @return The connection ID assigned to this client connection by the * SLAMD server. */ public String getConnectionID() { return connectionID; } /** * Retrieves the client ID that the client has chosen for itself. * * @return The client ID that the client has chosen for itself. */ public String getClientID() { return clientID; } /** * Retrieves the time at which this connection was established. * * @return The time at which this connection was established. */ public Date getEstablishedTime() { return establishedTime; } /** * Retrieves the IP address of the client associated with this connection. * * @return The IP address of the client associated with this connection. */ public String getClientIPAddress() { return clientIPAddress; } /** * Indicates whether at least one job is in progress. * * @return <CODE>true</CODE> if at least one job is in progress, or * <CODE>false</CODE> if there are no jobs in progress. */ public boolean jobInProgress() { synchronized (jobHashMutex) { return (! jobHash.isEmpty()); } } /** * Retrieves the list of jobs for which this client is collecting resource * utilization data. * * @return The list of jobs for which this client is collecting resource * utilization data. */ public Job[] getJobsInProgress() { synchronized (jobHashMutex) { Job[] jobs = new Job[jobHash.size()]; int i=0; Iterator iterator = jobHash.values().iterator(); while (iterator.hasNext()) { jobs[i++] = (Job) iterator.next(); } return jobs; } } /** * Retrieves the list of jobs for which this client is collecting resource * utilization data. * * @return The list of jobs for which this client is collecting resource * utilization data. */ public String[] getJobIDsInProgress() { synchronized (jobHashMutex) { String[] jobIDs = new String[jobHash.size()]; int i=0; Iterator iterator = jobHash.values().iterator(); while (iterator.hasNext()) { jobIDs[i++] = ((Job) iterator.next()).getJobID(); } return jobIDs; } } /** * Retrieves information about the specified job in progress. * * @param jobID The job ID of the job for which to retrieve information. * * @return Information about the specified job in progress, or * <CODE>null</CODE> if no information is available. */ public Job getJobInProgress(String jobID) { synchronized (jobHashMutex) { return jobHash.get(jobID); } } /** * Adds the provided job to the list of jobs in progress. * * @param job The job to add to the list of jobs in progress. */ public void addJobInProgress(Job job) { synchronized (jobHashMutex) { jobHash.put(job.getJobID(), job); } } /** * Removes the provided job from the list of jobs in progress. * * @param job The job to remove from the list of jobs in progress. */ public void removeJobInProgress(Job job) { synchronized (jobHashMutex) { jobHash.remove(job.getJobID()); } } /** * Listens for messages from the client and either handles them or hands them * off to be handled elsewhere. */ @Override() public void run() { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ResourceMonitorClientConnection.run() for " + clientID); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ResourceMonitorClientConnection.run() for " + clientID); keepListening = true; // First, set a timeout on the socket. This will allow us to interrupt the // reads periodically to send keepalive messages. if (keepAliveTime > 0) { try { socket.setSoTimeout(keepAliveTime*1000); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Set socket timeout of " + keepAliveTime + " seconds for monitor client " + clientID); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Could not set timeout for monitor client " + "connection " + clientID); } } // This flag will be used to detect two consecutive failures (indicates // that the connection has been closed without the client notifying us) boolean consecutiveFailures = false; // Loop infinitely (or at least until the client shuts down) and read // messages from the client while (keepListening) { try { ASN1Element element = reader.readElement(); if (element == null) { // This should only happen if the client has closed the connection. slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT, "Detected connection closure from monitor " + "client " + clientID); try { socket.close(); } catch (IOException ioe2) {} clientListener.connectionLost(this); return; } slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Read a message from monitor client " + clientID); Message message = Message.decode(element); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Decoded message from monitor client " + clientID + ": " + message.toString()); // We need to be able to handle two kinds of messages: solicited and // unsolicited. Solicited messages are those that the client provides // in response to a request from the server. Unsolicited messages are // those that the client provides without a request from the server // (primarily job complete messages and status response messages that // indicate the client is shutting down). This method will handle the // unsolicited messages, but the solicited messages will be placed into // a queue to be picked up by the method that issued the request. It is // possible to tell the difference between solicited and unsolicited // messages because messages that are in response to a request from the // server (solicited messages) will have an odd message ID and messages // that originate from the client (unsolicited messages) will have an // even message ID. if ((message.getMessageID() % 2) != 0) { // This is a solicited message, so add it into the queue to be // picked up by something else synchronized (messageListMutex) { messageList.add(message); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Queueing solicited response from " + "monitor client " + clientID); } } else if (message instanceof JobCompletedMessage) { // This is a job completed message, so update the scheduler that // the job is done JobCompletedMessage msg = (JobCompletedMessage) message; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Job completed response from monitor client " + clientID); Job job = getJobInProgress(msg.getJobID()); if (job != null) { job.resourceClientDone(this, msg); removeJobInProgress(job); } } else if (message instanceof StatusResponseMessage) { // This is a status response message, but not a solicited one. It // almost certainly means the client is shutting down StatusResponseMessage msg = (StatusResponseMessage) message; slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Status response from monitor client " + clientID); if (msg.getClientStatusCode() == Constants.CLIENT_STATE_SHUTTING_DOWN) { clientListener.connectionLost(this); try { socket.close(); } catch (IOException ioe) {} return; } } } catch (InterruptedIOException iioe) { // If this exception was thrown, it was because the socket timeout was // reached. We need to send a keepalive message. try { KeepAliveMessage kaMsg = new KeepAliveMessage(getMessageID()); writer.writeElement(kaMsg.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Sent keepalive to monitor client " + clientID); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Unable to send keepalive to monitor " + clientID + ": " + ioe); } } catch (IOException ioe) { // Some other I/O related exception was thrown slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "I/O exception from monitor " + clientID + ": " + ioe); // Some problem occurred. See if this is a second consecutive failure // and if so, end the connection and this thread. Otherwise set a // flag that will be used to detect a second consecutive failure if (consecutiveFailures) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Consecutive failures on connection to " + "monitor client " + clientID + " -- closing"); try { socket.close(); } catch (IOException ioe2) {} clientListener.connectionLost(this); return; } else { consecutiveFailures = true; } } catch (SLAMDException se) { // The specified class could not be found slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Exception handling message from monitor " + "client " + clientID + ": " + se); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(se)); se.printStackTrace(); } catch (ASN1Exception ae) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Exception decoding message from monitor " + "client " + connectionID + ": " + ae); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ae)); ae.printStackTrace(); try { socket.close(); } catch (Exception e) {} clientListener.connectionLost(this); return; } catch (Exception e) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Exception in monitor client processing for client " + connectionID + ": " + e); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(e)); e.printStackTrace(); try { socket.close(); } catch (Exception e2) {} clientListener.connectionLost(this); return; } } slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "Leaving ResourceMonitorClientConnection.run() " + "for monitor client " + clientID); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Leaving ResourceMonitorClientConnection.run() " + "for monitor client " + clientID); } /** * Sends the specified message to the client. * * @param message The message to send to the client. */ public void sendMessage(Message message) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ResourceMonitorClientConnection.sendMessage() " + "for monitor client " + clientID); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ResourceMonitorClientConnection.sendMessage() " + "for monitor client " + clientID); try { writer.writeElement(message.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Wrote message to monitor client " + clientID + " -- " + message.toString()); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Could not write message type " + message.getMessageType() + " to monitor client " + clientID + ": " + ioe); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); ioe.printStackTrace(); } } /** * Retrieves the message ID to use in the next message originating from the * server. * * @return The message ID to use in the next message originating from the * server. */ public synchronized int getMessageID() { int returnValue = messageID; messageID += 2; return returnValue; } /** * Retrieves a message with the specified message ID and type from the queue * used to hold responses to solicited messages. This will wait for a * configurable maximum amount of time for the message to appear before * returning <CODE>null</CODE>. * * @param messageID The message ID for the message that is expected. * @param messageType The type of message that is expected. * * @return The message with the specified message ID and type from the queue * used to hold responses to solicited messages. */ public Message getResponse(int messageID, int messageType) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ResourceMonitorClientConnection.getResponse(" + messageID + ", " + messageType + ") for " + clientID); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ResourceMonitorClientConnection.getResponse(" + messageID + ", " + messageType + ") for " + clientID); long stopWaitingTime = System.currentTimeMillis() + (maxResponseWaitTime * 1000); while (System.currentTimeMillis() < stopWaitingTime) { synchronized (messageListMutex) { for (int i=0; i < messageList.size(); i++) { Message message = messageList.get(i); int msgID = message.getMessageID(); int msgType = message.getMessageType(); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Monitor client connection for " + clientID + " looking at message " + msgID + " of type " + msgType + " -- " + message.toString()); if (msgID == messageID) { if (msgType == messageType) { messageList.remove(i); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Returning requested message " + messageID + " from monitor client " + clientID); return message; } else { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT, "WARNING: Message ID " + msgID + " was of the wrong type (expected " + messageType + ", got " + msgType + ") for monitor client connection " + clientID); } } else { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Ignoring message of type " + msgType + " sent to monitor client " + clientID + " with an unexpected message ID (expected " + messageID + ", got " + msgID + ')'); } } } // The requested message wasn't found, so wait a short period of time // before checking again if (System.currentTimeMillis() < stopWaitingTime) { try { Thread.sleep(Constants.THREAD_BLOCK_SLEEP_TIME); } catch (InterruptedException ie) {} } } slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Timeout while waiting for message " + messageID + "from monitor client " + clientID); return null; } /** * Sends a job request message to the client. * * @param job The information that should be included in the job * request. * @param clientNumber The client number for this client. * * @return The response from the client. */ public JobResponseMessage sendJobRequest(Job job, int clientNumber) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ResourceMonitorClientConnection." + "sendJobRequest(" + job.getJobID() + ") for monitor client " + clientID); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ResourceMonitorClientConnection." + "sendJobRequest(" + job.getJobID() + ") for monitor client " + clientID); addJobInProgress(job); int messageID = getMessageID(); String jobID = job.getJobID(); JobRequestMessage request = new JobRequestMessage(messageID, jobID, job.getJobClassName(), job.getStartTime(), job.getStopTime(), clientNumber, job.getDuration(), job.getThreadsPerClient(), job.getThreadStartupDelay(), job.getCollectionInterval(), job.getParameterList()); try { writer.writeElement(request.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Sent job request to monitor client " + clientID + " -- " + request.toString()); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Could not send job request to monitor client " + clientID + ": " + ioe); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); ioe.printStackTrace(); return new JobResponseMessage(messageID, jobID, Constants.MESSAGE_RESPONSE_LOCAL_ERROR); } Message response = getResponse(messageID, Constants.MESSAGE_TYPE_JOB_RESPONSE); if (response == null) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "No response to job request from monitor client " + clientID); return new JobResponseMessage(messageID, jobID, Constants.MESSAGE_RESPONSE_NO_RESPONSE); } else { // Make sure that the job request was accepted. If not, then set the // job in progress to null. JobResponseMessage jobResponse = (JobResponseMessage) response; if (jobResponse.getResponseCode() != Constants.MESSAGE_RESPONSE_SUCCESS) { removeJobInProgress(job); } return jobResponse; } } /** * Sends a job control request to the client for the specified job. * * @param job The job with which the request is associated. * @param controlType The type of operation being requested. * * @return The response from the client. */ public JobControlResponseMessage sendJobControlRequest(Job job, int controlType) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ResourceMonitorClientConnection." + "sendJobControlRequest(" + job.getJobID() + ", " + controlType + ") for monitor client " + clientID); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ResourceMonitorClientConnection." + "sendJobControlRequest(" + job.getJobID() + ", " + controlType + ") for monitor client " + clientID); // First, make sure that the job requested is the one that we know about. // If not, return a failure message on behalf of the client String jobID = job.getJobID(); if (getJobInProgress(jobID) == null) { JobControlResponseMessage response = new JobControlResponseMessage(getMessageID(), job.getJobID(), Constants.MESSAGE_RESPONSE_NO_SUCH_JOB, "Job " + job.getJobID() + " has not been defined to this client"); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Job not known to monitor client " + clientID); return response; } int messageID = getMessageID(); JobControlRequestMessage request = new JobControlRequestMessage(messageID, jobID, controlType); try { writer.writeElement(request.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Sent job control request to monitor client " + clientID + " -- " + request.toString()); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Could not send job control request to monitor " + "client " + clientID + ": " + ioe); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); ioe.printStackTrace(); return new JobControlResponseMessage(messageID, jobID, Constants.MESSAGE_RESPONSE_LOCAL_ERROR); } Message response = getResponse(messageID, Constants.MESSAGE_TYPE_JOB_CONTROL_RESPONSE); if (response == null) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "No response to job control request from " + "monitor client " + clientID); return new JobControlResponseMessage(messageID, jobID, Constants.MESSAGE_RESPONSE_NO_RESPONSE); } else { // Make sure that the job request was accepted. If not, then set the // job in progress to null. JobControlResponseMessage jobControlResponse = (JobControlResponseMessage) response; if (jobControlResponse.getResponseCode() == Constants.MESSAGE_RESPONSE_NO_SUCH_JOB) { removeJobInProgress(job); } return jobControlResponse; } } /** * Sends a status request message to the client as a general status request. * This request will not be for job-specific information. * * @return The status response message corresponding to this status request. */ public StatusResponseMessage sendStatusRequestMessage() { return sendStatusRequestMessage(null); } /** * Sends a status request message to the client requesting information about * the specified job. * * @param jobID The ID of the job for which to request status information. * * @return The status response message corresponding to this status request. */ public StatusResponseMessage sendStatusRequestMessage(String jobID) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ResourceMonitorClientConnection." + "sendStatusRequestMessage(" + jobID + ") for " + "monitor client " + clientID); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ResourceMonitorClientConnection." + "sendStatusRequestMessage(" + jobID + ") for " + "monitor client " + clientID); int messageID = getMessageID(); StatusRequestMessage request = null; if ((jobID == null) || (jobID.length() == 0)) { request = new StatusRequestMessage(messageID); } else { request = new StatusRequestMessage(messageID, jobID); } try { writer.writeElement(request.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Sent status request message to monitor client " + clientID + " -- " + request.toString()); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Could not send status request message to " + "monitor client " + clientID + ": " + ioe); ioe.printStackTrace(); return new StatusResponseMessage(messageID, Constants.MESSAGE_RESPONSE_NO_RESPONSE, Constants.CLIENT_STATE_UNKNOWN, "Unable to send status request: " + ioe); } Message response = getResponse(messageID, Constants.MESSAGE_TYPE_STATUS_RESPONSE); if (response == null) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "No response to status request from monitor " + "client " + clientID); return new StatusResponseMessage(messageID, Constants.MESSAGE_RESPONSE_NO_RESPONSE, Constants.CLIENT_STATE_UNKNOWN, "Did not receive status response"); } else { return (StatusResponseMessage) response; } } /** * Sends a message to the client that indicates the server is shutting down, * and then optionally closes the connection to the client. * * @param closeSocket Indicates whether the connection to the client should * be closed. */ public void sendServerShutdownMessage(boolean closeSocket) { slamdServer.logMessage(Constants.LOG_LEVEL_TRACE, "In ResourceMonitorClientConnection." + "sendServerShutdownMessage() for monitor client " + clientID); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "In ResourceMonitorClientConnection." + "sendServerShutdownMessage() for monitor client " + clientID); Job[] jobsInProgress = getJobsInProgress(); for (int i=0; i < jobsInProgress.length; i++) { sendJobControlRequest(jobsInProgress[i], Constants.JOB_CONTROL_TYPE_STOP_DUE_TO_SHUTDOWN); while (jobInProgress()) { try { Thread.sleep(Constants.THREAD_BLOCK_SLEEP_TIME); } catch (InterruptedException ie) {} } } ServerShutdownMessage shutdownMessage = new ServerShutdownMessage(getMessageID()); try { writer.writeElement(shutdownMessage.encode()); slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Sent shutdown message to monitor client " + clientID + " -- " + shutdownMessage.toString()); } catch (IOException ioe) { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Could not send shutdown message to monitor " + "client " + clientID + ": " + ioe); slamdServer.logMessage(Constants.LOG_LEVEL_EXCEPTION_DEBUG, JobClass.stackTraceToString(ioe)); ioe.printStackTrace(); clientListener.connectionLost(this); } if (closeSocket) { keepListening = false; try { slamdServer.logMessage(Constants.LOG_LEVEL_CLIENT_DEBUG, "Closing socket for monitor client " + clientID); socket.close(); } catch (IOException ioe) {} } } /** * Retrieves a string with information about this client connection. * * @return A string with information about this client connection. */ @Override() public String toString() { return connectionID + " (" + clientID + ' ' + clientIPAddress + ')'; } /** * Retrieves a string with basic status information about the current state of * the client. * * @return A string with basic status information about the current state of * the client. */ public String getStatusString() { String[] jobIDs = getJobIDsInProgress(); if ((jobIDs == null) || (jobIDs.length == 0)) { return "Idle"; } else if (jobIDs.length == 1) { return "Processing job " + jobIDs[0]; } else { String returnStr = "Processing job " + jobIDs[0]; for (int i=1; i < jobIDs.length; i++) { returnStr += ", " + jobIDs[i]; } return returnStr; } } /** * Compares this resource monitor client connection with the provided object. * The given object must be a resource monitor client connection object, and * the comparison will be made based on the lexicographic ordering of the * associated client IDs. * * @param o The resource monitor client connection object to compare against * this resource monitor client connection. * * @return A negative value if this resource monitor client connection should * come before the provided resource monitor client connection in a * sorted list, a positive value if this resource monitor client * connection should come after the provided resource monitor client * connection in a sorted list, or zero if there is no difference in * their ordering as far as this method is concerned. * * @throws ClassCastException If the provided object is not a resource * monitor client connection object. */ public int compareTo(Object o) throws ClassCastException { ResourceMonitorClientConnection c = (ResourceMonitorClientConnection) o; return clientID.compareTo(c.clientID); } }