Return-Path: X-Original-To: apmail-hadoop-hdfs-commits-archive@minotaur.apache.org Delivered-To: apmail-hadoop-hdfs-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 0294D10C19 for ; Fri, 14 Jun 2013 21:14:59 +0000 (UTC) Received: (qmail 30024 invoked by uid 500); 14 Jun 2013 21:14:58 -0000 Delivered-To: apmail-hadoop-hdfs-commits-archive@hadoop.apache.org Received: (qmail 29991 invoked by uid 500); 14 Jun 2013 21:14:58 -0000 Mailing-List: contact hdfs-commits-help@hadoop.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: hdfs-dev@hadoop.apache.org Delivered-To: mailing list hdfs-commits@hadoop.apache.org Received: (qmail 29983 invoked by uid 99); 14 Jun 2013 21:14:58 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 14 Jun 2013 21:14:58 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 14 Jun 2013 21:14:54 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 9596623888D2; Fri, 14 Jun 2013 21:14:33 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1493249 - /hadoop/common/branches/branch-2/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/HostFileManager.java Date: Fri, 14 Jun 2013 21:14:33 -0000 To: hdfs-commits@hadoop.apache.org From: cmccabe@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20130614211433.9596623888D2@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: cmccabe Date: Fri Jun 14 21:14:33 2013 New Revision: 1493249 URL: http://svn.apache.org/r1493249 Log: Add missing file for HDFS-3934 (cmccabe) Added: hadoop/common/branches/branch-2/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/HostFileManager.java Added: hadoop/common/branches/branch-2/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/HostFileManager.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-2/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/HostFileManager.java?rev=1493249&view=auto ============================================================================== --- hadoop/common/branches/branch-2/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/HostFileManager.java (added) +++ hadoop/common/branches/branch-2/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/HostFileManager.java Fri Jun 14 21:14:33 2013 @@ -0,0 +1,358 @@ +/** + * 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 java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hdfs.protocol.DatanodeID; +import org.apache.hadoop.util.HostsFileReader; + +/** + * This class manages the include and exclude files for HDFS. + * + * These files control which DataNodes the NameNode expects to see in the + * cluster. Loosely speaking, the include file, if it exists and is not + * empty, is a list of everything we expect to see. The exclude file is + * a list of everything we want to ignore if we do see it. + * + * Entries may or may not specify a port. If they don't, we consider + * them to apply to every DataNode on that host. For example, putting + * 192.168.0.100 in the excludes file blacklists both 192.168.0.100:5000 and + * 192.168.0.100:6000. This case comes up in unit tests. + * + * When reading the hosts files, we try to find the IP address for each + * entry. This is important because it allows us to de-duplicate entries. + * If the user specifies a node as foo.bar.com in the include file, but + * 192.168.0.100 in the exclude file, we need to realize that these are + * the same node. Resolving the IP address also allows us to give more + * information back to getDatanodeListForReport, which makes the web UI + * look nicer (among other things.) See HDFS-3934 for more details. + * + * DNS resolution can be slow. For this reason, we ONLY do it when (re)reading + * the hosts files. In all other cases, we rely on the cached values either + * in the DatanodeID objects, or in HostFileManager#Entry. + * We also don't want to be holding locks when doing this. + * See HDFS-3990 for more discussion of DNS overheads. + * + * Not all entries in the hosts files will have an associated IP address. + * Some entries may be "registration names." The "registration name" of + * a DataNode is either the actual hostname, or an arbitrary string configured + * by dfs.datanode.hostname. It's possible to add registration names to the + * include or exclude files. If we can't find an IP address associated with + * a host file entry, we assume it's a registered hostname and act accordingly. + * The "registration name" feature is a little odd and it may be removed in the + * future (I hope?) + */ +public class HostFileManager { + private static final Log LOG = LogFactory.getLog(HostFileManager.class); + + public static class Entry { + /** + * This what the user put on the line before the colon, or the whole line + * if there is no colon. + */ + private final String prefix; + + /** + * This is the port which was specified after the colon. It is 0 if no + * port was given. + */ + private final int port; + + /** + * If we can resolve the IP address, this is it. Otherwise, it is the + * empty string. + */ + private final String ipAddress; + + /** + * Parse a hosts file Entry. + */ + static Entry parse(String fileName, String entry) throws IOException { + final String prefix; + final int port; + String ipAddress = ""; + + int idx = entry.indexOf(':'); + if (-1 == idx) { + prefix = entry; + port = 0; + } else { + prefix = entry.substring(0, idx); + String portStr = entry.substring(idx + 1); + try { + port = Integer.valueOf(portStr); + } catch (NumberFormatException e) { + throw new IOException("unable to parse port number for " + + "'" + entry + "'", e); + } + } + try { + // Let's see if we can resolve this prefix to an IP address. + // This may fail; one example is with a registered hostname + // which is not actually a real DNS name. + InetAddress addr = InetAddress.getByName(prefix); + ipAddress = addr.getHostAddress(); + } catch (UnknownHostException e) { + LOG.info("When reading " + fileName + ", could not look up " + + "IP address for " + prefix + ". We will assume this is a " + + "registration name.", e); + } + return new Entry(prefix, port, ipAddress); + } + + public String getIdentifier() { + return ipAddress.isEmpty() ? prefix : ipAddress; + } + + public Entry(String prefix, int port, String ipAddress) { + this.prefix = prefix; + this.port = port; + this.ipAddress = ipAddress; + } + + public String getPrefix() { + return prefix; + } + + public int getPort() { + return port; + } + + public String getIpAddress() { + return ipAddress; + } + + public String toString() { + StringBuilder bld = new StringBuilder(); + bld.append("Entry{").append(prefix).append(", port="). + append(port).append(", ipAddress=").append(ipAddress).append("}"); + return bld.toString(); + } + } + + public static class EntrySet implements Iterable { + /** + * The index. Each Entry appears in here exactly once. + * + * It may be indexed by one of: + * ipAddress:port + * ipAddress + * registeredHostname:port + * registeredHostname + * + * The different indexing strategies reflect the fact that we may or may + * not have a port or IP address for each entry. + */ + TreeMap index = new TreeMap(); + + public boolean isEmpty() { + return index.isEmpty(); + } + + public Entry find(DatanodeID datanodeID) { + Entry entry; + int xferPort = datanodeID.getXferPort(); + assert(xferPort > 0); + String datanodeIpAddr = datanodeID.getIpAddr(); + if (datanodeIpAddr != null) { + entry = index.get(datanodeIpAddr + ":" + xferPort); + if (entry != null) { + return entry; + } + entry = index.get(datanodeIpAddr); + if (entry != null) { + return entry; + } + } + String registeredHostName = datanodeID.getHostName(); + if (registeredHostName != null) { + entry = index.get(registeredHostName + ":" + xferPort); + if (entry != null) { + return entry; + } + entry = index.get(registeredHostName); + if (entry != null) { + return entry; + } + } + return null; + } + + public Entry find(Entry toFind) { + int port = toFind.getPort(); + if (port != 0) { + return index.get(toFind.getIdentifier() + ":" + port); + } else { + // An Entry with no port matches any entry with the same identifer. + // In other words, we treat 0 as "any port." + Map.Entry ceil = + index.ceilingEntry(toFind.getIdentifier()); + if ((ceil != null) && + (ceil.getValue().getIdentifier().equals( + toFind.getIdentifier()))) { + return ceil.getValue(); + } + return null; + } + } + + public String toString() { + StringBuilder bld = new StringBuilder(); + + bld.append("HostSet("); + for (Map.Entry entry : index.entrySet()) { + bld.append("\n\t"); + bld.append(entry.getKey()).append("->"). + append(entry.getValue().toString()); + } + bld.append("\n)"); + return bld.toString(); + } + + @Override + public Iterator iterator() { + return index.values().iterator(); + } + } + + public static class MutableEntrySet extends EntrySet { + public void add(DatanodeID datanodeID) { + Entry entry = new Entry(datanodeID.getHostName(), + datanodeID.getXferPort(), datanodeID.getIpAddr()); + index.put(datanodeID.getIpAddr() + ":" + datanodeID.getXferPort(), + entry); + } + + public void add(Entry entry) { + int port = entry.getPort(); + if (port != 0) { + index.put(entry.getIdentifier() + ":" + port, entry); + } else { + index.put(entry.getIdentifier(), entry); + } + } + + void readFile(String type, String filename) throws IOException { + if (filename.isEmpty()) { + return; + } + HashSet entrySet = new HashSet(); + HostsFileReader.readFileToSet(type, filename, entrySet); + for (String str : entrySet) { + Entry entry = Entry.parse(filename, str); + add(entry); + } + } + } + + private EntrySet includes = new EntrySet(); + private EntrySet excludes = new EntrySet(); + + public HostFileManager() { + } + + public void refresh(String includeFile, String excludeFile) + throws IOException { + MutableEntrySet newIncludes = new MutableEntrySet(); + IOException includeException = null; + try { + newIncludes.readFile("included", includeFile); + } catch (IOException e) { + includeException = e; + } + MutableEntrySet newExcludes = new MutableEntrySet(); + IOException excludeException = null; + try { + newExcludes.readFile("excluded", excludeFile); + } catch (IOException e) { + excludeException = e; + } + synchronized(this) { + if (includeException == null) { + includes = newIncludes; + } + if (excludeException == null) { + excludes = newExcludes; + } + } + if (includeException == null) { + LOG.info("read includes:\n" + newIncludes); + } else { + LOG.error("failed to read include file '" + includeFile + "'. " + + "Continuing to use previous include list.", + includeException); + } + if (excludeException == null) { + LOG.info("read excludes:\n" + newExcludes); + } else { + LOG.error("failed to read exclude file '" + excludeFile + "'." + + "Continuing to use previous exclude list.", + excludeException); + } + if (includeException != null) { + throw new IOException("error reading hosts file " + includeFile, + includeException); + } + if (excludeException != null) { + throw new IOException("error reading exclude file " + excludeFile, + excludeException); + } + } + + public synchronized boolean isIncluded(DatanodeID dn) { + if (includes.isEmpty()) { + // If the includes list is empty, act as if everything is in the + // includes list. + return true; + } else { + return includes.find(dn) != null; + } + } + + public synchronized boolean isExcluded(DatanodeID dn) { + return excludes.find(dn) != null; + } + + public synchronized boolean hasIncludes() { + return !includes.isEmpty(); + } + + /** + * @return the includes as an immutable set. + */ + public synchronized EntrySet getIncludes() { + return includes; + } + + /** + * @return the excludes as an immutable set. + */ + public synchronized EntrySet getExcludes() { + return excludes; + } +}