/** * 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.Map; import java.util.concurrent.ConcurrentHashMap; 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.itest.control.ServerControl.ServerAddr; import org.diqube.itest.util.ClusterFlattenServiceTestUtil.TestClusterFlattenService; import org.diqube.remote.query.KeepAliveServiceConstants; import org.diqube.remote.query.QueryResultServiceConstants; import org.diqube.remote.query.thrift.KeepAliveService; import org.diqube.remote.query.thrift.QueryResultService; import org.diqube.remote.query.thrift.QueryService; import org.diqube.remote.query.thrift.RQueryException; import org.diqube.remote.query.thrift.RQueryStatistics; import org.diqube.remote.query.thrift.RResultTable; import org.diqube.thrift.base.thrift.RUUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Util class which opens a {@link QueryResultService} in this process in order to receive results from calling a * {@link QueryService} on a diqube-server. * * @author Bastian Gloeckle */ public class QueryResultServiceTestUtil { private static final Logger logger = LoggerFactory.getLogger(QueryResultServiceTestUtil.class); public static TestQueryResultService createQueryResultService() { short port = 5200; // TODO find port dynamically. TMultiplexedProcessor multiProcessor = new TMultiplexedProcessor(); TestQueryResultService res = new TestQueryResultService(new ServerAddr("127.0.0.1", port)); QueryResultServiceImpl serviceImpl = new QueryResultServiceImpl(res); multiProcessor.registerProcessor(QueryResultServiceConstants.SERVICE_NAME, new QueryResultService.Processor<QueryResultService.Iface>(serviceImpl)); multiProcessor.registerProcessor(KeepAliveServiceConstants.SERVICE_NAME, 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 TFramedTransport.Factory()); // no integrity check for everything. args.protocolFactory(new TCompactProtocol.Factory()); TNonblockingServer thriftServer = new TNonblockingServer(args); Thread serverThread = new Thread(() -> thriftServer.serve(), "Test-QueryResultService-serverthread"); res.setThriftServer(thriftServer); res.setServerThread(serverThread); serverThread.start(); return res; } public static class TestQueryResultService implements Closeable { private TServer thriftServer; private Thread serverThread; private Map<Short, RResultTable> intermediateUpdates = new ConcurrentHashMap<>(); private RResultTable finalUpdate = null; private RQueryException exception = null; private RQueryStatistics stats = null; private ServerAddr thisServicesAddr; /* package */ TestQueryResultService(ServerAddr thisServicesAddr) { this.thisServicesAddr = thisServicesAddr; } /** * Checks if there was an exception in the meantime and throws it. */ public boolean check() throws RuntimeException { if (exception != null) throw new RuntimeException("Exception while executing a query: " + exception.getMessage()); 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 Map<Short, RResultTable> getIntermediateUpdates() { return intermediateUpdates; } public RResultTable getFinalUpdate() { return finalUpdate; } public RQueryStatistics getStats() { return stats; } public ServerAddr getThisServicesAddr() { return thisServicesAddr; } public RQueryException getException() { return exception; } } /** * Internal implementation of {@link QueryResultService} which forwards all data to {@link TestClusterFlattenService}. */ private static class QueryResultServiceImpl implements QueryResultService.Iface { private TestQueryResultService res; public QueryResultServiceImpl(TestQueryResultService res) { this.res = res; } @Override public void partialUpdate(RUUID queryRUuid, RResultTable partialResult, short percentComplete) throws TException { logger.trace("Received partial update ({} %): {}", percentComplete, partialResult); res.intermediateUpdates.put(percentComplete, partialResult); } @Override public void queryResults(RUUID queryRUuid, RResultTable finalResult) throws TException { logger.trace("Received final update: {}", finalResult); res.finalUpdate = finalResult; } @Override public void queryException(RUUID queryRUuid, RQueryException exceptionThrown) throws TException { logger.trace("Received exception: {}", exceptionThrown.getMessage(), exceptionThrown); res.exception = exceptionThrown; } @Override public void queryStatistics(RUUID queryRuuid, RQueryStatistics stats) throws TException { logger.trace("Received stats: {}", stats); res.stats = stats; } } }