/*
* Copyright (c) 2012-2015 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.spotify.netty4.handler.codec.zmtp;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.After;
import org.junit.Before;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.FromDataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.zeromq.ZMQ;
import org.zeromq.ZMsg;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import io.netty.util.ReferenceCountUtil;
import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP10;
import static com.spotify.netty4.handler.codec.zmtp.ZMTPProtocols.ZMTP20;
import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.DEALER;
import static com.spotify.netty4.handler.codec.zmtp.ZMTPSocketType.ROUTER;
import static io.netty.util.CharsetUtil.UTF_8;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assume.assumeFalse;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
@RunWith(Theories.class)
public class ZMQIntegrationTest {
private static final String ANONYMOUS = "";
private static final String IDENTITY = "zmq-integration-test";
private static final String MIN_IDENTITY = "z";
private static final String MAX_IDENTITY = Strings.repeat("z", 255);
@DataPoints("identities")
public static final String[] IDENTITIES = {IDENTITY, MIN_IDENTITY, MAX_IDENTITY, ANONYMOUS};
@DataPoints("versions")
public static final ZMTPProtocol[] PROTOCOLS = {ZMTP10, ZMTP20};
private final ZMTPSocket.Handler handler =
Mockito.mock(ZMTPSocket.Handler.class);
private final ArgumentCaptor<ZMTPMessage> messageCaptor =
ArgumentCaptor.forClass(ZMTPMessage.class);
private ZMQ.Context context;
private ZMTPSocket zmtpSocket;
private int port;
private ZMQ.Socket zmqSocket;
@Before
public void setUp() {
context = ZMQ.context(1);
}
@After
public void tearDown() {
if (zmtpSocket != null) {
zmtpSocket.close();
}
if (zmqSocket != null) {
zmqSocket.close();
}
if (context != null) {
context.close();
}
}
@Theory
public void test_NettyBindRouter_ZmqConnectDealer(
@FromDataPoints("identities") final String zmqIdentity,
@FromDataPoints("identities") final String nettyIdentity,
@FromDataPoints("versions") final ZMTPProtocol nettyProtocol
)
throws TimeoutException, InterruptedException, ExecutionException {
// XXX (dano): jeromq fails on identities longer than 127 bytes due to a signedness issue
assumeFalse(MAX_IDENTITY.equals(zmqIdentity));
final ZMTPSocket router = nettyBind(ROUTER, nettyIdentity, nettyProtocol);
final ZMQ.Socket dealer = zmqConnect(ZMQ.DEALER, zmqIdentity);
testReqRep(dealer, router, zmqIdentity);
}
@Theory
public void test_NettyBindDealer_ZmqConnectRouter(
@FromDataPoints("identities") final String zmqIdentity,
@FromDataPoints("identities") final String nettyIdentity,
@FromDataPoints("versions") final ZMTPProtocol nettyProtocol
)
throws InterruptedException, TimeoutException, ExecutionException {
// XXX (dano): jeromq fails on identities longer than 127 bytes due to a signedness issue
assumeFalse(MAX_IDENTITY.equals(zmqIdentity));
final ZMTPSocket dealer = nettyBind(DEALER, nettyIdentity, nettyProtocol);
final ZMQ.Socket router = zmqConnect(ZMQ.ROUTER, zmqIdentity);
testReqRep(dealer, router, zmqIdentity);
}
@Theory
public void test_ZmqBindRouter_NettyConnectDealer(
@FromDataPoints("identities") final String zmqIdentity,
@FromDataPoints("identities") final String nettyIdentity,
@FromDataPoints("versions") final ZMTPProtocol nettyProtocol
)
throws InterruptedException, TimeoutException, ExecutionException {
// XXX (dano): jeromq fails on identities longer than 127 bytes due to a signedness issue
assumeFalse(MAX_IDENTITY.equals(zmqIdentity));
final ZMQ.Socket router = zmqBind(ZMQ.ROUTER, zmqIdentity);
final ZMTPSocket dealer = nettyConnect(DEALER, nettyIdentity, nettyProtocol);
testReqRep(dealer, router, zmqIdentity);
}
@Theory
public void test_ZmqBindDealer_NettyConnectRouter(
@FromDataPoints("identities") final String zmqIdentity,
@FromDataPoints("identities") final String nettyIdentity,
@FromDataPoints("versions") final ZMTPProtocol nettyProtocol
)
throws TimeoutException, InterruptedException, ExecutionException {
// XXX (dano): jeromq fails on identities longer than 127 bytes due to a signedness issue
assumeFalse(MAX_IDENTITY.equals(zmqIdentity));
final ZMQ.Socket dealer = zmqBind(ZMQ.DEALER, zmqIdentity);
final ZMTPSocket router = nettyConnect(ROUTER, nettyIdentity, nettyProtocol);
testReqRep(dealer, router, zmqIdentity);
}
private void testReqRep(final ZMQ.Socket req, final ZMTPSocket rep, final String zmqIdentity)
throws InterruptedException, TimeoutException {
// Verify that sockets are connected
verify(handler, timeout(5000)).connected(eq(rep), any(ZMTPSocket.ZMTPPeer.class));
// Verify that the peer identity was correctly received
verifyPeerIdentity(rep, zmqIdentity);
// Send request
final ZMsg request = ZMsg.newStringMsg("envelope", "", "hello", "world");
request.send(req, false);
// Receive request
verify(handler, timeout(5000)).message(
eq(rep), any(ZMTPSocket.ZMTPPeer.class), messageCaptor.capture());
final ZMTPMessage receivedRequest = messageCaptor.getValue();
// Send reply
rep.send(receivedRequest);
// Receive reply
final ZMsg reply = ZMsg.recvMsg(req);
// Verify echo
assertEquals(request, reply);
}
private void testReqRep(final ZMTPSocket req, final ZMQ.Socket rep, final String zmqIdentity)
throws InterruptedException, TimeoutException {
// Verify that sockets are connected
verify(handler, timeout(5000)).connected(eq(req), any(ZMTPSocket.ZMTPPeer.class));
// Verify that the peer identity was correctly received
verifyPeerIdentity(req, zmqIdentity);
// Send request
final ZMTPMessage request = ZMTPMessage.fromUTF8("envelope", "", "hello", "world");
request.retain();
req.send(request);
// Receive request
final ZMsg receivedRequest = ZMsg.recvMsg(rep);
// Send reply
receivedRequest.send(rep, false);
// Receive reply
verify(handler, timeout(5000)).message(
eq(req), any(ZMTPSocket.ZMTPPeer.class), messageCaptor.capture());
final ZMTPMessage reply = messageCaptor.getValue();
ReferenceCountUtil.releaseLater(reply);
// Verify echo
assertEquals(request, reply);
request.release();
}
private ZMQ.Socket zmqBind(final int zmqType, final String identity) {
zmqSocket = context.socket(zmqType);
setIdentity(zmqSocket, identity);
port = zmqSocket.bindToRandomPort("tcp://127.0.0.1");
return zmqSocket;
}
private ZMQ.Socket zmqConnect(final int zmqType, final String identity) {
zmqSocket = context.socket(zmqType);
setIdentity(zmqSocket, identity);
zmqSocket.connect("tcp://127.0.0.1:" + port);
return zmqSocket;
}
private ZMTPSocket nettyConnect(final ZMTPSocketType socketType, final String identity,
final ZMTPProtocol protocol)
throws InterruptedException, ExecutionException, TimeoutException {
zmtpSocket = ZMTPSocket.builder()
.handler(handler)
.protocol(protocol)
.type(socketType)
.identity(identity)
.build();
final ListenableFuture<Void> f = zmtpSocket.connect("tcp://127.0.0.1:" + port);
f.get(5, TimeUnit.SECONDS);
return zmtpSocket;
}
private ZMTPSocket nettyBind(final ZMTPSocketType socketType, final String identity,
final ZMTPProtocol protocol)
throws InterruptedException, ExecutionException, TimeoutException {
zmtpSocket = ZMTPSocket.builder()
.handler(handler)
.protocol(protocol)
.type(socketType)
.identity(identity)
.build();
final ListenableFuture<InetSocketAddress> f = zmtpSocket.bind("tcp://127.0.0.1:*");
final InetSocketAddress address = f.get(5, TimeUnit.SECONDS);
port = address.getPort();
return zmtpSocket;
}
private void setIdentity(final ZMQ.Socket socket, final String identity) {
if (!identity.equals(ANONYMOUS)) {
socket.setIdentity(identity.getBytes(UTF_8));
}
}
private void verifyPeerIdentity(final ZMTPSocket socket, final String zmqIdentity) throws InterruptedException {
final List<ZMTPSocket.ZMTPPeer> peers = socket.peers();
assertThat(peers.size(), is(1));
final ZMTPSession session = peers.get(0).session();
if (ANONYMOUS.equals(zmqIdentity)) {
assertThat(session.isPeerAnonymous(), is(true));
assertThat(UTF_8.decode(session.peerIdentity()).toString(), not(isEmptyString()));
} else {
assertThat(session.isPeerAnonymous(), is(false));
assertThat(session.peerIdentity(), is(UTF_8.encode(zmqIdentity)));
}
}
}