/*
* Copyright 2014-2016 CyberVision, Inc.
*
* 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.kaaproject.kaa.server.operations.service.event;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryUntilElapsed;
import org.apache.thrift.TMultiplexedProcessor;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.server.TThreadPoolServer.Args;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kaaproject.kaa.common.hash.EndpointObjectHash;
import org.kaaproject.kaa.server.common.thrift.KaaThriftService;
import org.kaaproject.kaa.server.common.thrift.gen.operations.OperationsThriftService;
import org.kaaproject.kaa.server.common.zk.gen.ConnectionInfo;
import org.kaaproject.kaa.server.common.zk.gen.LoadInfo;
import org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo;
import org.kaaproject.kaa.server.common.zk.gen.TransportMetaData;
import org.kaaproject.kaa.server.common.zk.operations.OperationsNode;
import org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.RouteOperation;
import org.kaaproject.kaa.server.operations.service.akka.messages.core.user.EndpointUserConfigurationUpdate;
import org.kaaproject.kaa.server.operations.service.config.OperationsServerConfig;
import org.kaaproject.kaa.server.operations.service.thrift.OperationsThriftServiceImpl;
import org.kaaproject.kaa.server.sync.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* EventService test Class. Test Thrift EventMessage sending and receiving
* procedures. Run ZK test cluster. Run 2 different thrift servers. Check that
* both is registered in each other, also check one fake thrift server to check
* errors on delivery messages. Send 3 different types of messages:
* UserRouteInfo RouteInfo RemoteEndpointEvent
*
* @author Andrey Panasenko
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/operations/operations-server-eventService-test-IT.xml")
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class EventServiceThriftTestIT {
/**
* ZooKeeper port constant
*/
public static final int ZK_PORT = 21810;
/**
* The Constant LOG.
*/
private static final Logger LOG = LoggerFactory.getLogger(EventServiceThriftTestIT.class);
/**
* Thread executor
*/
private static ExecutorService executor = null;
/**
* List wich hold all running thrift servers
*/
private static List<ThriftRunner> thriftServers;
/**
* Checking listener to check that all servers started
*/
private static OperationsNodesListener listener;
/**
* default number of started working thrift service, first server is used to
* send event messages to second server
*/
private final int numberOfServers = 2;
/**
* Default thrift host, used on both servers
*/
private final String thriftHostBase = "localhost";
/**
* Base thrift port, real is calculated with adding index of server, so
* first have 9810, second 9811
*/
private final int thriftPortBase = 9810;
/**
* Event service for fake thrift server
*/
@Autowired
private EventService eventService;
/**
* Initialization class, run ZK Cluster, register check listener and
* initialize other structures.
*/
@BeforeClass
public static void init() throws Exception {
TestCluster.checkStarted();
listener = new OperationsNodesListener();
TestCluster.addOperationsListener(listener);
executor = Executors.newCachedThreadPool();
thriftServers = new LinkedList<>();
}
/**
* Stops services.
*/
@AfterClass
public static void after() throws Exception {
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
thriftServers.clear();
thriftServers = null;
TestCluster.removeOperationsListener(listener);
TestCluster.stop();
}
@Before
public void beforeTest() throws Exception {
for (int i = 0; i < numberOfServers; i++) {
ThriftRunner runner = new ThriftRunner(thriftHostBase, thriftPortBase + i);
executor.execute(runner);
thriftServers.add(runner);
// waitNeigborsRegister(runner.getEventService(), i + 1);
}
eventService.setZkNode(TestCluster.getOperationsNode());
}
@After
public void afterTest() throws Exception {
for (ThriftRunner runner : thriftServers) {
runner.shutdown();
}
eventService.shutdown();
}
//TODO: make new test
@Test
@Ignore("Need to refactor this")
public void nodesInitializationTest() {
LOG.info("Starting initialization tests...");
final ESTest test = new ESTest();
EventService eventService1 = thriftServers.get(0).getEventService();
EventService eventService2 = thriftServers.get(1).getEventService();
assertNotNull(eventService1);
assertNotNull(eventService2);
// Register listener to receive errors of transition event messages
eventService1.addListener(new EventServiceListener() {
@Override
public void onUserRouteInfo(UserRouteInfo routeInfo) {
// TODO Auto-generated method stub
}
@Override
public void onServerError(String serverId) {
LOG.info("Server {} error", serverId);
assertEquals(TestCluster.OPERATIONS_NODE_HOST + ":1000", serverId);
test.setOnServerError(true);
}
@Override
public void onRouteInfo(RouteInfo routeInfo) {
// TODO Auto-generated method stub
}
@Override
public void onEvent(RemoteEndpointEvent event) {
// TODO Auto-generated method stub
}
@Override
public void onEndpointRouteUpdate(GlobalRouteInfo update) {
// TODO Auto-generated method stub
}
@Override
public void onEndpointStateUpdate(EndpointUserConfigurationUpdate update) {
// TODO Auto-generated method stub
}
});
LOG.info("Servers started sucessfully...");
String serverId1 = thriftServers.get(1).getThriftHost() + ":" + thriftServers.get(0).getThriftPort();
String serverId2 = thriftServers.get(1).getThriftHost() + ":" + thriftServers.get(1).getThriftPort();
// Generate UserRouteInfo
final UserRouteInfo sendUserRrouteInfo = new UserRouteInfo("tenant1", "user1", "localhost:9810", RouteOperation.DELETE);
// Generate RouteInfo
EndpointObjectHash endpointKey = EndpointObjectHash.fromBytes(new byte[]{30, 30, 30, 30, 30});
RouteTableAddress address = new RouteTableAddress(endpointKey, "appToken1", serverId1);
List<EventClassFamilyVersion> ecfVersions = new ArrayList<>();
ecfVersions.add(new EventClassFamilyVersion("ecfid1", 1));
final RouteInfo sendRouteInfo = new RouteInfo("tenant1", "user1", address, ecfVersions);
// generate event
Event avroEvent = new Event(0, "eventClassFQN1", ByteBuffer.wrap("adcascd".getBytes()), "localhost:9810", "localhost:9811");
EndpointEvent endpointEvent = new EndpointEvent(endpointKey, avroEvent, UUID.randomUUID(), System.currentTimeMillis(), 10);
RouteTableAddress recipient = new RouteTableAddress(endpointKey, "appToken", serverId2);
final RemoteEndpointEvent sendEvent = new RemoteEndpointEvent("tenant1", "user1", endpointEvent, recipient);
// Register listener to event messages
eventService2.addListener(new EventServiceListener() {
@Override
public void onUserRouteInfo(UserRouteInfo routeInfo) {
LOG.info("Got onUserRouteInfo serverId={}; tenantId={}; userId={}", routeInfo.getServerId(), routeInfo.getTenantId(),
routeInfo.getUserId());
assertEquals(sendUserRrouteInfo, routeInfo);
test.setOnUserRouteInfo(true);
}
@Override
public void onRouteInfo(RouteInfo routeInfo) {
LOG.info("Got onRouteInfo {}", routeInfo.toString());
// This sleep is used to check synchronous connections
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LOG.info("After timeout");
assertEquals(sendRouteInfo, routeInfo);
test.setOnRouteInfo(true);
}
@Override
public void onEvent(RemoteEndpointEvent event) {
LOG.info("Got onEvent {}", event.toString());
assertEquals(sendEvent, event);
test.setOnEvent(true);
}
@Override
public void onServerError(String serverId) {
// TODO Auto-generated method stub
}
@Override
public void onEndpointRouteUpdate(GlobalRouteInfo update) {
// TODO Auto-generated method stub
}
@Override
public void onEndpointStateUpdate(EndpointUserConfigurationUpdate update) {
// TODO Auto-generated method stub
}
});
LOG.info("Sending UserRoute Info: {}", sendUserRrouteInfo.toString());
eventService1.sendUserRouteInfo(sendUserRrouteInfo);
LOG.info("Sending Route Info: {}", sendRouteInfo.toString());
eventService1.sendRouteInfo(sendRouteInfo, serverId2);
LOG.info(">>>>>>>>>>>>>>>After Sending Route Info");
LOG.info("Sending Event : {}", sendEvent.toString());
eventService1.sendEvent(sendEvent);
test.waitFinish();
if (!test.isESTestComplete()) {
fail("Event Service Test failed");
}
LOG.info("Event Service test complete");
}
/**
* OperationsNode Listener Class. Used to check that all nodes started and
* registred.
*/
public static class OperationsNodesListener implements OperationsNodeListener {
/*
* (non-Javadoc)
*
* @see
* org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener
* #
* onNodeAdded(org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo
* )
*/
@Override
public void onNodeAdded(OperationsNodeInfo nodeInfo) {
LOG.info("Operation Node {}:{} added", nodeInfo.getConnectionInfo().getThriftHost().toString(), nodeInfo.getConnectionInfo()
.getThriftPort().toString());
}
/*
* (non-Javadoc)
*
* @see
* org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener
* #
* onNodeUpdated(org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo
* )
*/
@Override
public void onNodeUpdated(OperationsNodeInfo nodeInfo) {
LOG.info("Operation Node {}:{} updated", nodeInfo.getConnectionInfo().getThriftHost().toString(), nodeInfo.getConnectionInfo()
.getThriftPort().toString());
}
/*
* (non-Javadoc)
*
* @see
* org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener
* #
* onNodeRemoved(org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo
* )
*/
@Override
public void onNodeRemoved(OperationsNodeInfo nodeInfo) {
LOG.info("Operation Node {}:{} removed", nodeInfo.getConnectionInfo().getThriftHost().toString(), nodeInfo.getConnectionInfo()
.getThriftPort().toString());
}
}
/**
* ThriftRunner Class. Used to run thrift servers.
*/
public class ThriftRunner implements Runnable {
private final String thriftHost;
private final int thriftPort;
private final OperationsThriftServiceImpl operationsThriftService;
private final EventService eventService;
private OperationsNode operationsNode;
/**
* The server.
*/
private TServer server;
public ThriftRunner(String thriftHost, int thriftPort) {
this.thriftHost = thriftHost;
this.thriftPort = thriftPort;
DefaultEventService eventServiceInst = new DefaultEventService();
eventServiceInst.initBean();
OperationsServerConfig config = new OperationsServerConfig();
ReflectionTestUtils.setField(eventServiceInst, "operationsServerConfig", config);
eventService = eventServiceInst;
operationsThriftService = new OperationsThriftServiceImpl();
// operationsThriftService.setEventService(eventService);
}
/**
* @return the eventService
*/
public EventService getEventService() {
return eventService;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
LOG.info("Initializing Thrift Service for Operations Server....");
LOG.info("thrift host: {}", thriftHost);
LOG.info("thrift port: {}", thriftPort);
registerZK();
try {
TMultiplexedProcessor processor = new TMultiplexedProcessor();
OperationsThriftService.Processor<OperationsThriftService.Iface> operationsProcessor = new OperationsThriftService.Processor<OperationsThriftService.Iface>(
operationsThriftService);
processor.registerProcessor(KaaThriftService.OPERATIONS_SERVICE.getServiceName(), operationsProcessor);
TServerTransport serverTransport = new TServerSocket(new InetSocketAddress(thriftHost, thriftPort));
server = new TThreadPoolServer(new Args(serverTransport).processor(processor));
LOG.info("Operations Server {}:{} Started.", thriftHost, thriftPort);
server.serve();
LOG.info("Operations Server {}:{} Stopped.", thriftHost, thriftPort);
} catch (TTransportException e) {
LOG.error("TTransportException", e);
}
}
public void registerZK() {
LOG.info("Registring Operations Server in ZK {}:{}", thriftHost, thriftPort);
OperationsNodeInfo nodeInfo = new OperationsNodeInfo();
ByteBuffer keyData = ByteBuffer.wrap(new byte[]{45, 45, 45, 45, 45});
ConnectionInfo connectionInfo = new ConnectionInfo(thriftHost, thriftPort, keyData);
nodeInfo.setConnectionInfo(connectionInfo);
nodeInfo.setLoadInfo(new LoadInfo(1, 1.0));
nodeInfo.setTransports(new ArrayList<TransportMetaData>());
String zkHostPortList = "localhost:" + ZK_PORT;
CuratorFramework zkClient = CuratorFrameworkFactory.newClient(zkHostPortList, new RetryUntilElapsed(3000, 1000));
operationsNode = new OperationsNode(nodeInfo, zkClient);
try {
operationsNode.start();
eventService.setZkNode(operationsNode);
LOG.info("Operations Server {}:{} Zk node set in Config", thriftHost, thriftPort);
} catch (Exception e) {
LOG.error("Exception: ", e);
}
}
public void shutdown() {
LOG.info("Operations Server {}:{} shutdown()", thriftHost, thriftPort);
eventService.shutdown();
server.stop();
}
/**
* @return the thriftHost
*/
public String getThriftHost() {
return thriftHost;
}
/**
* @return the thriftPort
*/
public int getThriftPort() {
return thriftPort;
}
}
/**
* Test Class. Used to gather test pass.
*/
public class ESTest {
private final Object sync = new Object();
private int callCounter = 4;
private boolean onServerError = false;
private boolean onUserRouteInfo = false;
private boolean onRouteInfo = false;
private boolean onEvent = false;
;
/**
* @param onServerError the onServerError to set
*/
public void setOnServerError(boolean onServerError) {
synchronized (sync) {
if (this.onServerError != onServerError) {
this.onServerError = onServerError;
callCounter--;
sync.notify();
}
}
}
/**
* @param onUserRouteInfo the onUserRouteInfo to set
*/
public void setOnUserRouteInfo(boolean onUserRouteInfo) {
this.onUserRouteInfo = onUserRouteInfo;
synchronized (sync) {
callCounter--;
sync.notify();
}
}
public void setOnRouteInfo(boolean onRouteInfo) {
this.onRouteInfo = onRouteInfo;
synchronized (sync) {
callCounter--;
sync.notify();
}
}
/**
* @param onEvent
*/
public void setOnEvent(boolean onEvent) {
this.onEvent = onEvent;
synchronized (sync) {
callCounter--;
sync.notify();
}
}
public void waitFinish() {
while (callCounter > 0) {
synchronized (sync) {
try {
sync.wait(50000);
} catch (InterruptedException e) {
fail(e.toString());
}
}
}
}
public boolean isESTestComplete() {
return this.onServerError & this.onUserRouteInfo & this.onRouteInfo & this.onEvent;
}
}
}