/*
* Copyright 2012-2015, the original author or authors.
*
* 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.flipkart.aesop.runtime.spring.web;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codehaus.jackson.map.ObjectMapper;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.flipkart.aesop.runtime.config.ProducerRegistration;
import com.flipkart.aesop.runtime.impl.registry.ServerContainerRegistry;
import com.flipkart.aesop.runtime.relay.DefaultRelay;
import com.flipkart.aesop.runtime.spi.admin.RuntimeConfigService;
import com.linkedin.databus2.core.container.netty.ServerContainer;
import com.linkedin.databus2.relay.config.LogicalSourceConfig;
/**
* The <code>RelayController</code> class is a Spring MVC Controller that displays Relay Metrics. Also provides
* functionality to edit relay configurations and re-initialize them
*
* @author Regunath B
* @version 1.0, 12 May 2014
*/
@Controller
public class RelayController {
/** max connections for the stream */
private static final int MAX_CONNECTIONS = 5;
private static Logger logger = LoggerFactory.getLogger(RelayController.class);
/** The ServerContainerRegsitry instance for accessing all deployed Relay instances*/
private ServerContainerRegistry runtimeRegistry;
/** The RuntimeConfigService instance for administration actions on individual deployed Relay instances*/
private RuntimeConfigService configService;
/** Object mapper for json conversion */
private ObjectMapper mapper = new ObjectMapper();
/** counter for concurrent connections for the stream */
private static AtomicInteger concurrentConnections = new AtomicInteger(0);
/**
* Request handling for relays page
*/
@RequestMapping(value = {"/relays","/"}, method = RequestMethod.GET)
public String relays(ModelMap model, HttpServletRequest request) {
// list of all relays and connected clients
List<RelayInfo> relayInfos = new LinkedList<RelayInfo>();
for (ServerContainer serverContainer : this.runtimeRegistry.getRuntimes()) {
if (DefaultRelay.class.isAssignableFrom(serverContainer.getClass())) {
DefaultRelay relay = (DefaultRelay) serverContainer;
// get all producers
for (ProducerRegistration producerRegistration : relay.getProducerRegistrationList()){
RelayInfo relayInfo = new RelayInfo(
producerRegistration.getPhysicalSourceConfig().getId(),
producerRegistration.getPhysicalSourceConfig().getName(),
producerRegistration.getPhysicalSourceConfig().getUri()
);
// get all logical sources for the registered producer
RelayInfo.LSourceInfo[] lSourceInfos = this.getLogicalSourceForProducer(producerRegistration.getPhysicalSourceConfig().getSources());
relayInfo.setlSourceInfos(lSourceInfos);
// set producer name & SCN
relayInfo.setProducerName(producerRegistration.getEventProducer().getName());
relayInfo.setProducerSinceSCN(String.valueOf(producerRegistration.getEventProducer().getSCN()));
// now add connected clients details by getting the known connected clients from the Relay
List<String> peers = relay.getPeers();
RelayInfo.ClientInfo[] clientInfos = new RelayInfo.ClientInfo[peers.size()];
for (int i=0; i<clientInfos.length; i++) {
clientInfos[i] = new RelayInfo.ClientInfo(peers.get(i));
clientInfos[i].setClientSinceSCN(relay.getHttpStatisticsCollector()
.getPeerStats(peers.get(i))
.getMaxStreamWinScn());
}
// set all connected clients for the relay
relayInfo.setClientInfos(clientInfos);
// group the clients with their leading/trailing SCN
relayInfo.setHostGroupedClient();
relayInfos.add(relayInfo);
}
}
}
model.addAttribute("relayInfos", relayInfos.toArray(new RelayInfo[0]));
if (request.getServletPath().endsWith(".json")) {
return "relays-json";
}
// create Map object for json string required in the view to show expanded view of clients
JSONObject relayClientGrouped = this.getRelayClientGroupedJson(relayInfos);
model.addAttribute("relayClientGrouped", relayClientGrouped.toString());
return "relays";
}
@RequestMapping(value = {"/metrics"}, method = RequestMethod.GET)
public String metrics() {
return "metrics";
}
/**
* Request handling for metrics stream
*/
@RequestMapping(value = {"/metrics-stream"}, method = RequestMethod.GET)
public @ResponseBody void metricsStream(HttpServletRequest request, HttpServletResponse response) {
try {
// restrict max concurrency
if (concurrentConnections.incrementAndGet() > MAX_CONNECTIONS) {
logger.info("Client refused due to max concurrency reached");
response.sendError(503, "Max concurrent connections reached: " + MAX_CONNECTIONS);
} else {
// find the relay
DefaultRelay relay = null;
for (ServerContainer serverContainer : this.runtimeRegistry.getRuntimes()) {
if (DefaultRelay.class.isAssignableFrom(serverContainer.getClass())) {
relay = (DefaultRelay) serverContainer;
break;
}
}
if (relay != null) {
logger.info("Client connected: " + request.getSession().getId());
// set appropriate headers for a stream
response.setHeader("Content-Type", "text/event-stream;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
response.setHeader("Pragma", "no-cache");
// loop metrics
while (true) {
response.getWriter().println("data: " + relay.getMetricsCollector().getJson() + "\n");
response.flushBuffer();
Thread.sleep(relay.getMetricsCollector().getRefreshInterval() * 1000);
}
} else {
logger.info("Relay not found!");
response.sendError(404,"Relay not found!");
}
}
} catch (IOException e) {
logger.info("Client Disconnected: " + request.getSession().getId());
} catch (InterruptedException e) {
logger.info("Client Disconnected: " + request.getSession().getId() + " (Interrupted)");
Thread.currentThread().interrupt();
} catch (Exception e) {
logger.error("Client Disconnected: " + request.getSession().getId() + " (Unknown Exception)", e);
} finally {
concurrentConnections.decrementAndGet();
}
}
/**
* Request handling for metrics snapshot
*/
@RequestMapping(value = {"/metrics-json"}, method = RequestMethod.GET)
public @ResponseBody void metricsJSON(HttpServletRequest request, HttpServletResponse response) {
try {
// set appropriate headers for a stream
response.setHeader("Content-Type", "application/json");
response.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
response.setHeader("Pragma", "no-cache");
DefaultRelay relay = null;
for (ServerContainer serverContainer : this.runtimeRegistry.getRuntimes()) {
if (DefaultRelay.class.isAssignableFrom(serverContainer.getClass())) {
relay = (DefaultRelay) serverContainer;
break;
}
}
if (relay != null) {
if (request.getParameterMap().containsKey("full")) {
Map<String,Object> map = new HashMap<String, Object>();
map.put("inbound", relay.getInboundEventStatisticsCollector());
map.put("outbound", relay.getOutboundEventStatisticsCollector());
map.put("http", relay.getHttpStatisticsCollector());
response.getWriter().print(mapper.writeValueAsString(map));
} else {
response.getWriter().print(relay.getMetricsCollector().getJson());
}
} else {
response.getWriter().println("{}");
}
response.flushBuffer();
} catch (IOException e) {
logger.info("Client Disconnected: " + request.getSession().getId());
} catch (Exception e) {
logger.error("Client Disconnected: " + request.getSession().getId() + " (Unknown Exception)", e);
}
}
/** Getter Setter methods */
public ServerContainerRegistry getRuntimeRegistry() {
return runtimeRegistry;
}
public void setRuntimeRegistry(ServerContainerRegistry runtimeRegistry) {
this.runtimeRegistry = runtimeRegistry;
}
public RuntimeConfigService getConfigService() {
return configService;
}
public void setConfigService(RuntimeConfigService configService) {
this.configService = configService;
}
/**
* Returns JSON structure required for UI to show expanded view of client partitions per client host
* Structure of JSON { pId => { clientHost : [ { clientPartition : clienSCN } ] } }
* @param relayInfoList List of RelayInfo class
* @return JSONObject grouped structure
*/
private JSONObject getRelayClientGroupedJson(List<RelayInfo> relayInfoList) {
Map<Integer, Map<String, Map<String, Long>>> relayClientGrouped = new HashMap<Integer,Map<String, Map<String, Long>>>();
for(RelayInfo relay : relayInfoList) {
Map<String, Map<String, Long>> relayClientInfo = relayClientGrouped.get(relay.getpSourceId());
if ( relayClientInfo == null ) {
relayClientInfo = new HashMap<String, Map<String, Long>>();
}
RelayInfo.ClientInfo[] clientInfos = relay.getClientInfos();
for(RelayInfo.ClientInfo clientInfo : clientInfos) {
String hostName = clientInfo.getClientHost();
if(relayClientInfo.get(hostName) == null) {
relayClientInfo.put(
hostName , new HashMap<String, Long>()
);
}
relayClientInfo.get(hostName).put(
clientInfo.getClientName(), clientInfo.getClientSinceSCN()
);
}
relayClientGrouped.put(relay.getpSourceId(), relayClientInfo);
}
return new JSONObject(relayClientGrouped);
}
/**
* Create a list of LSourceInfo objects for the given lSources
* @param lSources
* @return
*/
private RelayInfo.LSourceInfo[] getLogicalSourceForProducer(List<LogicalSourceConfig> lSources) {
// take all the logical source that the producer will be registered with
RelayInfo.LSourceInfo[] lSourceInfos = new RelayInfo.LSourceInfo[lSources.size()];
for (int i=0; i < lSourceInfos.length; i++) {
lSourceInfos[i] = new RelayInfo.LSourceInfo(lSources.get(i).getId());
lSourceInfos[i].setLSourceName(lSources.get(i).getName());
lSourceInfos[i].setLSourceURI(lSources.get(i).getUri());
}
return lSourceInfos;
}
}