package net.johnewart.gearman.client; import net.johnewart.gearman.common.JobStatus; import net.johnewart.gearman.common.client.AbstractGearmanClient; import net.johnewart.gearman.common.packets.Packet; import net.johnewart.gearman.common.packets.request.GetStatus; import net.johnewart.gearman.common.packets.request.SubmitJob; import net.johnewart.gearman.common.packets.response.*; import net.johnewart.gearman.constants.JobPriority; import net.johnewart.gearman.constants.PacketType; import net.johnewart.gearman.exceptions.*; import net.johnewart.gearman.net.Connection; import net.johnewart.gearman.net.ConnectionPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Date; import java.util.UUID; import static java.lang.String.format; public class NetworkGearmanClient extends AbstractGearmanClient { // A list of managers to cycle through private final Logger LOG = LoggerFactory.getLogger(NetworkGearmanClient.class); private final ConnectionPool connectionPool; public NetworkGearmanClient() { connectionPool = new ConnectionPool(); } public NetworkGearmanClient(String host, int port) throws IOException { this(); connectionPool.addHostPort(host, port); } public NetworkGearmanClient(String host) throws IOException { this(host, 4730); } public NetworkGearmanClient(Connection conn) { this(); connectionPool.addConnection(conn); } public void shutdown() { LOG.debug("Client shutting down..."); connectionPool.shutdown(); } public void addConnection(Connection conn) { connectionPool.addConnection(conn); } public void addHostToList(String host, int port) throws IOException { connectionPool.addHostPort(host, port); } public void addHostToList(String host) throws IOException { this.addHostToList(host, 4730); } public void close() { connectionPool.cleanup(); } public byte[] submitJob(String callback, byte[] data, JobPriority priority) throws NoServersAvailableException, WorkException { Packet result = null; try { String jobid = UUID.randomUUID().toString(); ServerResponse response = sendJobPacket(new SubmitJob(callback, jobid, data, false, priority)); if(response != null) { // This method handles synchronous requests, so we wait // until we get a work complete packet while(true) { result = response.getConnection().getNextPacket(); switch(result.getType()) { // WORK_COMPLETE -> return the data in the response, all others // are handled by the event listeners. case WORK_COMPLETE: WorkCompleteResponse wc = (WorkCompleteResponse) result; LOG.debug("Completed job " + wc.getJobHandle()); return wc.data; case WORK_EXCEPTION: WorkExceptionResponse wr = (WorkExceptionResponse) result; LOG.debug("Exception for job " + wr.getJobHandle()); throw new WorkExceptionException(wr.getJobHandle(), wr.getException()); case WORK_FAIL: WorkFailResponse wf = (WorkFailResponse) result; LOG.debug("Job " + wf.getJobHandle() + " failed."); throw new WorkFailException(wf.getJobHandle()); case WORK_STATUS: WorkStatus ws = (WorkStatus) result; LOG.debug("Status data for job " + ws.getJobHandle()); handleWorkStatus(ws.getJobHandle(), ws.toJobStatus()); break; case WORK_DATA: WorkDataResponse wd = (WorkDataResponse) result; LOG.debug("Received data update for job " + wd.getJobHandle()); handleWorkData(wd.getJobHandle(), wd.getData()); break; case WORK_WARNING: WorkWarningResponse ww = (WorkWarningResponse) result; LOG.debug("Received warning for job " + ww.getJobHandle()); handleWorkWarning(ww.getJobHandle(), ww.getData()); break; default: LOG.info("Unexpected message: " + result.getType()); break; } } } else { LOG.warn("Unable to submit job to job severs..."); } } catch (IOException e) { LOG.error("Error submitting job: ", e); } catch (NoServersAvailableException nsae) { LOG.error("No servers available to submit the job."); } return null; } @Override public String submitFutureJob(String callback, byte[] data, Date whenToRun) throws NoServersAvailableException { String uniqueID = UUID.randomUUID().toString(); try { ServerResponse response = sendJobPacket(new SubmitJob(callback, uniqueID, data, whenToRun)); if(response != null) { LOG.debug("Sent future job request to " + response.getConnection()); // If we get back a JOB_CREATED packet, we can continue, // otherwise try the next job manager if (response.getPacket().getType() == PacketType.JOB_CREATED) { String jobHandle = ((JobCreated)response.getPacket()).getJobHandle(); LOG.debug("Created future job %s\n", jobHandle); return jobHandle; } } } catch (NoServersAvailableException nsae) { LOG.warn("No servers available to submit the job."); throw nsae; } return null; } @Override public String submitJobInBackground(String callback, byte[] data) throws JobSubmissionException { return submitJobInBackground(callback, data, JobPriority.NORMAL); } public String submitJobInBackground(String callback, byte[] data, JobPriority priority) throws NoServersAvailableException { String jobid = UUID.randomUUID().toString(); try { ServerResponse response = sendJobPacket(new SubmitJob(callback, jobid, data, true, priority)); if(response != null) { LOG.debug("Sent background job request to " + response.getConnection()); // If we get back a JOB_CREATED packet, we can continue, // otherwise try the next job manager if (response.getPacket().getType() == PacketType.JOB_CREATED) { String jobHandle = ((JobCreated)response.getPacket()).getJobHandle(); LOG.debug(format("Created background job %s, with priority %s\n", jobHandle, priority.toString())); return jobHandle; } } } catch (NoServersAvailableException nsae) { LOG.warn("No servers available to submit the job."); throw nsae; } return null; } @Override public byte[] submitJob(String callback, byte[] data) throws JobSubmissionException, WorkException { return submitJob(callback, data, JobPriority.NORMAL); } // TODO: Implement a percentage done feedback in the future? public JobStatus getStatus(String jobHandle) { GetStatus statusPkt = new GetStatus(jobHandle); Packet result; for (Connection conn : connectionPool.getGoodConnectionList()) { LOG.debug("Checking for status on %s on %s\n", jobHandle, conn); try { conn.sendPacket(statusPkt); result = conn.getNextPacket(); if(result.getType() == PacketType.STATUS_RES) { StatusRes statusResult = (StatusRes)result; if(statusResult.getJobHandle().equals(jobHandle)) { return statusResult.toJobStatus(); } } } catch (IOException ioe) { // Do nothing, we don't really care much here. LOG.error("Unable to send request to " + conn + ": " + ioe); } } return null; } private ServerResponse sendJobPacket(SubmitJob jobPacket) throws NoServersAvailableException { Packet result; Connection connection; while ( (connection = connectionPool.getConnection()) != null) { try { connection.sendPacket(jobPacket); LOG.debug("Sent job request to " + connection.toString()); // We need to get back a JOB_CREATED packet result = connection.getNextPacket(); // If we get back a JOB_CREATED packet, we can continue // otherwise try the next job manager if (result != null && result.getType() == PacketType.JOB_CREATED) { LOG.debug("Created job " + ((JobCreated) result).getJobHandle()); return new ServerResponse(connection, result); } } catch (IOException ioe) { LOG.error("Connection to " + connection.toString() + " flaky, marking as bad."); } } // The only way we get here is if connection == null, by which point we would have been // thrown a NoServersAvailableException() throw new NoServersAvailableException(); } class ServerResponse { private Connection connection; private Packet packet; public ServerResponse(Connection connection, Packet packet) { this.connection = connection; this.packet = packet; } public Connection getConnection() { return connection; } public Packet getPacket() { return packet; } } }