/* * Copyright © 2014-2015 Cask Data, 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 co.cask.cdap.data2.datafabric.dataset.service; import co.cask.cdap.api.metrics.MetricsCollectionService; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.discovery.ResolvingDiscoverable; import co.cask.cdap.common.http.CommonNettyHttpServiceBuilder; import co.cask.cdap.common.metrics.MetricsReporterHook; import co.cask.cdap.common.namespace.NamespacedLocationFactory; import co.cask.cdap.common.service.UncaughtExceptionIdleService; import co.cask.cdap.data2.datafabric.dataset.service.executor.DatasetOpExecutor; import co.cask.cdap.data2.datafabric.dataset.service.mds.MDSDatasetsRegistry; import co.cask.cdap.data2.datafabric.dataset.type.DatasetTypeManager; import co.cask.cdap.data2.metrics.DatasetMetricsReporter; import co.cask.cdap.store.NamespaceStore; import co.cask.http.NettyHttpService; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.AbstractExecutionThreadService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import com.google.inject.Inject; import org.apache.twill.common.Cancellable; import org.apache.twill.discovery.Discoverable; import org.apache.twill.discovery.DiscoveryService; import org.apache.twill.discovery.DiscoveryServiceClient; import org.apache.twill.discovery.ServiceDiscovered; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * DatasetService implemented using the common http netty framework. */ public class DatasetService extends AbstractExecutionThreadService { private static final Logger LOG = LoggerFactory.getLogger(DatasetService.class); private final NettyHttpService httpService; private final DiscoveryService discoveryService; private final DiscoveryServiceClient discoveryServiceClient; private final DatasetOpExecutor opExecutorClient; private final Set<DatasetMetricsReporter> metricReporters; private final DatasetTypeManager typeManager; private final MDSDatasetsRegistry mdsDatasets; private Cancellable cancelDiscovery; private Cancellable opExecutorServiceWatch; private SettableFuture<ServiceDiscovered> opExecutorDiscovered; private volatile boolean stopping = false; @Inject public DatasetService(CConfiguration cConf, NamespacedLocationFactory namespacedLocationFactory, DiscoveryService discoveryService, DiscoveryServiceClient discoveryServiceClient, DatasetTypeManager typeManager, MetricsCollectionService metricsCollectionService, DatasetOpExecutor opExecutorClient, MDSDatasetsRegistry mdsDatasets, Set<DatasetMetricsReporter> metricReporters, DatasetInstanceService datasetInstanceService, StorageProviderNamespaceAdmin storageProviderNamespaceAdmin, NamespaceStore namespaceStore) throws Exception { this.typeManager = typeManager; DatasetTypeHandler datasetTypeHandler = new DatasetTypeHandler(typeManager, cConf, namespacedLocationFactory, namespaceStore); DatasetInstanceHandler datasetInstanceHandler = new DatasetInstanceHandler(datasetInstanceService); StorageProviderNamespaceHandler storageProviderNamespaceHandler = new StorageProviderNamespaceHandler(storageProviderNamespaceAdmin); NettyHttpService.Builder builder = new CommonNettyHttpServiceBuilder(cConf); builder.addHttpHandlers(ImmutableList.of(datasetTypeHandler, datasetInstanceHandler, storageProviderNamespaceHandler)); builder.setHandlerHooks(ImmutableList.of(new MetricsReporterHook(metricsCollectionService, Constants.Service.DATASET_MANAGER))); builder.setHost(cConf.get(Constants.Dataset.Manager.ADDRESS)); builder.setConnectionBacklog(cConf.getInt(Constants.Dataset.Manager.BACKLOG_CONNECTIONS, Constants.Dataset.Manager.DEFAULT_BACKLOG)); builder.setExecThreadPoolSize(cConf.getInt(Constants.Dataset.Manager.EXEC_THREADS, Constants.Dataset.Manager.DEFAULT_EXEC_THREADS)); builder.setBossThreadPoolSize(cConf.getInt(Constants.Dataset.Manager.BOSS_THREADS, Constants.Dataset.Manager.DEFAULT_BOSS_THREADS)); builder.setWorkerThreadPoolSize(cConf.getInt(Constants.Dataset.Manager.WORKER_THREADS, Constants.Dataset.Manager.DEFAULT_WORKER_THREADS)); this.httpService = builder.build(); this.discoveryService = discoveryService; this.discoveryServiceClient = discoveryServiceClient; this.opExecutorClient = opExecutorClient; this.mdsDatasets = mdsDatasets; this.metricReporters = metricReporters; } @Override protected void startUp() throws Exception { LOG.info("Starting DatasetService..."); mdsDatasets.startAndWait(); typeManager.startAndWait(); opExecutorClient.startAndWait(); httpService.startAndWait(); // setting watch for ops executor service that we need to be running to operate correctly ServiceDiscovered discover = discoveryServiceClient.discover(Constants.Service.DATASET_EXECUTOR); opExecutorDiscovered = SettableFuture.create(); opExecutorServiceWatch = discover.watchChanges( new ServiceDiscovered.ChangeListener() { @Override public void onChange(ServiceDiscovered serviceDiscovered) { if (!Iterables.isEmpty(serviceDiscovered)) { LOG.info("Discovered {} service", Constants.Service.DATASET_EXECUTOR); opExecutorDiscovered.set(serviceDiscovered); } } }, MoreExecutors.sameThreadExecutor()); for (DatasetMetricsReporter metricsReporter : metricReporters) { metricsReporter.start(); } } @Override protected String getServiceName() { return "DatasetService"; } @Override protected void run() throws Exception { waitForOpExecutorToStart(); LOG.info("Announcing DatasetService for discovery..."); // Register the service cancelDiscovery = discoveryService.register(ResolvingDiscoverable.of(new Discoverable() { @Override public String getName() { return Constants.Service.DATASET_MANAGER; } @Override public InetSocketAddress getSocketAddress() { return httpService.getBindAddress(); } })); LOG.info("DatasetService started successfully on {}", httpService.getBindAddress()); while (isRunning()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // It's triggered by stop Thread.currentThread().interrupt(); break; } } } private void waitForOpExecutorToStart() throws Exception { LOG.info("Waiting for {} service to be discoverable", Constants.Service.DATASET_EXECUTOR); while (!stopping) { try { opExecutorDiscovered.get(1, TimeUnit.SECONDS); opExecutorServiceWatch.cancel(); break; } catch (TimeoutException e) { // re-try } catch (InterruptedException e) { LOG.warn("Got interrupted while waiting for service {}", Constants.Service.DATASET_EXECUTOR); Thread.currentThread().interrupt(); opExecutorServiceWatch.cancel(); break; } catch (ExecutionException e) { LOG.error("Error during discovering service {}, DatasetService start failed", Constants.Service.DATASET_EXECUTOR); opExecutorServiceWatch.cancel(); throw e; } } } @Override protected void triggerShutdown() { stopping = true; super.triggerShutdown(); } @Override protected void shutDown() throws Exception { LOG.info("Stopping DatasetService..."); for (DatasetMetricsReporter metricsReporter : metricReporters) { metricsReporter.stop(); } if (opExecutorServiceWatch != null) { opExecutorServiceWatch.cancel(); } mdsDatasets.shutDown(); typeManager.stopAndWait(); if (cancelDiscovery != null) { cancelDiscovery.cancel(); } // Wait for a few seconds for requests to stop try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { LOG.error("Interrupted while waiting...", e); } httpService.stopAndWait(); opExecutorClient.stopAndWait(); } @Override public String toString() { return Objects.toStringHelper(this) .add("bindAddress", httpService.getBindAddress()) .toString(); } // in case there is an exception thrown during startup, don't want the thread to print to system.err // instead, the caller should handler the error messaging. @SuppressWarnings("NullableProblems") @Override protected Executor executor() { final String name = getClass().getSimpleName(); return new Executor() { @Override public void execute(Runnable runnable) { Thread t = new Thread(runnable, name); t.setUncaughtExceptionHandler(UncaughtExceptionIdleService.newHandler(LOG)); t.start(); } }; } }