// Copyright 2016 Twitter. All rights reserved. // // 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.twitter.heron.metricsmgr; import java.io.IOException; import java.lang.reflect.Field; import java.nio.channels.SocketChannel; import java.time.Duration; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; import com.google.protobuf.Message; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mockito; import com.twitter.heron.api.metric.MultiCountMetric; import com.twitter.heron.common.basics.ByteAmount; import com.twitter.heron.common.basics.NIOLooper; import com.twitter.heron.common.basics.SingletonRegistry; import com.twitter.heron.common.basics.SysUtils; import com.twitter.heron.common.network.HeronClient; import com.twitter.heron.common.network.HeronSocketOptions; import com.twitter.heron.common.network.StatusCode; import com.twitter.heron.proto.system.Common; import com.twitter.heron.proto.system.Metrics; import com.twitter.heron.proto.tmaster.TopologyMaster; /** * Test whether MetricsManagerServer could handle TMasterLocationRefreshMessage correctly. * <p> * We make a SimpleTMasterLocationPublisher, which would send two TMasterLocationRefreshMessage, * (twice each) after connected and registered with * MetricsManagerServer, and then we check: * 1. Whether onMessage(...) is invoked 4 times, with correct arguments. * <p> * 2. Whether onMessage(...) is invoked 4 times, with correct order. * <p> * 3. Whether eventually the TMasterLocation in SingletonRegistry should be the latest one. */ public class HandleTMasterLocationTest { private static final HeronSocketOptions TEST_SOCKET_OPTIONS = new HeronSocketOptions( ByteAmount.fromMegabytes(100), Duration.ofMillis(100), ByteAmount.fromMegabytes(100), Duration.ofMillis(100), ByteAmount.fromMegabytes(5), ByteAmount.fromMegabytes(5)); // Two TMasterLocationRefreshMessage to verify private static final Metrics.TMasterLocationRefreshMessage TMASTERLOCATIONREFRESHMESSAGE0 = Metrics.TMasterLocationRefreshMessage.newBuilder().setTmaster( TopologyMaster.TMasterLocation.newBuilder(). setTopologyName("topology-name").setTopologyId("topology-id"). setHost("host").setControllerPort(0).setMasterPort(0)). build(); private static final Metrics.TMasterLocationRefreshMessage TMASTERLOCATIONREFRESHMESSAGE1 = Metrics.TMasterLocationRefreshMessage.newBuilder().setTmaster( TopologyMaster.TMasterLocation.newBuilder(). setTopologyName("topology-name").setTopologyId("topology-id"). setHost("host").setControllerPort(0).setMasterPort(1)). build(); // Bean name to register the TMasterLocation object into SingletonRegistry private static final String TMASTER_LOCATION_BEAN_NAME = TopologyMaster.TMasterLocation.newBuilder().getDescriptorForType().getFullName(); private static final String SERVER_HOST = "127.0.0.1"; private static int serverPort; private MetricsManagerServer metricsManagerServer; private SimpleTMasterLocationPublisher simpleLocationPublisher; private NIOLooper serverLooper; private ExecutorService threadsPool; @Before public void before() throws Exception { // Get an available port serverPort = SysUtils.getFreePort(); threadsPool = Executors.newFixedThreadPool(2); serverLooper = new NIOLooper(); // Spy it for unit test metricsManagerServer = Mockito.spy(new MetricsManagerServer(serverLooper, SERVER_HOST, serverPort, TEST_SOCKET_OPTIONS, new MultiCountMetric())); } @After @SuppressWarnings("unchecked") public void after() throws Exception { threadsPool.shutdownNow(); metricsManagerServer.stop(); metricsManagerServer = null; simpleLocationPublisher.stop(); simpleLocationPublisher.getNIOLooper().exitLoop(); serverLooper.exitLoop(); serverLooper = null; threadsPool = null; // Remove the Singleton by Reflection Field field = SingletonRegistry.INSTANCE.getClass().getDeclaredField("singletonObjects"); field.setAccessible(true); Map<String, Object> singletonObjects = (Map<String, Object>) field.get(SingletonRegistry.INSTANCE); singletonObjects.clear(); } @Test public void testHandleTMasterLocation() throws Exception { // First run Server runServer(); // Wait a while for server fully starting Thread.sleep(3 * 1000); // Then run Client runClient(); // Wait some while to let message fully send out Thread.sleep(10 * 1000); // Verification TopologyMaster.TMasterLocation tMasterLocation = (TopologyMaster.TMasterLocation) SingletonRegistry.INSTANCE.getSingleton(TMASTER_LOCATION_BEAN_NAME); // Verify we received these message Mockito.verify(metricsManagerServer, Mockito.times(2)). onMessage(Mockito.any(SocketChannel.class), Mockito.eq(TMASTERLOCATIONREFRESHMESSAGE0)); Mockito.verify(metricsManagerServer, Mockito.times(2)). onMessage(Mockito.any(SocketChannel.class), Mockito.eq(TMASTERLOCATIONREFRESHMESSAGE1)); // Verify we received message in order InOrder inOrder = Mockito.inOrder(metricsManagerServer); inOrder.verify(metricsManagerServer, Mockito.times(2)). onMessage(Mockito.any(SocketChannel.class), Mockito.eq(TMASTERLOCATIONREFRESHMESSAGE0)); inOrder.verify(metricsManagerServer, Mockito.times(2)). onMessage(Mockito.any(SocketChannel.class), Mockito.eq(TMASTERLOCATIONREFRESHMESSAGE1)); Assert.assertEquals("topology-name", tMasterLocation.getTopologyName()); Assert.assertEquals("topology-id", tMasterLocation.getTopologyId()); Assert.assertEquals("host", tMasterLocation.getHost()); Assert.assertEquals(0, tMasterLocation.getControllerPort()); Assert.assertEquals(1, tMasterLocation.getMasterPort()); } private void runServer() { Runnable runServer = new Runnable() { @Override public void run() { metricsManagerServer.start(); metricsManagerServer.getNIOLooper().loop(); } }; threadsPool.execute(runServer); } private void runClient() { Runnable runClient = new Runnable() { private NIOLooper looper; @Override public void run() { try { looper = new NIOLooper(); simpleLocationPublisher = new SimpleTMasterLocationPublisher(looper, SERVER_HOST, serverPort); simpleLocationPublisher.start(); looper.loop(); } catch (IOException e) { throw new RuntimeException("Some error instantiating client"); } finally { simpleLocationPublisher.stop(); if (looper != null) { looper.exitLoop(); } } } }; threadsPool.execute(runClient); } private static class SimpleTMasterLocationPublisher extends HeronClient { private static final Logger LOG = Logger.getLogger( SimpleTMasterLocationPublisher.class.getName()); SimpleTMasterLocationPublisher(NIOLooper looper, String host, int port) { super(looper, host, port, TEST_SOCKET_OPTIONS); } @Override public void onConnect(StatusCode status) { if (status != StatusCode.OK) { org.junit.Assert.fail("Connection with server failed"); } else { LOG.info("Connected with Metrics Manager Server"); sendRequest(); } } @Override public void onError() { org.junit.Assert.fail("Error in client while talking to server"); } @Override public void onClose() { } private void sendRequest() { Metrics.MetricPublisher publisher = Metrics.MetricPublisher.newBuilder(). setHostname("hostname"). setPort(0). setComponentName("tmaster-location-publisher"). setInstanceId("instance-id"). setInstanceIndex(1). build(); Metrics.MetricPublisherRegisterRequest request = Metrics.MetricPublisherRegisterRequest.newBuilder().setPublisher(publisher).build(); sendRequest(request, Metrics.MetricPublisherRegisterResponse.newBuilder()); } // We send two TMasterLocationRefreshMessage twice each // Then we check: // 1. Whether onMessage(...) is invoked 4 times, with correct arguments. // 2. Finally the TMasterLocation in SingletonRegistry should be the latest one. private void sendMessage() { // First send TMASTERLOCATIONREFRESHMESSAGE0 twice sendMessage(TMASTERLOCATIONREFRESHMESSAGE0); sendMessage(TMASTERLOCATIONREFRESHMESSAGE0); // Then send TMASTERLOCATIONREFRESHMESSAGE1 twice sendMessage(TMASTERLOCATIONREFRESHMESSAGE1); sendMessage(TMASTERLOCATIONREFRESHMESSAGE1); } @Override public void onResponse(StatusCode status, Object ctx, Message response) { if (response instanceof Metrics.MetricPublisherRegisterResponse) { Assert.assertEquals(Common.StatusCode.OK, ((Metrics.MetricPublisherRegisterResponse) response).getStatus().getStatus()); // Start sending the TMasterLocation sendMessage(); } else { org.junit.Assert.fail("Unknown type of response received"); } } @Override public void onIncomingMessage(Message request) { org.junit.Assert.fail("Expected message from client"); } } }