/*
* Copyright 2013 Rackspace
*
* 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 com.rackspacecloud.blueflood.outputs.handlers;
import com.google.common.annotations.VisibleForTesting;
import com.rackspacecloud.blueflood.http.*;
import com.rackspacecloud.blueflood.inputs.handlers.UserDefinedEventHandler;
import com.rackspacecloud.blueflood.io.EventsIO;
import com.rackspacecloud.blueflood.service.Configuration;
import com.rackspacecloud.blueflood.service.CoreConfig;
import com.rackspacecloud.blueflood.service.HttpConfig;
import com.rackspacecloud.blueflood.tracker.Tracker;
import com.rackspacecloud.blueflood.utils.ModuleLoader;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
public class HttpMetricDataQueryServer {
private static final Logger log = LoggerFactory.getLogger(HttpMetricDataQueryServer.class);
private final int httpQueryPort;
private final String httpQueryHost;
private final int httpMaxContentLength;
private Channel serverChannel;
private EventsIO eventsIO;
private EventLoopGroup acceptorGroup;
private EventLoopGroup workerGroup;
private int HTTP_CONNECTION_READ_IDLE_TIME_SECONDS =
Configuration.getInstance().getIntegerProperty(HttpConfig.HTTP_CONNECTION_READ_IDLE_TIME_SECONDS);
public HttpMetricDataQueryServer() {
this.httpQueryPort = Configuration.getInstance().getIntegerProperty(HttpConfig.HTTP_METRIC_DATA_QUERY_PORT);
this.httpQueryHost = Configuration.getInstance().getStringProperty(HttpConfig.HTTP_QUERY_HOST);
this.httpMaxContentLength = Configuration.getInstance().getIntegerProperty(HttpConfig.HTTP_MAX_CONTENT_LENGTH);
int acceptThreads = Configuration.getInstance().getIntegerProperty(HttpConfig.MAX_READ_ACCEPT_THREADS);
int workerThreads = Configuration.getInstance().getIntegerProperty(HttpConfig.MAX_READ_WORKER_THREADS);
acceptorGroup = new NioEventLoopGroup(acceptThreads); // acceptor threads
workerGroup = new NioEventLoopGroup(workerThreads); // client connections threads
}
public void startServer() throws InterruptedException {
RouteMatcher router = new RouteMatcher();
router.get("/v1.0", new DefaultHandler());
router.get("/v1.0/:tenantId/experimental/views/metric_data/:metricName", new HttpRollupsQueryHandler());
router.post("/v1.0/:tenantId/experimental/views/metric_data", new HttpMultiRollupsQueryHandler());
router.post("/v2.0/:tenantId/views", new HttpMultiRollupsQueryHandler());
router.get("/v2.0", new DefaultHandler());
router.get("/v2.0/:tenantId/views/:metricName", new HttpRollupsQueryHandler());
router.get("/v2.0/:tenantId/metrics/search", new HttpMetricsIndexHandler());
router.get("/v2.0/:tenantId/metric_name/search", new HttpMetricNamesHandler());
router.get("/v2.0/:tenantId/events/getEvents", new HttpEventsQueryHandler(getEventsIO()));
router.options("/v2.0/:tenantId/views/:metricName", new HttpOptionsHandler());
router.options("/v2.0/:tenantId/views", new HttpOptionsHandler());
router.options("/v2.0/:tenantId/metrics/search", new HttpOptionsHandler());
router.options("/v2.0/:tenantId/metric_name/search", new HttpOptionsHandler());
router.options("/v2.0/:tenantId/events/getEvents", new HttpOptionsHandler());
final RouteMatcher finalRouter = router;
log.info("Starting metric data query server (HTTP) on port {}", this.httpQueryPort);
ServerBootstrap server = new ServerBootstrap();
server.group(acceptorGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
setupPipeline(channel, finalRouter);
}
});
serverChannel = server.bind(new InetSocketAddress(httpQueryHost, httpQueryPort)).sync().channel();
//register the tracker MBean for JMX/jolokia
log.info("Registering tracker service");
Tracker.getInstance().register();
}
private void setupPipeline(SocketChannel channel, RouteMatcher router) {
final ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("logging", new LoggingHandler(LogLevel.TRACE)); //duplex handler
pipeline.addLast("idleStateHandler", new IdleStateHandler(HTTP_CONNECTION_READ_IDLE_TIME_SECONDS, 0, 0)); //duplex handler
pipeline.addLast("eventHandler", new UserDefinedEventHandler()); //duplex handler
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("decoder", new HttpRequestDecoder() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable thr) throws Exception {
try {
if (ctx.channel().isWritable()) {
log.debug("request decoder error " + thr.getCause() + " on channel " + ctx.channel().toString());
ctx.channel().write(
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST))
.addListener(ChannelFutureListener.CLOSE);
} else {
log.debug("channel " + ctx.channel().toString() + " is no longer writeable, not sending 400 response back to client");
}
} catch (Exception ex) {
// If we are getting exception trying to write,
// don't propagate to caller. It may cause this
// method to be called again and will produce
// stack overflow. So just log it here.
log.debug("Can't write to channel " + ctx.channel().toString(), ex);
}
}
});
pipeline.addLast("chunkaggregator", new HttpObjectAggregator(httpMaxContentLength));
pipeline.addLast("handler", new QueryStringDecoderAndRouter(router));
}
@VisibleForTesting
public void stopServer() {
try {
serverChannel.close().await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// Pass
}
acceptorGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
private EventsIO getEventsIO() {
if (this.eventsIO == null) {
this.eventsIO = (EventsIO) ModuleLoader.getInstance(EventsIO.class, CoreConfig.EVENTS_MODULES);
}
return this.eventsIO;
}
@VisibleForTesting
public void setEventsIO(EventsIO eventsIO) {
this.eventsIO = eventsIO;
}
}