package org.corfudb.infrastructure;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.corfudb.protocols.wireprotocol.CorfuMsg;
import org.corfudb.protocols.wireprotocol.CorfuMsgType;
import org.corfudb.protocols.wireprotocol.CorfuPayloadMsg;
import org.corfudb.runtime.clients.TestChannelContext;
import org.corfudb.runtime.clients.TestRule;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Created by mwei on 12/13/15.
*/
@Slf4j
public class TestServerRouter implements IServerRouter {
@Getter
public List<CorfuMsg> responseMessages;
@Getter
public Map<CorfuMsgType, AbstractServer> handlerMap;
public List<TestRule> rules;
AtomicLong requestCounter;
@Getter
@Setter
long serverEpoch;
@Getter
int port = 0;
public TestServerRouter() {
reset();
}
public TestServerRouter(int port) {
reset();
this.port = port;
}
public void reset() {
this.responseMessages = new ArrayList<>();
this.requestCounter = new AtomicLong();
this.handlerMap = new ConcurrentHashMap<>();
this.rules = new ArrayList<>();
}
@Override
public void sendResponse(ChannelHandlerContext ctx, CorfuMsg inMsg, CorfuMsg outMsg) {
outMsg.copyBaseFields(inMsg);
outMsg.setEpoch(getServerEpoch());
if (rules.stream()
.map(x -> x.evaluate(outMsg, this))
.allMatch(x -> x)) {
if (ctx != null && ctx instanceof TestChannelContext) {
ctx.writeAndFlush(outMsg);
} else {
this.responseMessages.add(outMsg);
}
}
}
/**
* Register a server to route messages to
*
* @param server The server to route messages to
*/
@Override
public void addServer(AbstractServer server) {
server.getHandler()
.getHandledTypes().forEach(x -> {
handlerMap.put(x, server);
log.trace("Registered {} to handle messages of type {}", server, x);
});
}
/**
* Validate the epoch of a CorfuMsg, and send a WRONG_EPOCH response if
* the server is in the wrong epoch. Ignored if the message type is reset (which
* is valid in any epoch).
*
* @param msg The incoming message to validate.
* @param ctx The context of the channel handler.
* @return True, if the epoch is correct, but false otherwise.
*/
public boolean validateEpoch(CorfuMsg msg, ChannelHandlerContext ctx) {
if (!msg.getMsgType().ignoreEpoch && msg.getEpoch() != serverEpoch) {
sendResponse(ctx, msg, new CorfuPayloadMsg<>(CorfuMsgType.WRONG_EPOCH,
getServerEpoch()));
log.trace("Incoming message with wrong epoch, got {}, expected {}, message was: {}",
msg.getEpoch(), serverEpoch, msg);
return false;
}
return true;
}
public void sendServerMessage(CorfuMsg msg) {
AbstractServer as = handlerMap.get(msg.getMsgType());
if (validateEpoch(msg, null)) {
if (as != null) {
as.handleMessage(msg, null, this);
} else {
log.trace("Unregistered message of type {} sent to router", msg.getMsgType());
}
} else {
log.trace("Message with wrong epoch {}, expected {}", msg.getEpoch(), serverEpoch);
}
}
public void sendServerMessage(CorfuMsg msg, ChannelHandlerContext ctx) {
AbstractServer as = handlerMap.get(msg.getMsgType());
if (validateEpoch(msg, ctx)) {
if (as != null) {
as.handleMessage(msg, ctx, this);
}
else {
log.trace("Unregistered message of type {} sent to router", msg.getMsgType());
}
} else {
log.trace("Message with wrong epoch {}, expected {}", msg.getEpoch(), serverEpoch);
}
}
/**
* This simulates the serialization and deserialization that happens in the Netty pipeline for all messages
* from server to client.
*
* @param message
* @return
*/
public CorfuMsg simulateSerialization(CorfuMsg message) {
/* simulate serialization/deserialization */
ByteBuf oBuf = Unpooled.buffer();
//Class<? extends CorfuMsg> type = message.getMsgType().messageType;
//extra assert needed to simulate real Netty behavior
//assertThat(message.getClass().getSimpleName()).isEqualTo(type.getSimpleName());
//type.cast(message).serialize(oBuf);
message.serialize(oBuf);
oBuf.resetReaderIndex();
CorfuMsg msgOut = CorfuMsg.deserialize(oBuf);
oBuf.release();
return msgOut;
}
}