/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.itest.util;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import org.apache.thrift.TException;
import org.apache.thrift.TMultiplexedProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TTransportException;
import org.diqube.connection.integrity.IntegrityCheckingProtocol;
import org.diqube.itest.control.ServerControl.ServerAddr;
import org.diqube.remote.cluster.ClusterFlattenServiceConstants;
import org.diqube.remote.cluster.thrift.ClusterFlattenService;
import org.diqube.remote.cluster.thrift.RFlattenException;
import org.diqube.remote.cluster.thrift.ROptionalUuid;
import org.diqube.remote.cluster.thrift.RRetryLaterException;
import org.diqube.remote.query.KeepAliveServiceConstants;
import org.diqube.remote.query.thrift.KeepAliveService;
import org.diqube.remote.query.thrift.QueryResultService;
import org.diqube.thrift.base.thrift.RNodeAddress;
import org.diqube.thrift.base.thrift.RUUID;
import org.diqube.thrift.base.util.RUuidUtil;
import org.diqube.thrift.util.RememberingTransport;
import org.diqube.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Util class which opens a {@link ClusterFlattenService} in this process in order to receive results from calling
* another {@link ClusterFlattenService} on a diqube-server.
*
* @author Bastian Gloeckle
*/
public class ClusterFlattenServiceTestUtil {
private static final Logger logger = LoggerFactory.getLogger(ClusterFlattenServiceTestUtil.class);
public static TestClusterFlattenService createClusterFlattenService(byte[] serverMacKey) {
short port = 5200; // TODO find port dynamically.
TMultiplexedProcessor multiProcessor = new TMultiplexedProcessor();
TestClusterFlattenService res = new TestClusterFlattenService(new ServerAddr("127.0.0.1", port));
ClusterFlattenServiceImpl serviceImpl = new ClusterFlattenServiceImpl(res);
multiProcessor.registerProcessor(ClusterFlattenServiceConstants.SERVICE_NAME,
new ClusterFlattenService.Processor<ClusterFlattenService.Iface>(serviceImpl));
multiProcessor.registerProcessor(KeepAliveServiceConstants.SERVICE_NAME,
// no integrity check for keep alives.
new IntegrityCheckingProtocol.IntegrityCheckDisablingProcessor(
new KeepAliveService.Processor<KeepAliveService.Iface>(new KeepAliveService.Iface() {
@Override
public void ping() throws TException {
// noop.
}
})));
TNonblockingServerSocket transport;
try {
transport = new TNonblockingServerSocket(new InetSocketAddress("127.0.0.1", port));
} catch (TTransportException e) {
throw new RuntimeException("Could not open transport for result service", e);
}
TNonblockingServer.Args args = new TNonblockingServer.Args(transport);
args.processor(multiProcessor);
args.transportFactory(new RememberingTransport.Factory(new TFramedTransport.Factory()));
args.protocolFactory(new IntegrityCheckingProtocol.Factory(new TCompactProtocol.Factory(), serverMacKey));
TNonblockingServer thriftServer = new TNonblockingServer(args);
Thread serverThread = new Thread(() -> thriftServer.serve(), "Test-ClusterFlattenService-serverthread");
res.setThriftServer(thriftServer);
res.setServerThread(serverThread);
serverThread.start();
return res;
}
public static class TestClusterFlattenService implements Closeable {
private TServer thriftServer;
private Thread serverThread;
private Deque<RFlattenException> exceptions = new ConcurrentLinkedDeque<>();
/** map from address of not to list of pair of "requestId" and "flattenId" that were received from the node */
private ConcurrentMap<RNodeAddress, Deque<Pair<UUID, UUID>>> nodeResults = new ConcurrentHashMap<>();
private ServerAddr thisServicesAddr;
/* package */ TestClusterFlattenService(ServerAddr thisServicesAddr) {
this.thisServicesAddr = thisServicesAddr;
}
/**
* Checks if there was an exception in the meantime and throws it.
*/
public boolean check() throws RuntimeException {
if (!exceptions.isEmpty())
throw new RuntimeException("Exceptions while executing a query: "
+ exceptions.stream().map(e -> e.getMessage()).collect(Collectors.toList()));
return true;
}
@Override
public void close() throws IOException {
thriftServer.stop();
try {
serverThread.join(1000);
} catch (InterruptedException e) {
throw new IOException("Interrupted while waiting for test thread to shut down.", e);
}
if (serverThread.isAlive())
throw new IOException("Could not shutdown test server thread.");
}
private void setThriftServer(TServer thriftServer) {
this.thriftServer = thriftServer;
}
private void setServerThread(Thread serverThread) {
this.serverThread = serverThread;
}
public ServerAddr getThisServicesAddr() {
return thisServicesAddr;
}
public Deque<RFlattenException> getExceptions() {
return exceptions;
}
public Map<RNodeAddress, Deque<Pair<UUID, UUID>>> getNodeResults() {
return nodeResults;
}
}
/**
* Internal implementation of {@link QueryResultService} which forwards all data to {@link TestClusterFlattenService}.
*/
private static class ClusterFlattenServiceImpl implements ClusterFlattenService.Iface {
private TestClusterFlattenService res;
public ClusterFlattenServiceImpl(TestClusterFlattenService res) {
this.res = res;
}
@Override
public void flattenAllLocalShards(RUUID flattenRequestId, String tableName, String flattenBy,
List<RNodeAddress> otherFlatteners, RNodeAddress resultAddress) throws RFlattenException, TException {
throw new RuntimeException("flattenAllLocalShards was called on the service instance of the test.");
}
@Override
public void shardsFlattened(RUUID flattenRequestId, Map<Long, Long> origShardFirstRowIdToFlattenedNumberOfRowsDelta,
RNodeAddress flattener) throws RRetryLaterException, TException {
throw new RuntimeException("shardsFlattened was called on the service instance of the test.");
}
@Override
public ROptionalUuid getLatestValidFlattening(String tableName, String flattenBy)
throws RFlattenException, TException {
throw new RuntimeException("getLatestValidFlattening was called on the service instance of the test.");
}
@Override
public void flattenDone(RUUID flattenRequestId, RUUID flattenedTableId, RNodeAddress flattener) throws TException {
Deque<Pair<UUID, UUID>> newList = new ConcurrentLinkedDeque<>();
Deque<Pair<UUID, UUID>> previous = res.nodeResults.putIfAbsent(flattener, newList);
if (previous != null)
newList = previous;
logger.info("Received flattenDone: request {}, tableId {}, flattener {}", flattenRequestId, flattenedTableId,
flattener);
newList.add(new Pair<>(RUuidUtil.toUuid(flattenRequestId), RUuidUtil.toUuid(flattenedTableId)));
}
@Override
public void flattenFailed(RUUID flattenRequestId, RFlattenException flattenException) throws TException {
logger.info("Received exception: request {}, exception {}", flattenRequestId, flattenException.getMessage(),
flattenException);
res.exceptions.add(flattenException);
}
}
}