| /** |
| * 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.server.namenode; |
| |
| import com.fasterxml.jackson.core.JsonFactory; |
| import com.fasterxml.jackson.core.JsonGenerator; |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager; |
| import org.apache.hadoop.net.NetUtils; |
| import org.apache.hadoop.net.Node; |
| import org.apache.hadoop.net.NodeBase; |
| import org.apache.hadoop.thirdparty.com.google.common.annotations.VisibleForTesting; |
| import org.apache.hadoop.util.StringUtils; |
| |
| import javax.servlet.ServletContext; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.ws.rs.core.HttpHeaders; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeSet; |
| |
| /** |
| * A servlet to print out the network topology. |
| */ |
| @InterfaceAudience.Private |
| public class NetworkTopologyServlet extends DfsServlet { |
| |
| public static final String SERVLET_NAME = "topology"; |
| public static final String PATH_SPEC = "/topology"; |
| |
| protected static final String FORMAT_JSON = "json"; |
| protected static final String FORMAT_TEXT = "text"; |
| |
| @Override |
| public void doGet(HttpServletRequest request, HttpServletResponse response) |
| throws IOException { |
| final ServletContext context = getServletContext(); |
| |
| String format = parseAcceptHeader(request); |
| if (FORMAT_TEXT.equals(format)) { |
| response.setContentType("text/plain; charset=UTF-8"); |
| } else if (FORMAT_JSON.equals(format)) { |
| response.setContentType("application/json; charset=UTF-8"); |
| } |
| |
| NameNode nn = NameNodeHttpServer.getNameNodeFromContext(context); |
| BlockManager bm = nn.getNamesystem().getBlockManager(); |
| List<Node> leaves = bm.getDatanodeManager().getNetworkTopology() |
| .getLeaves(NodeBase.ROOT); |
| |
| try (PrintStream out = new PrintStream( |
| response.getOutputStream(), false, "UTF-8")) { |
| printTopology(out, leaves, format); |
| } catch (Throwable t) { |
| String errMsg = "Print network topology failed. " |
| + StringUtils.stringifyException(t); |
| response.sendError(HttpServletResponse.SC_GONE, errMsg); |
| throw new IOException(errMsg); |
| } finally { |
| response.getOutputStream().close(); |
| } |
| } |
| |
| /** |
| * Display each rack and the nodes assigned to that rack, as determined |
| * by the NameNode, in a hierarchical manner. The nodes and racks are |
| * sorted alphabetically. |
| * |
| * @param stream print stream |
| * @param leaves leaves nodes under base scope |
| * @param format the response format |
| */ |
| protected void printTopology(PrintStream stream, List<Node> leaves, |
| String format) throws BadFormatException, IOException { |
| if (leaves.isEmpty()) { |
| stream.print("No DataNodes"); |
| return; |
| } |
| |
| // Build a map of rack -> nodes |
| Map<String, TreeSet<String>> tree = new HashMap<>(); |
| for(Node dni : leaves) { |
| String location = dni.getNetworkLocation(); |
| String name = dni.getName(); |
| |
| tree.putIfAbsent(location, new TreeSet<>()); |
| tree.get(location).add(name); |
| } |
| |
| // Sort the racks (and nodes) alphabetically, display in order |
| ArrayList<String> racks = new ArrayList<>(tree.keySet()); |
| Collections.sort(racks); |
| |
| if (FORMAT_JSON.equals(format)) { |
| printJsonFormat(stream, tree, racks); |
| } else if (FORMAT_TEXT.equals(format)) { |
| printTextFormat(stream, tree, racks); |
| } else { |
| throw new BadFormatException("Bad format: " + format); |
| } |
| } |
| |
| protected void printJsonFormat(PrintStream stream, Map<String, |
| TreeSet<String>> tree, ArrayList<String> racks) throws IOException { |
| JsonFactory dumpFactory = new JsonFactory(); |
| JsonGenerator dumpGenerator = dumpFactory.createGenerator(stream); |
| dumpGenerator.writeStartArray(); |
| |
| for(String r : racks) { |
| dumpGenerator.writeStartObject(); |
| dumpGenerator.writeFieldName(r); |
| TreeSet<String> nodes = tree.get(r); |
| dumpGenerator.writeStartArray(); |
| |
| for(String n : nodes) { |
| dumpGenerator.writeStartObject(); |
| dumpGenerator.writeStringField("ip", n); |
| String hostname = NetUtils.getHostNameOfIP(n); |
| if(hostname != null) { |
| dumpGenerator.writeStringField("hostname", hostname); |
| } |
| dumpGenerator.writeEndObject(); |
| } |
| dumpGenerator.writeEndArray(); |
| dumpGenerator.writeEndObject(); |
| } |
| dumpGenerator.writeEndArray(); |
| dumpGenerator.flush(); |
| |
| if (!dumpGenerator.isClosed()) { |
| dumpGenerator.close(); |
| } |
| } |
| |
| protected void printTextFormat(PrintStream stream, Map<String, |
| TreeSet<String>> tree, ArrayList<String> racks) { |
| for(String r : racks) { |
| stream.println("Rack: " + r); |
| TreeSet<String> nodes = tree.get(r); |
| |
| for(String n : nodes) { |
| stream.print(" " + n); |
| String hostname = NetUtils.getHostNameOfIP(n); |
| if(hostname != null) { |
| stream.print(" (" + hostname + ")"); |
| } |
| stream.println(); |
| } |
| stream.println(); |
| } |
| } |
| |
| @VisibleForTesting |
| protected static String parseAcceptHeader(HttpServletRequest request) { |
| String format = request.getHeader(HttpHeaders.ACCEPT); |
| return format != null && format.contains(FORMAT_JSON) ? |
| FORMAT_JSON : FORMAT_TEXT; |
| } |
| |
| public static class BadFormatException extends Exception { |
| private static final long serialVersionUID = 1L; |
| |
| public BadFormatException(String msg) { |
| super(msg); |
| } |
| } |
| } |