/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.hdfs.qjournal.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.DFSUtil;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
/**
* Class used for connecting to journal nodes and obtaining
* statistics about the underlying journals.
*/
public class JournalNodeJspHelper {
private static final Log LOG = LogFactory.getLog(JournalNodeJspHelper.class);
public static final ObjectMapper mapper = new ObjectMapper();
private static final int HTTP_CONNECT_TIMEOUT = 2000; // 2 secs
private static final int HTTP_READ_TIMEOUT = 5000; // 5 secs
private final Configuration conf;
private final String name;
public JournalNodeJspHelper(JournalNode jn) {
conf = jn.getConf();
StringBuilder tempName = new StringBuilder("");
for (InetSocketAddress addr : getJournals()) {
tempName = tempName.append(getHostAddress(addr)).append(",");
}
if (tempName.length() > 0) {
// Removing last ,
tempName.deleteCharAt(tempName.length() - 1);
}
name = tempName.toString();
}
public String getName() {
return name;
}
public QJMStatus generateQJMStatusReport() throws IOException {
QJMStatus status = new QJMStatus(getJournals());
Map<InetSocketAddress, Map<String, Map<String, String>>> temp
= new HashMap<InetSocketAddress, Map<String, Map<String, String>>>();
Map<InetSocketAddress, Boolean> aliveMap
= new HashMap<InetSocketAddress, Boolean>();
Set<String> allJournals = new HashSet<String>();
for (InetSocketAddress jn : status.journalNodes) {
LOG.info("Connecting to journal node: " + jn);
// mapping from a single journal to key,value stats
String json = fetchStats(jn);
aliveMap.put(jn, json != null);
Map<String, Map<String, String>> map = getStatsMap(json);
// update the list of all available journals
allJournals.addAll(map.keySet());
// store the retrieved results
temp.put(jn, map);
}
for (InetSocketAddress jn : temp.keySet()) {
// if we haven't fetch anything, we need to handle this
Map<String, Map<String, String>> stats = temp.get(jn);
for (String journal : allJournals) {
// for each journal
// we create (node, stats) mapping
status.addJournalStats(journal, jn, stats.get(journal),
aliveMap.get(jn));
}
}
return status;
}
public static class QJMStatus {
// all journal nodes
private final Collection<InetSocketAddress> journalNodes;
// all stats exposed by journals
private final Set<String> statNames;
// all journal nodes
private final Map<String, Boolean> aliveStatus;
// (journalid) -> (list(node -> (k,v))) mapping
final Map<String, List<StatsDescriptor>> stats;
public QJMStatus(Collection<InetSocketAddress> jns) {
statNames = new HashSet<String>();
aliveStatus = new HashMap<String, Boolean>();
journalNodes = jns;
stats = new HashMap<String, List<StatsDescriptor>>();
}
void addJournalStats(String journal, InetSocketAddress jn,
Map<String, String> stat, boolean alive) {
StatsDescriptor sd = new StatsDescriptor(jn, stat);
// update the set of available stats
statNames.addAll(sd.statsPerJournal.keySet());
// and the names of journalnodes
aliveStatus.put(sd.journalNode, alive);
// we add the mapping to the list of mappings for each journal
List<StatsDescriptor> currentList = stats.get(journal);
if (currentList == null) {
currentList = new ArrayList<StatsDescriptor>();
stats.put(journal, currentList);
}
currentList.add(sd);
}
public Map<String, List<StatsDescriptor>> getStatus() {
return stats;
}
public Collection<String> getJournalIds() {
return stats.keySet();
}
public Map<String, Boolean> getAliveMap() {
return aliveStatus;
}
}
public static class StatsDescriptor {
String journalNode;
Map<String, String> statsPerJournal;
public StatsDescriptor(InetSocketAddress jn,
Map<String, String> statsPerJournal) {
this.journalNode = getHostAddress(jn) + " : " + jn.getPort();
this.statsPerJournal = statsPerJournal == null ? new HashMap<String, String>()
: statsPerJournal;
}
public String toString() {
return journalNode + ":" + statsPerJournal.toString();
}
}
/**
* Retrieve value from the map corresponding to the given key.
*/
private static String getValue(Map<String, String> map, String keyName) {
String value = map.get(keyName);
return value == null ? "-" : value;
}
/**
* Fetch stats from a single given journal node over http.
*/
private String fetchStats(InetSocketAddress jn) throws IOException {
try {
return DFSUtil.getHTMLContentWithTimeout(new URI("http", null, jn.getAddress().getHostAddress(), jn
.getPort(), "/journalStats", null, null).toURL(), HTTP_CONNECT_TIMEOUT, HTTP_READ_TIMEOUT);
} catch (Exception e) {
LOG.error("Problem connecting to " + getHostAddress(jn), e);
return null;
}
}
/**
* Get the map corresponding to the JSON string
*/
private static Map<String, Map<String, String>> getStatsMap(String json)
throws IOException {
if (json == null || json.isEmpty()) {
return new HashMap<String, Map<String, String>>();
}
TypeReference<Map<String, Map<String, String>>> type = new TypeReference<Map<String, Map<String, String>>>() {
};
return mapper.readValue(json, type);
}
/**
* Get the list of journal addresses to connect.
*/
private Collection<InetSocketAddress> getJournals() {
return JournalNode.getJournalHttpAddresses(conf);
}
/**
* Generate health report for journal nodes
*/
public static String getNodeReport(QJMStatus status) {
StringBuilder sb = new StringBuilder();
sb.append("<table border=1 cellpadding=1 cellspacing=0 title=\"Journals\">");
sb.append("<thead><tr><td><b>Journal node</b></td><td><b>Alive</b></td></tr></thead>");
for (Entry<String, Boolean> e : status.getAliveMap().entrySet()) {
if (e.getValue()) {
sb.append("<tr><td>" + e.getKey()
+ "</td><td><font color=green>Active</font></td></tr>");
} else {
sb.append("<tr><td>" + e.getKey()
+ "</td><td><font color=red>Failed</font></td></tr>");
}
}
sb.append("</table>");
return sb.toString();
}
/**
* Generate report for all journals and all journal nodes
*/
public static String getJournalReport(QJMStatus status) {
StringBuilder sb = new StringBuilder();
sb.append("<table border=1 cellpadding=1 cellspacing=0 title=\"Journals\">");
sb.append("<thead><tr><td><b>JournalId</b></td><td><b>Statistics</b></td></tr></thead>");
for (String journalId : status.getJournalIds()) {
sb.append("<tr><td>" + journalId + "</td><td>");
getHTMLTableForASingleJournal(status, journalId, sb);
sb.append("</td></tr>");
}
sb.append("</table>");
return sb.toString();
}
/**
* Render html table for a single journal.
*/
public static void getHTMLTableForASingleJournal(QJMStatus status,
String journalName, StringBuilder sb) {
List<StatsDescriptor> stats = status.stats.get(journalName);
if (stats == null) {
return;
}
Set<String> statsNames = status.statNames;
// header
sb.append("<table border=1 align=\"right\" cellpadding=1 "
+ "cellspacing=0 title=\"Journal statistics\">");
sb.append("<thead><tr><td></td>");
for (StatsDescriptor sd : stats) {
sb.append("<td><b>" + sd.journalNode + "</b></td>");
}
sb.append("</tr></thead>");
// contents
for (String st : statsNames) {
// for each available stat
sb.append("<tr><td>" + st + "</td>");
// for each available node
for (StatsDescriptor sd : stats) {
sb.append("<td align=\"right\">" + getValue(sd.statsPerJournal, st)
+ "</td>");
}
sb.append("</tr>");
}
sb.append("</table>");
}
/**
* Returns the address of the host minimizing DNS lookups.
* @param addr
* @return
*/
private static String getHostAddress(InetSocketAddress addr) {
String hostToAppend = "";
if (addr.isUnresolved()) {
hostToAppend = addr.getHostName();
} else {
hostToAppend = addr.getAddress().getHostAddress();
}
return hostToAppend;
}
}