package org.radargun;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.UUID;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.utils.ArgsHolder;
/**
* Abstracts connection to the master node from slave side.
*
* @author Radim Vansa <rvansa@redhat.com>
*/
public class RemoteMasterConnection {
private static Log log = LogFactory.getLog(RemoteMasterConnection.class);
private String masterHost;
private int masterPort;
private SocketChannel socketChannel;
private ByteBuffer buffer;
public RemoteMasterConnection(String masterHost, int masterPort) {
this.masterHost = masterHost;
this.masterPort = masterPort;
int byteBufferSize = 8192;
try {
byteBufferSize = Integer.valueOf(System.getProperty("slave.bufsize", "8192"));
} catch (Exception e) {
log.error("Couldn't parse byte buffer size, keeping default", e);
}
this.buffer = ByteBuffer.allocate(byteBufferSize);
}
/**
* Connects to the master node, sending requested slave ID.
*
* @param slaveIndex
* @return Local address of the slave.
* @throws IOException
*/
public InetAddress connectToMaster(int slaveIndex) throws IOException {
InetSocketAddress socketAddress = new InetSocketAddress(masterHost, masterPort);
log.info("Attempting to connect to master " + masterHost + ":" + masterPort);
for (int i = 0; ; ++i) {
try {
socketChannel = SocketChannel.open();
socketChannel.connect(socketAddress);
break;
} catch (IOException e) {
log.trace("Connect attempt " + i + " failed", e);
if (i >= 10) {
throw e;
}
try {
Thread.sleep(2000);
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
log.warn("Slave thread interrupted", interruptedException);
}
}
}
log.info("Successfully established connection with master at: " + masterHost + ":" + masterPort);
buffer.clear();
buffer.putInt(slaveIndex);
UUID uuid = ArgsHolder.getUuid();
if (uuid == null) {
buffer.putLong(0);
buffer.putLong(0);
} else {
buffer.putLong(uuid.getMostSignificantBits());
buffer.putLong(uuid.getLeastSignificantBits());
}
buffer.flip();
while (buffer.hasRemaining()) socketChannel.write(buffer);
return socketChannel.socket().getLocalAddress();
}
/**
* Receives final slave ID. Should be called after successful connectToMaster() call.
* @return
* @throws IOException
*/
public int receiveSlaveIndex() throws IOException {
return readInt();
}
/**
* Receives total number of connected slaves. Should be called after receiveSlaveIndex().
* @return
* @throws IOException
*/
public int receiveSlaveCount() throws IOException {
return readInt();
}
/**
* Receive ID of stage that should be now executed. List of stage IDs and configurations
* was already received as Scenario object.
* @return
* @throws IOException
*/
public int receiveNextStageId() throws IOException {
return readInt();
}
private int readInt() throws IOException {
buffer.clear();
buffer.limit(4);
while (buffer.hasRemaining()) {
int read = socketChannel.read(buffer);
if (read < 0) {
throw new IOException("Cannot read from socket!");
}
}
buffer.flip();
return buffer.getInt();
}
/**
* Receive any (serializable) object from the master node.
* @return
* @throws IOException
*/
public Object receiveObject() throws IOException {
// we must expect that more than one object is sent, so read only the first one
int objectSize = readInt();
log.trace("Expecting object with size " + objectSize);
if (objectSize == 0) return null;
if (objectSize > buffer.capacity()) {
buffer = ByteBuffer.allocate(objectSize);
} else {
buffer.clear();
}
buffer.limit(objectSize);
while (buffer.hasRemaining()) {
int read = socketChannel.read(buffer);
log.trace("Read " + read + " bytes");
if (read < 0) {
throw new IOException("Cannot read from socket!");
}
}
return SerializationHelper.deserialize(buffer.array(), 0, objectSize);
}
/**
* Send any serializable object to the master node.
* @param obj
* @param nextUuid UUID of the next generation of slaves, or null if this slave will continue
* @throws IOException
*/
public void sendObject(Serializable obj, UUID nextUuid) throws IOException {
buffer.clear();
buffer = SerializationHelper.serializeObjectWithLength(obj, buffer);
if (nextUuid == null) {
buffer = SerializationHelper.appendLong(0, buffer);
buffer = SerializationHelper.appendLong(0, buffer);
} else {
buffer = SerializationHelper.appendLong(nextUuid.getMostSignificantBits(), buffer);
buffer = SerializationHelper.appendLong(nextUuid.getLeastSignificantBits(), buffer);
}
log.trace("Sending a message to the master, message has " + buffer.position() + " bytes.");
buffer.flip();
while (buffer.hasRemaining()) socketChannel.write(buffer);
log.info("Message successfully sent to the master");
}
public void release() throws IOException {
socketChannel.close();
socketChannel = null;
}
}