/**
* Copyright 2016 Yahoo 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 com.yahoo.pulsar.discovery.service.web;
import static org.apache.bookkeeper.util.MathUtils.signSafeMod;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.yahoo.pulsar.common.policies.data.loadbalancer.LoadReport;
import com.yahoo.pulsar.zookeeper.ZooKeeperClientFactory;
import com.yahoo.pulsar.zookeeper.ZookeeperClientFactoryImpl;
/**
* Acts a load-balancer that receives any incoming request and discover active-available broker in round-robin manner
* and redirect request to that broker.
* <p>
* Accepts any {@value GET, PUT, POST} request and redirects to available broker-server to serve the request
* </p>
*
*/
public class DiscoveryServiceServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final AtomicInteger counter = new AtomicInteger();
private ZookeeperCacheLoader zkCache;
@Override
public void init(ServletConfig config) throws ServletException {
log.info("Initializing DiscoveryServiceServlet resource");
String zookeeperServers = config.getInitParameter("zookeeperServers");
String zookeeperSessionTimeoutMsStr = config.getInitParameter("zookeeperSessionTimeoutMs");
int zookeeperSessionTimeoutMs = zookeeperSessionTimeoutMsStr != null
? Integer.valueOf(zookeeperSessionTimeoutMsStr)
: 30_000;
String zookeeperClientFactoryClassName = config.getInitParameter("zookeeperClientFactoryClass");
if (zookeeperClientFactoryClassName == null) {
zookeeperClientFactoryClassName = ZookeeperClientFactoryImpl.class.getName();
}
log.info("zookeeperServers={} zookeeperClientFactoryClass={}", zookeeperServers,
zookeeperClientFactoryClassName);
try {
ZooKeeperClientFactory zkClientFactory = (ZooKeeperClientFactory) Class
.forName(zookeeperClientFactoryClassName).newInstance();
zkCache = new ZookeeperCacheLoader(zkClientFactory, zookeeperServers, zookeeperSessionTimeoutMs);
} catch (Throwable t) {
throw new ServletException(t);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
redirect(req, resp);
}
@Override
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
redirect(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
redirect(req, resp);
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
redirect(req, resp);
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
redirect(req, resp);
}
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
redirect(req, resp);
}
@Override
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
redirect(req, resp);
}
private void redirect(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
LoadReport broker = nextBroker();
URI brokerURI;
if (request.getScheme().equals("http")) {
// Use normal HTTP url
brokerURI = new URI(broker.getWebServiceUrl());
} else {
brokerURI = new URI(broker.getWebServiceUrlTls());
}
StringBuilder location = new StringBuilder();
location.append(brokerURI.getScheme()).append("://").append(brokerURI.getHost()).append(':')
.append(brokerURI.getPort()).append(request.getRequestURI());
if (request.getQueryString() != null) {
location.append('?').append(request.getQueryString());
}
if (log.isDebugEnabled()) {
log.info("Redirecting to {}", location);
}
response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
response.setHeader("Location", location.toString());
} catch (URISyntaxException e) {
log.warn("No broker found in zookeeper {}", e.getMessage(), e);
throw new RestException(Status.SERVICE_UNAVAILABLE, "Broker is not available");
}
}
/**
* Find next broke url in round-robin
*
* @return
*/
LoadReport nextBroker() {
List<LoadReport> availableBrokers = zkCache.getAvailableBrokers();
if (availableBrokers.isEmpty()) {
throw new RestException(Status.SERVICE_UNAVAILABLE, "No active broker is available");
} else {
int brokersCount = availableBrokers.size();
int nextIdx = signSafeMod(counter.getAndIncrement(), brokersCount);
return availableBrokers.get(nextIdx);
}
}
private static final Logger log = LoggerFactory.getLogger(DiscoveryServiceServlet.class);
}