/* * Copyright 2016-present Open Networking Laboratory * * 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 org.onosproject.store.cluster.messaging.impl; import com.google.common.collect.Sets; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.Uninterruptibles; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.onlab.packet.IpAddress; import org.onosproject.cluster.ClusterMetadata; import org.onosproject.cluster.ClusterMetadataEventListener; import org.onosproject.cluster.ClusterMetadataService; import org.onosproject.cluster.ControllerNode; import org.onosproject.cluster.NodeId; import org.onosproject.core.HybridLogicalClockService; import org.onosproject.core.HybridLogicalTime; import org.onosproject.net.provider.ProviderId; import org.onosproject.store.cluster.messaging.Endpoint; import java.net.ConnectException; import java.util.Arrays; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.onlab.junit.TestTools.findAvailablePort; /** * Unit tests for NettyMessaging. */ public class NettyMessagingManagerTest { HybridLogicalClockService testClockService = new HybridLogicalClockService() { AtomicLong counter = new AtomicLong(); @Override public HybridLogicalTime timeNow() { return new HybridLogicalTime(counter.incrementAndGet(), 0); } @Override public void recordEventTime(HybridLogicalTime time) { } }; NettyMessagingManager netty1; NettyMessagingManager netty2; private static final String DUMMY_NAME = "node"; private static final String IP_STRING = "127.0.0.1"; Endpoint ep1 = new Endpoint(IpAddress.valueOf(IP_STRING), 5001); Endpoint ep2 = new Endpoint(IpAddress.valueOf(IP_STRING), 5002); Endpoint invalidEndPoint = new Endpoint(IpAddress.valueOf(IP_STRING), 5003); @Before public void setUp() throws Exception { ep1 = new Endpoint(IpAddress.valueOf("127.0.0.1"), findAvailablePort(5001)); netty1 = new NettyMessagingManager(); netty1.clusterMetadataService = dummyMetadataService(DUMMY_NAME, IP_STRING, ep1); netty1.clockService = testClockService; netty1.activate(); ep2 = new Endpoint(IpAddress.valueOf("127.0.0.1"), findAvailablePort(5003)); netty2 = new NettyMessagingManager(); netty2.clusterMetadataService = dummyMetadataService(DUMMY_NAME, IP_STRING, ep2); netty2.clockService = testClockService; netty2.activate(); } /** * Returns a random String to be used as a test subject. * @return string */ private String nextSubject() { return UUID.randomUUID().toString(); } @After public void tearDown() throws Exception { if (netty1 != null) { netty1.deactivate(); } if (netty2 != null) { netty2.deactivate(); } } @Test public void testSendAsync() { String subject = nextSubject(); CountDownLatch latch1 = new CountDownLatch(1); CompletableFuture<Void> response = netty1.sendAsync(ep2, subject, "hello world".getBytes()); response.whenComplete((r, e) -> { assertNull(e); latch1.countDown(); }); Uninterruptibles.awaitUninterruptibly(latch1); CountDownLatch latch2 = new CountDownLatch(1); response = netty1.sendAsync(invalidEndPoint, subject, "hello world".getBytes()); response.whenComplete((r, e) -> { assertNotNull(e); assertTrue(e instanceof ConnectException); latch2.countDown(); }); Uninterruptibles.awaitUninterruptibly(latch2); } @Test @Ignore // FIXME disabled on 9/29/16 due to random failures public void testSendAndReceive() { String subject = nextSubject(); AtomicBoolean handlerInvoked = new AtomicBoolean(false); AtomicReference<byte[]> request = new AtomicReference<>(); AtomicReference<Endpoint> sender = new AtomicReference<>(); BiFunction<Endpoint, byte[], byte[]> handler = (ep, data) -> { handlerInvoked.set(true); sender.set(ep); request.set(data); return "hello there".getBytes(); }; netty2.registerHandler(subject, handler, MoreExecutors.directExecutor()); CompletableFuture<byte[]> response = netty1.sendAndReceive(ep2, subject, "hello world".getBytes()); assertTrue(Arrays.equals("hello there".getBytes(), response.join())); assertTrue(handlerInvoked.get()); assertTrue(Arrays.equals(request.get(), "hello world".getBytes())); assertEquals(ep1, sender.get()); } /* * Supplies executors when registering a handler and calling sendAndReceive and verifies the request handling * and response completion occurs on the expected thread. */ @Test @Ignore public void testSendAndReceiveWithExecutor() { String subject = nextSubject(); ExecutorService completionExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "completion-thread")); ExecutorService handlerExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "handler-thread")); AtomicReference<String> handlerThreadName = new AtomicReference<>(); AtomicReference<String> completionThreadName = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); BiFunction<Endpoint, byte[], byte[]> handler = (ep, data) -> { handlerThreadName.set(Thread.currentThread().getName()); try { latch.await(); } catch (InterruptedException e1) { Thread.currentThread().interrupt(); fail("InterruptedException"); } return "hello there".getBytes(); }; netty2.registerHandler(subject, handler, handlerExecutor); CompletableFuture<byte[]> response = netty1.sendAndReceive(ep2, subject, "hello world".getBytes(), completionExecutor); response.whenComplete((r, e) -> { completionThreadName.set(Thread.currentThread().getName()); }); latch.countDown(); // Verify that the message was request handling and response completion happens on the correct thread. assertTrue(Arrays.equals("hello there".getBytes(), response.join())); assertEquals("completion-thread", completionThreadName.get()); assertEquals("handler-thread", handlerThreadName.get()); } private ClusterMetadataService dummyMetadataService(String name, String ipAddress, Endpoint ep) { return new ClusterMetadataService() { @Override public ClusterMetadata getClusterMetadata() { return new ClusterMetadata(new ProviderId(DUMMY_NAME, DUMMY_NAME), name, Sets.newHashSet(), Sets.newHashSet()); } @Override public ControllerNode getLocalNode() { return new ControllerNode() { @Override public NodeId id() { return null; } @Override public IpAddress ip() { return IpAddress.valueOf(ipAddress); } @Override public int tcpPort() { return ep.port(); } }; } @Override public void addListener(ClusterMetadataEventListener listener) {} @Override public void removeListener(ClusterMetadataEventListener listener) {} }; } }