/** * Copyright 2013-2014 Recruit Technologies Co., Ltd. and contributors * (see CONTRIBUTORS.md) * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. A copy of the * License is distributed with this work in the LICENSE.md file. You may * also obtain a copy of the License from * * 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.gennai.gungnir.server; import static org.gennai.gungnir.GungnirConfig.*; import static org.gennai.gungnir.GungnirConst.*; import java.net.InetSocketAddress; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.commons.lang.StringUtils; import org.gennai.gungnir.GungnirConfig; import org.gennai.gungnir.GungnirManager; import org.gennai.gungnir.GungnirTopology; import org.gennai.gungnir.GungnirTopology.TopologyStatus; import org.gennai.gungnir.UserEntity; import org.gennai.gungnir.cluster.ClusterManager; import org.gennai.gungnir.cluster.ClusterManagerException; import org.gennai.gungnir.cluster.storm.CapacityWorkerException; import org.gennai.gungnir.cluster.storm.StormClusterManager; import org.gennai.gungnir.cluster.storm.StormClusterManagerException; import org.gennai.gungnir.cluster.storm.TopologyStatusChangedListener; import org.gennai.gungnir.metastore.InMemoryMetaStore; import org.gennai.gungnir.metastore.MetaStore; import org.gennai.gungnir.metastore.MetaStoreException; import org.gennai.gungnir.tuple.persistent.InMemoryEmitter; import org.gennai.gungnir.utils.GungnirUtils; import org.gennai.gungnir.utils.SLF4JHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext; import com.twitter.finagle.Announcement; import com.twitter.finagle.Http; import com.twitter.finagle.ListeningServer; import com.twitter.finagle.Thrift; import com.twitter.util.Await; import com.twitter.util.TimeoutException; public class GungnirServer { private static final Logger LOG = LoggerFactory.getLogger(GungnirServer.class); private GungnirManager manager; private GungnirConfig config; private MetaStore metaStore; private ClusterManager clusterManager; private ListeningServer gungnirServer; private Announcement gsAnm; private ExecutorService gungnirServerExecutor; private ListeningServer tupleStoreServer; private Announcement tssAnm; private ExecutorService tupleStoreExecutor; private OstrichAdminService adminService; public GungnirServer() throws MetaStoreException { manager = GungnirManager.getManager(); config = manager.getConfig(); metaStore = manager.getMetaStore(); } public GungnirConfig getConfig() { return config; } private void initMetaStore() throws MetaStoreException { String clusterMode = config.getString(CLUSTER_MODE); if (metaStore instanceof InMemoryMetaStore && !clusterMode.equals(LOCAL_CLUSTER)) { LOG.error("In-memory metastore can't be used in cluster other than the local cluster"); throw new MetaStoreException( "In-memory metastore can't be used in cluster other than the local cluster"); } metaStore.init(); } public void startClusterManager() throws ClusterManagerException { clusterManager = manager.getClusterManager(); clusterManager.start(); } private void restartTopologies() throws MetaStoreException, StormClusterManagerException, CapacityWorkerException { int cnt = 0; List<UserEntity> accounts = metaStore.findUserAccounts(); for (UserEntity account : accounts) { List<GungnirTopology> topologies = metaStore.findTopologies(account, TopologyStatus.RUNNING); cnt += topologies.size(); } final CountDownLatch restarted = new CountDownLatch(cnt); for (UserEntity account : accounts) { List<GungnirTopology> topologies = metaStore.findTopologies(account, TopologyStatus.RUNNING); for (final GungnirTopology topology : topologies) { topology.setStatus(TopologyStatus.STARTING); StormClusterManager.getManager().startTopology(topology, new TopologyStatusChangedListener() { @Override public void process() { topology.setStatus(TopologyStatus.RUNNING); try { manager.getClusterManager().sync(topology); LOG.info("Successful to restart topology '{}'", topology.getId()); } catch (Exception e) { LOG.error("Failed to restart topology '{}'", topology.getId()); } restarted.countDown(); } @Override public void rollback() { restarted.countDown(); } }); } } try { restarted.await(); } catch (InterruptedException e) { LOG.error("Failed to restart topologies", e); } } private void startStormCluster() throws MetaStoreException, StormClusterManagerException, CapacityWorkerException { if (config.getString(STORM_CLUSTER_MODE).equals(LOCAL_CLUSTER)) { StormClusterManager.getManager().startLocalCluster(); restartTopologies(); } } public void executeServer() { gungnirServer = Thrift.serveIface(new InetSocketAddress(config.getInteger(GUNGNIR_SERVER_PORT)), new GungnirServiceProcessor()); if (config.getString(CLUSTER_MODE).equals(DISTRIBUTED_CLUSTER)) { List<String> zkServers = config.getList(CLUSTER_ZOOKEEPER_SERVERS); try { gsAnm = Await.result(gungnirServer.announce("zk!" + StringUtils.join(zkServers, ",") + "!" + config.getString(GUNGNIR_NODE_PATH) + SERVERS_NODE_PATH + "!0")); } catch (Exception e) { throw new RuntimeException(e); } } gungnirServerExecutor = Executors.newSingleThreadExecutor(GungnirUtils.createThreadFactory("GungnirServer")); gungnirServerExecutor.execute(new Runnable() { @Override public void run() { try { Await.ready(gungnirServer); } catch (TimeoutException e) { LOG.error("Gungnir server timed out"); } catch (InterruptedException e) { LOG.info("Gungnir server interrupted"); } } }); LOG.info("Gungnir server started"); } public void executeTupleStore() throws ClusterManagerException { try { if (config.getClass(GungnirConfig.PERSISTENT_EMITTER) == InMemoryEmitter.class && (!config.getString(CLUSTER_MODE).equals(LOCAL_CLUSTER) || !config.getString(STORM_CLUSTER_MODE).equals(LOCAL_CLUSTER))) { LOG.error("Memory queue can't be used in cluster other than the storm local cluster"); throw new RuntimeException( "Memory queue can't be used in cluster other than the storm local cluster"); } } catch (ClassNotFoundException e) { throw new RuntimeException(e); } try { tupleStoreServer = Http.serve(new InetSocketAddress(config.getInteger(TUPLE_STORE_SERVER_PORT)), new TupleStoreService()); } catch (Exception e) { throw new RuntimeException(e); } clusterManager.join(); if (config.getString(CLUSTER_MODE).equals(DISTRIBUTED_CLUSTER)) { List<String> zkServers = config.getList(CLUSTER_ZOOKEEPER_SERVERS); try { tssAnm = Await.result(tupleStoreServer.announce("zk!" + StringUtils.join(zkServers, ",") + "!" + config.getString(GUNGNIR_NODE_PATH) + STORES_NODE_PATH + "!0")); } catch (Exception e) { throw new RuntimeException(e); } } tupleStoreExecutor = Executors.newSingleThreadExecutor(GungnirUtils.createThreadFactory("TupleStoreServer")); tupleStoreExecutor.execute(new Runnable() { @Override public void run() { try { Await.ready(tupleStoreServer); } catch (TimeoutException e) { LOG.error("Tuple store server timed out"); } catch (InterruptedException e) { LOG.info("Tuple store server interrupted"); } } }); LOG.info("Tuple store server started"); } public void executeAdminService() { Integer port = config.getInteger(GUNGNIR_ADMIN_SERVER_PORT); Integer backlog = config.getInteger(GUNGNIR_ADMIN_SERVER_BACKLOG); if (port != null && backlog != null) { adminService = new OstrichAdminService(port, backlog); adminService.start(); } } public void close() { if (adminService != null) { adminService.shutdown(); LOG.info("Admin server closed"); } if (tupleStoreExecutor != null) { tupleStoreExecutor.shutdownNow(); LOG.info("Tuple store server shutdown"); } if (tupleStoreServer != null) { try { if (tssAnm != null) { Await.result(tssAnm.unannounce()); } Await.result(tupleStoreServer.close()); LOG.info("Tuple store server closed"); } catch (Exception e) { LOG.error("Failed to close tuple store server", e); } } if (gungnirServerExecutor != null) { gungnirServerExecutor.shutdownNow(); LOG.info("Gungnir server shutdown"); } if (gungnirServer != null) { try { if (gsAnm != null) { Await.result(gsAnm.unannounce()); } Await.result(gungnirServer.close()); } catch (Exception e) { LOG.error("Failed to close gungnir server", e); } } if (config.getString(STORM_CLUSTER_MODE).equals(LOCAL_CLUSTER)) { StormClusterManager.getManager().shutdownLocalCluster(); } if (clusterManager != null) { clusterManager.close(); } if (manager != null) { manager.close(); } ((LoggerContext) LoggerFactory.getILoggerFactory()).stop(); LOG.info("Gungnir server closed"); } public static void main(String[] args) throws MetaStoreException { java.util.logging.Logger logger = java.util.logging.Logger.getLogger("com.twitter"); logger.setUseParentHandlers(false); logger.addHandler(new SLF4JHandler()); final GungnirServer gungnirServer = new GungnirServer(); try { gungnirServer.initMetaStore(); gungnirServer.startClusterManager(); gungnirServer.startStormCluster(); gungnirServer.executeServer(); if (gungnirServer.getConfig().getString(CLUSTER_MODE).equals(LOCAL_CLUSTER)) { gungnirServer.executeTupleStore(); gungnirServer.executeAdminService(); } Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { gungnirServer.close(); } }); } catch (Exception e) { LOG.error("Failed to start gungnir server", e); if (gungnirServer != null) { gungnirServer.close(); } } } }