/** * 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.ui.websocket.request; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.thrift.TException; import org.apache.thrift.TServiceClient; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.protocol.TMultiplexedProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.diqube.remote.query.ClusterInformationServiceConstants; import org.diqube.remote.query.FlattenPreparationServiceConstants; import org.diqube.remote.query.IdentityServiceConstants; import org.diqube.remote.query.QueryServiceConstants; import org.diqube.remote.query.thrift.ClusterInformationService; import org.diqube.remote.query.thrift.FlattenPreparationService; import org.diqube.remote.query.thrift.IdentityService; import org.diqube.remote.query.thrift.QueryResultService; import org.diqube.remote.query.thrift.QueryResultService.Iface; import org.diqube.remote.query.thrift.QueryService; import org.diqube.remote.query.thrift.RQueryException; import org.diqube.thrift.base.thrift.AuthenticationException; import org.diqube.thrift.base.thrift.AuthorizationException; import org.diqube.thrift.base.thrift.Ticket; import org.diqube.thrift.base.util.RUuidUtil; import org.diqube.ui.DiqubeServletConfig; import org.diqube.ui.UiQueryRegistry; import org.diqube.util.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract base implementation for {@link CommandClusterInteraction}. * * @author Bastian Gloeckle */ public abstract class AbstractCommandClusterInteraction implements CommandClusterInteraction { private static final Logger logger = LoggerFactory.getLogger(AbstractCommandClusterInteraction.class); private DiqubeServletConfig config; private Ticket ticket; /** * @param ticket * <code>null</code> or a {@link Ticket} that has been validated already. */ /* package */ AbstractCommandClusterInteraction(DiqubeServletConfig config, Ticket ticket) { this.config = config; this.ticket = ticket; } @Override public void executeDiqlQuery(String diql, Iface resultHandler) throws RuntimeException { Set<Integer> idxToCheck = IntStream.range(0, config.getClusterServers().size()).boxed().collect(Collectors.toSet()); UUID queryUuid = null; while (queryUuid == null) { if (idxToCheck.isEmpty()) throw new RuntimeException("No cluster servers were reachable"); int nextIdx = (int) Math.floor(Math.random() * config.getClusterServers().size()); if (!idxToCheck.remove(nextIdx)) continue; queryUuid = sendDiqlQuery(config.getClusterServers().get(nextIdx), diql, resultHandler); } } @Override public void cancelQuery() { if (ticket == null) throw new RuntimeException("Not logged in."); Pair<UUID, Pair<String, Short>> queryUuidAndAddrPair = findQueryUuidAndServerAddr(); UUID queryUuid = queryUuidAndAddrPair.getLeft(); // server that the query was sent to. That is the query master for that query! Pair<String, Short> serverAddr = queryUuidAndAddrPair.getRight(); TTransport transport = new TSocket(serverAddr.getLeft(), serverAddr.getRight()); transport = new TFramedTransport(transport); TProtocol queryProtocol = new TMultiplexedProtocol(new TCompactProtocol(transport), QueryServiceConstants.SERVICE_NAME); QueryService.Client queryClient = new QueryService.Client(queryProtocol); try { transport.open(); logger.info("Sending request to cancel query {} to the diqube server at {}.", queryUuid, serverAddr); queryClient.cancelQueryExecution(ticket, RUuidUtil.toRUuid(queryUuid)); } catch (TException e) { logger.warn("Could not cancel execution of query {} although requested by user.", queryUuidAndAddrPair); } finally { transport.close(); } } @Override public ClusterInformationService.Iface getClusterInformationService() { Set<Integer> idxToCheck = IntStream.range(0, config.getClusterServers().size()).boxed().collect(Collectors.toSet()); ClusterInformationService.Client client = null; while (client == null) { if (idxToCheck.isEmpty()) throw new RuntimeException("No cluster servers were reachable"); int nextIdx = (int) Math.floor(Math.random() * config.getClusterServers().size()); if (!idxToCheck.remove(nextIdx)) continue; client = openConnection(ClusterInformationService.Client.class, ClusterInformationServiceConstants.SERVICE_NAME, config.getClusterServers().get(nextIdx)); } return client; } @Override public FlattenPreparationService.Iface getFlattenPreparationService() { Set<Integer> idxToCheck = IntStream.range(0, config.getClusterServers().size()).boxed().collect(Collectors.toSet()); FlattenPreparationService.Client client = null; while (client == null) { if (idxToCheck.isEmpty()) throw new RuntimeException("No cluster servers were reachable"); int nextIdx = (int) Math.floor(Math.random() * config.getClusterServers().size()); if (!idxToCheck.remove(nextIdx)) continue; client = openConnection(FlattenPreparationService.Client.class, FlattenPreparationServiceConstants.SERVICE_NAME, config.getClusterServers().get(nextIdx)); } return client; } @Override public IdentityService.Iface getIdentityService() { Set<Integer> idxToCheck = IntStream.range(0, config.getClusterServers().size()).boxed().collect(Collectors.toSet()); IdentityService.Client client = null; while (client == null) { if (idxToCheck.isEmpty()) throw new RuntimeException("No cluster servers were reachable"); int nextIdx = (int) Math.floor(Math.random() * config.getClusterServers().size()); if (!idxToCheck.remove(nextIdx)) continue; client = openConnection(IdentityService.Client.class, IdentityServiceConstants.SERVICE_NAME, config.getClusterServers().get(nextIdx)); } return client; } private UUID sendDiqlQuery(Pair<String, Short> node, String diql, QueryResultService.Iface resultHandler) { if (ticket == null) throw new RuntimeException("Not logged in."); QueryService.Iface queryClient = openConnection(QueryService.Client.class, QueryServiceConstants.SERVICE_NAME, node); if (queryClient == null) return null; UUID queryUuid = UUID.randomUUID(); try { registerQueryThriftResultCallback(node, queryUuid, resultHandler); queryClient.asyncExecuteQuery(ticket, RUuidUtil.toRUuid(queryUuid), // diql, // true, config.createClusterResponseAddr()); logger.info("Started executing new query {} on server {}", queryUuid, node); return queryUuid; } catch (RQueryException e) { throw new RuntimeException(e.getMessage()); } catch (AuthenticationException | AuthorizationException e) { throw new RuntimeException(e.getClass().getSimpleName() + ": " + e.getMessage()); } catch (TException e) { return null; } } /** * Register a new query execution on the diqube cluster in {@link UiQueryRegistry}. * * <p> * Note that the implementing class needs to take care of cleaning up the thrift result callback! * * @param node * Cluster node that the query is sent to - this is the query master! * @param queryUuid * The UUID that was passed to the server as query UUID * @param resultHandler * The handler for results of this query. */ protected abstract void registerQueryThriftResultCallback(Pair<String, Short> node, UUID queryUuid, QueryResultService.Iface resultHandler); /** * @return Pair of query UUID and Server addr that the currently running query was sent to. */ protected abstract Pair<UUID, Pair<String, Short>> findQueryUuidAndServerAddr(); /** * Open a thift connection to a diqube-server. * * <p> * Note that the implementing class needs to take care of closing the connection again! * * @param thriftClientClass * The "Client" class of the thrift service that a connection should be opened to. * @param serviceName * The multiplexing name of the service. * @param node * The node to open a connection to. * @return A service that can be called or <code>null</code> if connection could not be opened. */ protected abstract <T extends TServiceClient> T openConnection(Class<? extends T> thriftClientClass, String serviceName, Pair<String, Short> node); }