// 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.grouping;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.twitter.heron.api.Config;
import com.twitter.heron.api.generated.TopologyAPI;
import com.twitter.heron.api.topology.TopologyBuilder;
import com.twitter.heron.common.basics.Communicator;
import com.twitter.heron.common.basics.SingletonRegistry;
import com.twitter.heron.common.basics.SlaveLooper;
import com.twitter.heron.common.basics.SysUtils;
import com.twitter.heron.common.basics.WakeableLooper;
import com.twitter.heron.common.utils.misc.PhysicalPlanHelper;
import com.twitter.heron.instance.InstanceControlMsg;
import com.twitter.heron.instance.Slave;
import com.twitter.heron.proto.system.HeronTuples;
import com.twitter.heron.proto.system.Metrics;
import com.twitter.heron.proto.system.PhysicalPlans;
import com.twitter.heron.resource.Constants;
import com.twitter.heron.resource.TestBolt;
import com.twitter.heron.resource.TestSpout;
import com.twitter.heron.resource.UnitTestHelper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Abstract test to verify that tuples can be routed according to custom logic. Specific tests
* should extend this and override the initSpout(..), initBoltA(..) and/or initBoltB(..) methods as
* necessary to achieve the desired routing logic.
*/
public abstract class AbstractTupleRoutingTest {
private WakeableLooper testLooper;
private SlaveLooper slaveLooper;
private PhysicalPlans.PhysicalPlan physicalPlan;
private Communicator<HeronTuples.HeronTupleSet> outStreamQueue;
private Communicator<HeronTuples.HeronTupleSet> inStreamQueue;
private Communicator<InstanceControlMsg> inControlQueue;
private ExecutorService threadsPool;
private volatile int tupleReceived;
private volatile StringBuilder groupingInitInfo;
private Slave slave;
// Test component info. Topology is SPOUT -> BOLT_A -> BOLT_B
protected enum Component {
SPOUT("test-spout", "spout-id"),
BOLT_A("test-bolt-a", "bolt-a-id"),
BOLT_B("test-bolt-b", "bolt-b-id");
private String name;
private String id;
Component(String name, String instanceId) {
this.name = name;
this.id = instanceId;
}
public String getName() {
return name;
}
public String getInstanceId() {
return id;
}
}
@Before
public void before() throws Exception {
UnitTestHelper.addSystemConfigToSingleton();
tupleReceived = 0;
groupingInitInfo = new StringBuilder();
testLooper = new SlaveLooper();
slaveLooper = new SlaveLooper();
outStreamQueue = new Communicator<>(slaveLooper, testLooper);
outStreamQueue.init(Constants.QUEUE_BUFFER_SIZE, Constants.QUEUE_BUFFER_SIZE, 0.5);
inStreamQueue = new Communicator<>(testLooper, slaveLooper);
inStreamQueue.init(Constants.QUEUE_BUFFER_SIZE, Constants.QUEUE_BUFFER_SIZE, 0.5);
inControlQueue = new Communicator<>(testLooper, slaveLooper);
Communicator<Metrics.MetricPublisherPublishMessage> slaveMetricsOut =
new Communicator<>(slaveLooper, testLooper);
slaveMetricsOut.init(Constants.QUEUE_BUFFER_SIZE, Constants.QUEUE_BUFFER_SIZE, 0.5);
slave = new Slave(slaveLooper, inStreamQueue, outStreamQueue, inControlQueue, slaveMetricsOut);
threadsPool = Executors.newSingleThreadExecutor();
threadsPool.execute(slave);
}
@After
public void after() throws Exception {
UnitTestHelper.clearSingletonRegistry();
tupleReceived = 0;
groupingInitInfo = null;
if (testLooper != null) {
testLooper.exitLoop();
}
if (slaveLooper != null) {
slaveLooper.exitLoop();
}
if (threadsPool != null) {
threadsPool.shutdownNow();
}
physicalPlan = null;
testLooper = null;
slaveLooper = null;
outStreamQueue = null;
inStreamQueue = null;
slave = null;
threadsPool = null;
}
protected String getInitInfoKey(String componentName) {
return "routing-init-info+" + componentName;
}
/**
* Test that tuple routing occurs using round robin
*/
@Test
public void testRoundRobinRouting() throws Exception {
physicalPlan = constructPhysicalPlan();
PhysicalPlanHelper physicalPlanHelper =
new PhysicalPlanHelper(physicalPlan, getComponentToVerify().getInstanceId());
InstanceControlMsg instanceControlMsg = InstanceControlMsg.newBuilder()
.setNewPhysicalPlanHelper(physicalPlanHelper)
.build();
inControlQueue.offer(instanceControlMsg);
SingletonRegistry.INSTANCE.registerSingleton(
getInitInfoKey(getComponentToVerify().getName()), groupingInitInfo);
final int expectedTuplesValidated = 10;
Runnable task = new Runnable() {
@Override
public void run() {
for (int i = 0; i < Constants.RETRY_TIMES; i++) {
if (outStreamQueue.size() != 0) {
HeronTuples.HeronTupleSet set = outStreamQueue.poll();
assertTrue(set.isInitialized());
assertFalse(set.hasControl());
assertTrue(set.hasData());
HeronTuples.HeronDataTupleSet dataTupleSet = set.getData();
assertEquals(dataTupleSet.getStream().getId(), "default");
assertEquals(dataTupleSet.getStream().getComponentName(),
getComponentToVerify().getName());
for (HeronTuples.HeronDataTuple dataTuple : dataTupleSet.getTuplesList()) {
List<Integer> destTaskIds = dataTuple.getDestTaskIdsList();
assertEquals(1, destTaskIds.size());
assertEquals((Integer) tupleReceived, destTaskIds.get(0));
tupleReceived++;
}
}
if (tupleReceived == expectedTuplesValidated) {
assertEquals(getExpectedComponentInitInfo(), groupingInitInfo.toString());
testLooper.exitLoop();
break;
}
SysUtils.sleep(Constants.RETRY_INTERVAL);
}
}
};
testLooper.addTasksOnWakeup(task);
testLooper.loop();
assertEquals(expectedTuplesValidated, tupleReceived);
}
private PhysicalPlans.PhysicalPlan constructPhysicalPlan() {
PhysicalPlans.PhysicalPlan.Builder physicalPlanBuilder
= PhysicalPlans.PhysicalPlan.newBuilder();
// Set topology protobuf
TopologyBuilder topologyBuilder = new TopologyBuilder();
initSpout(topologyBuilder, Component.SPOUT.getName());
initBoltA(topologyBuilder, Component.BOLT_A.getName(), Component.SPOUT.getName());
initBoltB(topologyBuilder, Component.BOLT_B.getName(), Component.BOLT_A.getName());
Config conf = new Config();
conf.setTeamEmail("some-team@company.com");
conf.setTeamName("some-team");
conf.setTopologyProjectName("heron-integration-test");
conf.setNumStmgrs(1);
conf.setMaxSpoutPending(100);
conf.setEnableAcking(false);
TopologyAPI.Topology topology = topologyBuilder.createTopology()
.setName("topology-name")
.setConfig(conf)
.setState(TopologyAPI.TopologyState.RUNNING)
.getTopology();
physicalPlanBuilder.setTopology(topology);
// Set instances
int taskId = 0;
for (Component component : Component.values()) {
addComponent(physicalPlanBuilder, component, taskId++);
}
// Set stream mgr
PhysicalPlans.StMgr.Builder stmgr = PhysicalPlans.StMgr.newBuilder();
stmgr.setId("stream-manager-id");
stmgr.setHostName("127.0.0.1");
stmgr.setDataPort(8888);
stmgr.setLocalEndpoint("endpoint");
physicalPlanBuilder.addStmgrs(stmgr);
return physicalPlanBuilder.build();
}
private void addComponent(PhysicalPlans.PhysicalPlan.Builder builder,
Component component, int taskId) {
PhysicalPlans.InstanceInfo.Builder instanceInfo = PhysicalPlans.InstanceInfo.newBuilder();
instanceInfo.setComponentName(component.getName());
instanceInfo.setTaskId(taskId);
instanceInfo.setComponentIndex(0);
PhysicalPlans.Instance.Builder instance = PhysicalPlans.Instance.newBuilder();
instance.setInstanceId(component.getInstanceId());
instance.setStmgrId("stream-manager-id");
instance.setInfo(instanceInfo);
builder.addInstances(instance);
}
protected void initSpout(TopologyBuilder topologyBuilder, String spoutId) {
topologyBuilder.setSpout(spoutId, new TestSpout(), 1);
}
protected void initBoltA(TopologyBuilder topologyBuilder,
String boltId, String upstreamComponentId) {
topologyBuilder.setBolt(boltId, new TestBolt(), 1)
.shuffleGrouping(upstreamComponentId);
}
protected void initBoltB(TopologyBuilder topologyBuilder,
String boltId, String upstreamComponentId) {
topologyBuilder.setBolt(boltId, new TestBolt(), 1)
.shuffleGrouping(upstreamComponentId);
}
protected abstract Component getComponentToVerify();
protected abstract String getExpectedComponentInitInfo();
}