/**
* Copyright 2016 LinkedIn Corp. All rights reserved.
*
* 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.
*/
package com.github.ambry.rest;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.JmxReporter;
import com.codahale.metrics.MetricRegistry;
import com.github.ambry.clustermap.ClusterMap;
import com.github.ambry.commons.SSLFactory;
import com.github.ambry.config.RestServerConfig;
import com.github.ambry.config.VerifiableProperties;
import com.github.ambry.notification.NotificationSystem;
import com.github.ambry.router.Router;
import com.github.ambry.router.RouterFactory;
import com.github.ambry.utils.Utils;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The RestServer represents any RESTful service (frontend, admin etc.) whose main concern is to receive requests from
* clients through a REST protocol (HTTP), handle them appropriately by contacting the backend service if required and
* return responses via the same REST protocol.
* <p/>
* The RestServer is responsible for starting up (and shutting down) multiple services required to handle requests from
* clients. Currently it starts/shuts down the following: -
* 1. A {@link Router} - A service that is used to contact the backend service.
* 2. A {@link BlobStorageService} - A service that understands the operations supported by the backend service and can
* handle requests from clients for such operations.
* 3. A {@link NioServer} - To receive requests and return responses via a REST protocol (HTTP).
* 4. A {@link RestRequestHandler} and a {@link RestResponseHandler} - Scaling units that are responsible for
* interfacing between the {@link NioServer} and the {@link BlobStorageService}.
* 5. A {@link PublicAccessLogger} - To assist in public access logging
* 6. A {@link RestServerState} - To maintain the health of the server
* <p/>
* Depending upon what is specified in the configuration file, the RestServer can start different implementations of
* {@link NioServer} and {@link BlobStorageService} and behave accordingly.
* <p/>
* With RestServer, the goals are threefold:-
* 1. To support ANY RESTful frontend service as long as it can provide an implementation of {@link BlobStorageService}.
* 2. Make it easy to plug in any implementation of {@link NioServer} as long as it can provide implementations that
* abstract framework specific objects and actions (like write/read from channel) into generic APIs through
* {@link RestRequest}, {@link RestResponseChannel} etc.
* 3. Provide scaling capabilities independent of any other component through {@link RestRequestHandler} and
* {@link RestResponseHandler}.
*/
public class RestServer {
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private final Logger logger = LoggerFactory.getLogger(getClass());
private final RestServerMetrics restServerMetrics;
private final JmxReporter reporter;
private final Router router;
private final BlobStorageService blobStorageService;
private final RestRequestHandler restRequestHandler;
private final RestResponseHandler restResponseHandler;
private final NioServer nioServer;
private final PublicAccessLogger publicAccessLogger;
private final RestServerState restServerState;
/**
* {@link RestServer} specific metrics tracking.
*/
private class RestServerMetrics {
// Errors
public final Counter restServerInstantiationError;
// Others
public final Histogram blobStorageServiceShutdownTimeInMs;
public final Histogram blobStorageServiceStartTimeInMs;
public final Histogram nioServerShutdownTimeInMs;
public final Histogram nioServerStartTimeInMs;
public final Histogram jmxReporterShutdownTimeInMs;
public final Histogram jmxReporterStartTimeInMs;
public final Histogram restRequestHandlerShutdownTimeInMs;
public final Histogram restRequestHandlerStartTimeInMs;
public final Histogram restResponseHandlerShutdownTimeInMs;
public final Histogram restResponseHandlerStartTimeInMs;
public final Histogram restServerShutdownTimeInMs;
public final Histogram restServerStartTimeInMs;
public final Histogram routerCloseTime;
/**
* Creates an instance of RestServerMetrics using the given {@code metricRegistry}.
* @param metricRegistry the {@link MetricRegistry} to use for the metrics.
* @param restServerState the {@link RestServerState} object used to track the state of the {@link RestServer}.
*/
public RestServerMetrics(MetricRegistry metricRegistry, final RestServerState restServerState) {
// Errors
restServerInstantiationError =
metricRegistry.counter(MetricRegistry.name(RestServer.class, "InstantiationError"));
// Others
blobStorageServiceShutdownTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "BlobStorageServiceShutdownTimeInMs"));
blobStorageServiceStartTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "BlobStorageServiceStartTimeInMs"));
jmxReporterShutdownTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "JmxShutdownTimeInMs"));
jmxReporterStartTimeInMs = metricRegistry.histogram(MetricRegistry.name(RestServer.class, "JmxStartTimeInMs"));
nioServerShutdownTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "NioServerShutdownTimeInMs"));
nioServerStartTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "NioServerStartTimeInMs"));
restRequestHandlerShutdownTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "RestRequestHandlerShutdownTimeInMs"));
restRequestHandlerStartTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "RestRequestHandlerStartTimeInMs"));
restResponseHandlerShutdownTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "RestResponseHandlerShutdownTimeInMs"));
restResponseHandlerStartTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "RestResponseHandlerStartTimeInMs"));
restServerShutdownTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "RestServerShutdownTimeInMs"));
restServerStartTimeInMs =
metricRegistry.histogram(MetricRegistry.name(RestServer.class, "RestServerStartTimeInMs"));
routerCloseTime = metricRegistry.histogram(MetricRegistry.name(RestServer.class, "RouterCloseTimeInMs"));
Gauge<Integer> restServerStatus = new Gauge<Integer>() {
@Override
public Integer getValue() {
return restServerState.isServiceUp() ? 1 : 0;
}
};
metricRegistry.register(MetricRegistry.name(RestServer.class, "RestServerState"), restServerStatus);
}
}
/**
* Creates an instance of RestServer.
* @param verifiableProperties the properties that define the behavior of the RestServer and its components.
* @param clusterMap the {@link ClusterMap} instance that needs to be used.
* @param notificationSystem the {@link NotificationSystem} instance that needs to be used.
* @param sslFactory the {@link SSLFactory} to be used. This can be {@code null} if no components require SSL support.
* @throws InstantiationException if there is any error instantiating an instance of RestServer.
*/
public RestServer(VerifiableProperties verifiableProperties, ClusterMap clusterMap,
NotificationSystem notificationSystem, SSLFactory sslFactory) throws Exception {
if (verifiableProperties == null || clusterMap == null || notificationSystem == null) {
throw new IllegalArgumentException("Null arg(s) received during instantiation of RestServer");
}
MetricRegistry metricRegistry = clusterMap.getMetricRegistry();
RestServerConfig restServerConfig = new RestServerConfig(verifiableProperties);
reporter = JmxReporter.forRegistry(metricRegistry).build();
RestRequestMetricsTracker.setDefaults(metricRegistry);
restServerState = new RestServerState(restServerConfig.restServerHealthCheckUri);
restServerMetrics = new RestServerMetrics(metricRegistry, restServerState);
RouterFactory routerFactory =
Utils.getObj(restServerConfig.restServerRouterFactory, verifiableProperties, clusterMap, notificationSystem,
sslFactory);
router = routerFactory.getRouter();
RestResponseHandlerFactory restResponseHandlerFactory =
Utils.getObj(restServerConfig.restServerResponseHandlerFactory,
restServerConfig.restServerResponseHandlerScalingUnitCount, metricRegistry);
restResponseHandler = restResponseHandlerFactory.getRestResponseHandler();
BlobStorageServiceFactory blobStorageServiceFactory =
Utils.getObj(restServerConfig.restServerBlobStorageServiceFactory, verifiableProperties, clusterMap,
restResponseHandler, router);
blobStorageService = blobStorageServiceFactory.getBlobStorageService();
RestRequestHandlerFactory restRequestHandlerFactory = Utils.getObj(restServerConfig.restServerRequestHandlerFactory,
restServerConfig.restServerRequestHandlerScalingUnitCount, metricRegistry, blobStorageService);
restRequestHandler = restRequestHandlerFactory.getRestRequestHandler();
publicAccessLogger = new PublicAccessLogger(restServerConfig.restServerPublicAccessLogRequestHeaders.split(","),
restServerConfig.restServerPublicAccessLogResponseHeaders.split(","));
NioServerFactory nioServerFactory =
Utils.getObj(restServerConfig.restServerNioServerFactory, verifiableProperties, metricRegistry,
restRequestHandler, publicAccessLogger, restServerState, sslFactory);
nioServer = nioServerFactory.getNioServer();
if (router == null || restResponseHandler == null || blobStorageService == null || restRequestHandler == null
|| nioServer == null) {
throw new InstantiationException("Some of the server components were null");
}
logger.trace("Instantiated RestServer");
}
/**
* Starts up all the components required. Returns when startup is FULLY complete.
* @throws InstantiationException if the RestServer is unable to start.
*/
public void start() throws InstantiationException {
logger.info("Starting RestServer");
long startupBeginTime = System.currentTimeMillis();
try {
// ordering is important.
reporter.start();
long reporterStartTime = System.currentTimeMillis();
long elapsedTime = reporterStartTime - startupBeginTime;
logger.info("JMX reporter start took {} ms", elapsedTime);
restServerMetrics.jmxReporterStartTimeInMs.update(elapsedTime);
restResponseHandler.start();
long restResponseHandlerStartTime = System.currentTimeMillis();
elapsedTime = restResponseHandlerStartTime - reporterStartTime;
logger.info("Response handler start took {} ms", elapsedTime);
restServerMetrics.restResponseHandlerStartTimeInMs.update(elapsedTime);
blobStorageService.start();
long blobStorageServiceStartTime = System.currentTimeMillis();
elapsedTime = blobStorageServiceStartTime - restResponseHandlerStartTime;
logger.info("Blob storage service start took {} ms", elapsedTime);
restServerMetrics.blobStorageServiceStartTimeInMs.update(elapsedTime);
restRequestHandler.start();
long restRequestHandlerStartTime = System.currentTimeMillis();
elapsedTime = restRequestHandlerStartTime - blobStorageServiceStartTime;
logger.info("Request handler start took {} ms", elapsedTime);
restServerMetrics.restRequestHandlerStartTimeInMs.update(elapsedTime);
nioServer.start();
elapsedTime = System.currentTimeMillis() - restRequestHandlerStartTime;
logger.info("NIO server start took {} ms", elapsedTime);
restServerMetrics.nioServerStartTimeInMs.update(elapsedTime);
restServerState.markServiceUp();
logger.info("Service marked as up");
} finally {
long startupTime = System.currentTimeMillis() - startupBeginTime;
logger.info("RestServer start took {} ms", startupTime);
restServerMetrics.restServerStartTimeInMs.update(startupTime);
}
}
/**
* Shuts down all the components. Returns when shutdown is FULLY complete.
* This method is expected to be called in the exit path as long as the RestServer instance construction was
* successful. This is expected to be called even if {@link #start()} did not succeed.
*/
public void shutdown() {
logger.info("Shutting down RestServer");
long shutdownBeginTime = System.currentTimeMillis();
try {
//ordering is important.
restServerState.markServiceDown();
logger.info("Service marked as down ");
nioServer.shutdown();
long nioServerShutdownTime = System.currentTimeMillis();
long elapsedTime = nioServerShutdownTime - shutdownBeginTime;
logger.info("NIO server shutdown took {} ms", elapsedTime);
restServerMetrics.nioServerShutdownTimeInMs.update(elapsedTime);
restRequestHandler.shutdown();
long requestHandlerShutdownTime = System.currentTimeMillis();
elapsedTime = requestHandlerShutdownTime - nioServerShutdownTime;
logger.info("Request handler shutdown took {} ms", elapsedTime);
restServerMetrics.restRequestHandlerShutdownTimeInMs.update(elapsedTime);
blobStorageService.shutdown();
long blobStorageServiceShutdownTime = System.currentTimeMillis();
elapsedTime = blobStorageServiceShutdownTime - requestHandlerShutdownTime;
logger.info("Blob storage service shutdown took {} ms", elapsedTime);
restServerMetrics.blobStorageServiceShutdownTimeInMs.update(elapsedTime);
restResponseHandler.shutdown();
long responseHandlerShutdownTime = System.currentTimeMillis();
elapsedTime = responseHandlerShutdownTime - blobStorageServiceShutdownTime;
logger.info("Response handler shutdown took {} ms", elapsedTime);
restServerMetrics.restResponseHandlerShutdownTimeInMs.update(elapsedTime);
router.close();
long routerCloseTime = System.currentTimeMillis();
elapsedTime = routerCloseTime - responseHandlerShutdownTime;
logger.info("Router close took {} ms", elapsedTime);
restServerMetrics.routerCloseTime.update(elapsedTime);
reporter.stop();
elapsedTime = System.currentTimeMillis() - routerCloseTime;
logger.info("JMX reporter shutdown took {} ms", elapsedTime);
restServerMetrics.jmxReporterShutdownTimeInMs.update(elapsedTime);
} catch (IOException e) {
logger.error("Exception during shutdown", e);
} finally {
long shutdownTime = System.currentTimeMillis() - shutdownBeginTime;
logger.info("RestServer shutdown took {} ms", shutdownTime);
restServerMetrics.restServerShutdownTimeInMs.update(shutdownTime);
shutdownLatch.countDown();
}
}
/**
* Wait for shutdown to be triggered and for it to complete.
* @throws InterruptedException if the wait for shutdown is interrupted.
*/
public void awaitShutdown() throws InterruptedException {
shutdownLatch.await();
}
}