You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by el...@apache.org on 2012/04/03 01:08:15 UTC

svn commit: r1308612 - in /hadoop/common/branches/branch-1: ./ ivy/ src/contrib/streaming/ src/core/org/apache/hadoop/net/ src/core/org/apache/hadoop/util/ src/hdfs/ src/hdfs/org/apache/hadoop/hdfs/ src/test/org/apache/hadoop/hdfs/

Author: eli
Date: Mon Apr  2 23:08:14 2012
New Revision: 1308612

URL: http://svn.apache.org/viewvc?rev=1308612&view=rev
Log:
HDFS-3148. The client should be able to use multiple local interfaces for data transfer. Contributed by Eli Collins

Modified:
    hadoop/common/branches/branch-1/CHANGES.txt
    hadoop/common/branches/branch-1/ivy/libraries.properties
    hadoop/common/branches/branch-1/src/contrib/streaming/ivy.xml
    hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/DNS.java
    hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/NetUtils.java
    hadoop/common/branches/branch-1/src/core/org/apache/hadoop/util/StringUtils.java
    hadoop/common/branches/branch-1/src/hdfs/hdfs-default.xml
    hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSClient.java
    hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSConfigKeys.java
    hadoop/common/branches/branch-1/src/test/org/apache/hadoop/hdfs/TestFileCreation.java

Modified: hadoop/common/branches/branch-1/CHANGES.txt
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/CHANGES.txt?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/CHANGES.txt (original)
+++ hadoop/common/branches/branch-1/CHANGES.txt Mon Apr  2 23:08:14 2012
@@ -16,6 +16,9 @@ Release 1.1.0 - unreleased
 
     HDFS-3150. Add option for clients to contact DNs via hostname. (eli)
 
+    HDFS-3148. The client should be able to use multiple local interfaces
+    for data transfer. (eli)
+
   IMPROVEMENTS
 
     MAPREDUCE-3597. [Rumen] Provide a way to access other info of history file

Modified: hadoop/common/branches/branch-1/ivy/libraries.properties
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/ivy/libraries.properties?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/ivy/libraries.properties (original)
+++ hadoop/common/branches/branch-1/ivy/libraries.properties Mon Apr  2 23:08:14 2012
@@ -40,7 +40,7 @@ commons-math.version=2.1
 commons-el.version=1.0
 commons-fileupload.version=1.2
 commons-io.version=1.4
-commons-net.version=1.4.1
+commons-net.version=3.1
 core.version=3.1.1
 coreplugin.version=1.3.2
 

Modified: hadoop/common/branches/branch-1/src/contrib/streaming/ivy.xml
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/src/contrib/streaming/ivy.xml?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/src/contrib/streaming/ivy.xml (original)
+++ hadoop/common/branches/branch-1/src/contrib/streaming/ivy.xml Mon Apr  2 23:08:14 2012
@@ -86,10 +86,6 @@
       name="jets3t"
       rev="${jets3t.version}"
       conf="common->master"/>  -->
-<!--    <dependency org="commons-net"
-      name="commons-net"
-      rev="${commons-net.version}"
-      conf="common->master"/>  -->
     <dependency org="commons-codec"
       name="commons-codec"
       rev="${commons-codec.version}"

Modified: hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/DNS.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/DNS.java?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/DNS.java (original)
+++ hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/DNS.java Mon Apr  2 23:08:14 2012
@@ -22,7 +22,9 @@ import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.util.Collections;
 import java.util.Enumeration;
+import java.util.LinkedHashSet;
 import java.util.Vector;
 
 import javax.naming.NamingException;
@@ -101,12 +103,40 @@ public class DNS {
   }
 
   /**
+   * @param nif network interface to get addresses for
+   * @return set containing addresses for each subinterface of nif,
+   *    see below for the rationale for using an ordered set
+   */
+  private static LinkedHashSet<InetAddress> getSubinterfaceInetAddrs(
+      NetworkInterface nif) {
+    LinkedHashSet<InetAddress> addrs = new LinkedHashSet<InetAddress>();
+    Enumeration<NetworkInterface> subNifs = nif.getSubInterfaces();
+    while (subNifs.hasMoreElements()) {
+      NetworkInterface subNif = subNifs.nextElement();
+      addrs.addAll(Collections.list(subNif.getInetAddresses()));
+    }
+    return addrs;
+  }
+
+  /**
+   * Like {@link DNS#getIPs(String, boolean), but returns all
+   * IPs associated with the given interface and its subinterfaces.
+   */
+  public static String[] getIPs(String strInterface)
+      throws UnknownHostException {
+    return getIPs(strInterface, true);
+  }
+
+  /**
    * Returns all the IPs associated with the provided interface, if any, in
    * textual form.
    * 
    * @param strInterface
    *            The name of the network interface or subinterface to query
    *            (eg eth0 or eth0:0) or the string "default"
+   * @param returnSubinterfaces
+   *            Whether to return IPs associated with subinterfaces of
+   *            the given interface
    * @return A string vector of all the IPs associated with the provided
    *         interface
    * @throws UnknownHostException
@@ -114,8 +144,8 @@ public class DNS {
    *             default interface or the given interface can not be found
    * 
    */
-  public static String[] getIPs(String strInterface)
-    throws UnknownHostException {
+  public static String[] getIPs(String strInterface,
+      boolean returnSubinterfaces) throws UnknownHostException {
     if ("default".equals(strInterface)) {
       return new String[] {
           InetAddress.getLocalHost().getHostAddress()
@@ -136,11 +166,22 @@ public class DNS {
           InetAddress.getLocalHost().getHostAddress()
       };
     }
-    Vector<String> ips = new Vector<String>();
-    Enumeration<InetAddress> e = netIf.getInetAddresses();
-    while (e.hasMoreElements())
-      ips.add(e.nextElement().getHostAddress());
-    return ips.toArray(new String[] {});
+
+    // NB: Using a LinkedHashSet to preserve the order for callers
+    // that depend on a particular element being 1st in the array.
+    // For example, getDefaultIP always returns the first element.
+    LinkedHashSet<InetAddress> allAddrs = new LinkedHashSet<InetAddress>();
+    allAddrs.addAll(Collections.list(netIf.getInetAddresses()));
+    if (!returnSubinterfaces) {
+      allAddrs.removeAll(getSubinterfaceInetAddrs(netIf));
+    }
+
+    String ips[] = new String[allAddrs.size()];
+    int i = 0;
+    for (InetAddress addr : allAddrs) {
+      ips[i++] = addr.getHostAddress();
+    }
+    return ips;
   }
 
   /**

Modified: hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/NetUtils.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/NetUtils.java?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/NetUtils.java (original)
+++ hadoop/common/branches/branch-1/src/core/org/apache/hadoop/net/NetUtils.java Mon Apr  2 23:08:14 2012
@@ -39,6 +39,8 @@ import javax.net.SocketFactory;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.commons.net.util.SubnetUtils;
+import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.ipc.Server;
 import org.apache.hadoop.ipc.VersionedProtocol;
@@ -455,7 +457,7 @@ public class NetUtils {
     return (socket.getChannel() == null) ? 
             socket.getOutputStream() : new SocketOutputStream(socket, timeout);            
   }
-  
+
   /**
    * This is a drop-in replacement for 
    * {@link Socket#connect(SocketAddress, int)}.
@@ -470,11 +472,27 @@ public class NetUtils {
    * @see java.net.Socket#connect(java.net.SocketAddress, int)
    * 
    * @param socket
-   * @param endpoint 
-   * @param timeout - timeout in milliseconds
+   * @param address the remote address
+   * @param timeout timeout in milliseconds
+   */
+  public static void connect(Socket socket,
+      SocketAddress address,
+      int timeout) throws IOException {
+    connect(socket, address, null, timeout);
+  }
+
+  /**
+   * Like {@link NetUtils#connect(Socket, SocketAddress, int)} but
+   * also takes a local address and port to bind the socket to. 
+   * 
+   * @param socket
+   * @param address the remote address
+   * @param localAddr the local address to bind the socket to
+   * @param timeout timeout in milliseconds
    */
   public static void connect(Socket socket, 
-                             SocketAddress endpoint, 
+                             SocketAddress endpoint,
+                             SocketAddress localAddr,
                              int timeout) throws IOException {
     if (socket == null || endpoint == null || timeout < 0) {
       throw new IllegalArgumentException("Illegal argument for connect()");
@@ -482,6 +500,10 @@ public class NetUtils {
     
     SocketChannel ch = socket.getChannel();
     
+    if (localAddr != null) {
+      socket.bind(localAddr);
+    }
+
     if (ch == null) {
       // let the default implementation handle it.
       socket.connect(endpoint, timeout);
@@ -593,4 +615,70 @@ public class NetUtils {
     } catch (UnknownHostException ignore) { }
     return addr;
   }
+
+  /**
+   * @return true if the given string is a subnet specified
+   *     using CIDR notation, false otherwise
+   */
+  public static boolean isValidSubnet(String subnet) {
+    try {
+      new SubnetUtils(subnet);
+      return true;
+    } catch (IllegalArgumentException iae) {
+      return false;
+    }
+  }
+
+  /**
+   * Add all addresses associated with the given nif in the
+   * given subnet to the given list.
+   */
+  private static void addMatchingAddrs(NetworkInterface nif,
+      SubnetInfo subnetInfo, List<InetAddress> addrs) {
+    Enumeration<InetAddress> ifAddrs = nif.getInetAddresses();
+    while (ifAddrs.hasMoreElements()) {
+      InetAddress ifAddr = ifAddrs.nextElement();
+      if (subnetInfo.isInRange(ifAddr.getHostAddress())) {
+        addrs.add(ifAddr);
+      }
+    }
+  }
+
+  /**
+   * Return an InetAddress for each interface that matches the
+   * given subnet specified using CIDR notation.
+   *
+   * @param subnet subnet specified using CIDR notation
+   * @param returnSubinterfaces
+   *            whether to return IPs associated with subinterfaces
+   * @throws IllegalArgumentException if subnet is invalid
+   */
+  public static List<InetAddress> getIPs(String subnet,
+      boolean returnSubinterfaces) {
+    List<InetAddress> addrs = new ArrayList<InetAddress>();
+    SubnetInfo subnetInfo = new SubnetUtils(subnet).getInfo();
+    Enumeration<NetworkInterface> nifs;
+
+    try {
+      nifs = NetworkInterface.getNetworkInterfaces();
+    } catch (SocketException e) {
+      LOG.error("Unable to get host interfaces", e);
+      return addrs;
+    }
+
+    while (nifs.hasMoreElements()) {
+      NetworkInterface nif = nifs.nextElement();
+      // NB: adding addresses even if the nif is not up
+      addMatchingAddrs(nif, subnetInfo, addrs);
+
+      if (!returnSubinterfaces) {
+        continue;
+      }
+      Enumeration<NetworkInterface> subNifs = nif.getSubInterfaces();
+      while (subNifs.hasMoreElements()) {
+        addMatchingAddrs(subNifs.nextElement(), subnetInfo, addrs);
+      }
+    }
+    return addrs;
+  }
 }

Modified: hadoop/common/branches/branch-1/src/core/org/apache/hadoop/util/StringUtils.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/src/core/org/apache/hadoop/util/StringUtils.java?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/src/core/org/apache/hadoop/util/StringUtils.java (original)
+++ hadoop/common/branches/branch-1/src/core/org/apache/hadoop/util/StringUtils.java Mon Apr  2 23:08:14 2012
@@ -734,6 +734,27 @@ public class StringUtils {
   }
 
   /**
+   * Concatenates objects, using a separator.
+   *
+   * @param separator to join with
+   * @param object to join
+   * @return the joined string
+   */
+  public static String join(CharSequence separator, Object[] objects) {
+    StringBuilder sb = new StringBuilder();
+    boolean first = true;
+    for (Object obj : objects) {
+      if (first) {
+        first = false;
+      } else {
+        sb.append(separator);
+      }
+      sb.append(obj);
+    }
+    return sb.toString();
+  }
+
+  /**
    * Capitalize a word
    *
    * @param s the input string

Modified: hadoop/common/branches/branch-1/src/hdfs/hdfs-default.xml
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/src/hdfs/hdfs-default.xml?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/src/hdfs/hdfs-default.xml (original)
+++ hadoop/common/branches/branch-1/src/hdfs/hdfs-default.xml Mon Apr  2 23:08:14 2012
@@ -450,4 +450,18 @@ creations/deletions), or "all".</descrip
   </description>
 </property>
 
+<property>
+  <name>dfs.client.local.interfaces</name>
+  <value></value>
+  <description>A comma separated list of network interface names to use
+    for data transfer between the client and datanodes. When creating
+    a connection to read from or write to a datanode, the client
+    chooses one of the specified interfaces at random and binds its
+    socket to the IP of that interface. Individual names may be
+    specified as either an interface name (eg "eth0"), a subinterface
+    name (eg "eth0:0"), or an IP address (which may be specified using
+    CIDR notation to match a range of IPs).
+  </description>
+</property>
+
 </configuration>

Modified: hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSClient.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSClient.java?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSClient.java (original)
+++ hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSClient.java Mon Apr  2 23:08:14 2012
@@ -25,6 +25,7 @@ import org.apache.hadoop.fs.*;
 import org.apache.hadoop.fs.permission.FsPermission;
 import org.apache.hadoop.ipc.*;
 import org.apache.hadoop.fs.FileAlreadyExistsException;
+import org.apache.hadoop.net.DNS;
 import org.apache.hadoop.net.NetUtils;
 import org.apache.hadoop.classification.InterfaceAudience;
 import org.apache.hadoop.conf.*;
@@ -60,6 +61,8 @@ import java.nio.ByteBuffer;
 
 import javax.net.SocketFactory;
 
+import sun.net.util.IPAddressUtil;
+
 /********************************************************
  * DFSClient can connect to a Hadoop Filesystem and 
  * perform basic file tasks.  It uses the ClientProtocol
@@ -95,6 +98,7 @@ public class DFSClient implements FSCons
   private int maxBlockAcquireFailures;
   private boolean shortCircuitLocalReads;
   private boolean connectToDnViaHostname;
+  private SocketAddress[] localInterfaceAddrs;
 
   /**
    * We assume we're talking to another CDH server, which supports
@@ -262,6 +266,17 @@ public class DFSClient implements FSCons
     if (LOG.isDebugEnabled()) {
       LOG.debug("Connect to datanode via hostname is " + connectToDnViaHostname);
     }
+    String localInterfaces[] =
+      conf.getStrings(DFSConfigKeys.DFS_CLIENT_LOCAL_INTERFACES);
+    if (null == localInterfaces) {
+      localInterfaces = new String[0];
+    }
+    this.localInterfaceAddrs = getLocalInterfaceAddrs(localInterfaces);
+    if (LOG.isDebugEnabled() && 0 != localInterfaces.length) {
+      LOG.debug("Using local interfaces [" +
+          StringUtils.join(",",localInterfaces)+ "] with addresses [" +
+          StringUtils.join(",",localInterfaceAddrs) + "]");
+    }
   }
 
   static int getMaxBlockAcquireFailures(Configuration conf) {
@@ -891,6 +906,60 @@ public class DFSClient implements FSCons
   }
 
   /**
+   * Return the socket addresses to use with each configured
+   * local interface. Local interfaces may be specified by IP
+   * address, IP address range using CIDR notation, interface
+   * name (e.g. eth0) or sub-interface name (e.g. eth0:0).
+   * The socket addresses consist of the IPs for the interfaces
+   * and the ephemeral port (port 0). If an IP, IP range, or
+   * interface name matches an interface with sub-interfaces
+   * only the IP of the interface is used. Sub-interfaces can
+   * be used by specifying them explicitly (by IP or name).
+   * 
+   * @return SocketAddresses for the configured local interfaces,
+   *    or an empty array if none are configured
+   * @throws UnknownHostException if a given interface name is invalid
+   */
+  private static SocketAddress[] getLocalInterfaceAddrs(
+        String interfaceNames[]) throws UnknownHostException {
+    List<SocketAddress> localAddrs = new ArrayList<SocketAddress>();
+    for (String interfaceName : interfaceNames) {
+      if (IPAddressUtil.isIPv4LiteralAddress(interfaceName)) {
+        localAddrs.add(new InetSocketAddress(interfaceName, 0));
+      } else if (NetUtils.isValidSubnet(interfaceName)) {
+        for (InetAddress addr : NetUtils.getIPs(interfaceName, false)) {
+          localAddrs.add(new InetSocketAddress(addr, 0));
+        }
+      } else {
+        for (String ip : DNS.getIPs(interfaceName, false)) {
+          localAddrs.add(new InetSocketAddress(ip, 0));
+        }
+      }
+    }
+    return localAddrs.toArray(new SocketAddress[localAddrs.size()]);
+  }
+
+  /**
+   * Select one of the configured local interfaces at random. We use a random
+   * interface because other policies like round-robin are less effective
+   * given that we cache connections to datanodes.
+   *
+   * @return one of the local interface addresses at random, or null if no
+   *    local interfaces are configured
+   */
+  private SocketAddress getRandomLocalInterfaceAddr() {
+    if (localInterfaceAddrs.length == 0) {
+      return null;
+    }
+    final int idx = r.nextInt(localInterfaceAddrs.length);
+    final SocketAddress addr = localInterfaceAddrs[idx];
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Using local interface " + addr);
+    }
+    return addr;
+  }
+
+  /**
    * Get the checksum of a file.
    * @param src The file path
    * @return The checksum 
@@ -2111,7 +2180,8 @@ public class DFSClient implements FSCons
         try {
           s = socketFactory.createSocket();
           LOG.debug("Connecting to " + targetAddr);
-          NetUtils.connect(s, targetAddr, socketTimeout);
+          NetUtils.connect(s, targetAddr, getRandomLocalInterfaceAddr(), 
+              socketTimeout);
           s.setSoTimeout(socketTimeout);
           blockReader = RemoteBlockReader.newBlockReader(s, src, blk.getBlockId(), 
               accessToken, 
@@ -2343,7 +2413,8 @@ public class DFSClient implements FSCons
             // go to the datanode
             dn = socketFactory.createSocket();
             LOG.debug("Connecting to " + targetAddr);
-            NetUtils.connect(dn, targetAddr, socketTimeout);
+            NetUtils.connect(dn, targetAddr, getRandomLocalInterfaceAddr(),
+                socketTimeout);
             dn.setSoTimeout(socketTimeout);
             reader = RemoteBlockReader.newBlockReader(dn, src, 
                 block.getBlock().getBlockId(), accessToken,
@@ -3447,7 +3518,7 @@ public class DFSClient implements FSCons
         s = socketFactory.createSocket();
         timeoutValue = 3000 * nodes.length + socketTimeout;
         LOG.debug("Connecting to " + dnName);
-        NetUtils.connect(s, target, timeoutValue);
+        NetUtils.connect(s, target, getRandomLocalInterfaceAddr(), timeoutValue);
         s.setSoTimeout(timeoutValue);
         s.setSendBufferSize(DEFAULT_DATA_SOCKET_SIZE);
         LOG.debug("Send buf size " + s.getSendBufferSize());

Modified: hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSConfigKeys.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSConfigKeys.java?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSConfigKeys.java (original)
+++ hadoop/common/branches/branch-1/src/hdfs/org/apache/hadoop/hdfs/DFSConfigKeys.java Mon Apr  2 23:08:14 2012
@@ -127,6 +127,7 @@ public class DFSConfigKeys extends Commo
   public static final String  DFS_CLIENT_SOCKET_TIMEOUT_KEY = "dfs.client.socket-timeout";
   public static final String  DFS_NAMENODE_CHECKPOINT_DIR_KEY = "dfs.namenode.checkpoint.dir";
   public static final String  DFS_NAMENODE_CHECKPOINT_EDITS_DIR_KEY = "dfs.namenode.checkpoint.edits.dir";
+  public static final String  DFS_CLIENT_LOCAL_INTERFACES = "dfs.client.local.interfaces";
 
   //Code in hdfs is not updated to use these keys.
   public static final String  DFS_CLIENT_BLOCK_WRITE_LOCATEFOLLOWINGBLOCK_RETRIES_KEY = "dfs.client.block.write.locateFollowingBlock.retries";

Modified: hadoop/common/branches/branch-1/src/test/org/apache/hadoop/hdfs/TestFileCreation.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-1/src/test/org/apache/hadoop/hdfs/TestFileCreation.java?rev=1308612&r1=1308611&r2=1308612&view=diff
==============================================================================
--- hadoop/common/branches/branch-1/src/test/org/apache/hadoop/hdfs/TestFileCreation.java (original)
+++ hadoop/common/branches/branch-1/src/test/org/apache/hadoop/hdfs/TestFileCreation.java Mon Apr  2 23:08:14 2012
@@ -23,6 +23,7 @@ import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 
 import org.apache.commons.logging.impl.Log4JLogger;
 import org.apache.hadoop.conf.Configuration;
@@ -47,6 +48,7 @@ import org.apache.hadoop.hdfs.server.nam
 import org.apache.hadoop.io.IOUtils;
 import org.apache.log4j.Level;
 
+import static org.junit.Assume.assumeTrue;
 
 /**
  * This class tests that a file need not be closed before its
@@ -171,29 +173,50 @@ public class TestFileCreation extends ju
   }
 
   public void testFileCreation() throws IOException {
-    checkFileCreation(false);
+    checkFileCreation(null, null);
   }
 
+  /** Same test but the client should use DN hostname instead of IPs */
   public void testFileCreationByHostname() throws IOException {
-    checkFileCreation(true);
+    assumeTrue(System.getProperty("os.name").startsWith("Linux"));
+
+    // Since the mini cluster only listens on the loopback we have to
+    // ensure the hostname used to access DNs maps to the loopback. We
+    // do this by telling the DN to advertise localhost as its hostname
+    // instead of the default hostname.
+    checkFileCreation("localhost", null);
+  }
+
+  /** Same test but the client should bind to a local interface */
+  public void testFileCreationSetLocalInterface() throws IOException {
+    assumeTrue(System.getProperty("os.name").startsWith("Linux"));
+
+    // The mini cluster listens on the loopback so we can use it here
+    checkFileCreation(null, "lo");
+
+    try {
+      checkFileCreation(null, "bogus-interface");
+      fail("Able to specify a bogus interface");
+    } catch (UnknownHostException e) {
+      assertEquals("Unknown interface bogus-interface", e.getMessage());
+    }
   }
 
   /**
    * Test that file data becomes available before file is closed.
-   * @param useDnHostname if clients should access DNs by hostname (vs IP)
+   * @param hostname the hostname, if any, clients should use to access DNs
+   * @param netIf the local interface, if any, clients should use to access DNs
    */
-  public void checkFileCreation(boolean useDnHostname) throws IOException {
+  public void checkFileCreation(String hostname, String netIf) throws IOException {
     Configuration conf = new Configuration();
 
-    conf.setBoolean(DFSConfigKeys.DFS_CLIENT_USE_DN_HOSTNAME, useDnHostname);
-    if (useDnHostname) {
-      // Since the mini cluster only listens on the loopback we have to
-      // ensure the hostname used to access DNs maps to the loopback. We
-      // do this by telling the DN to advertise localhost as its hostname
-      // instead of the default hostname.
-      conf.set("slave.host.name", "localhost");
+    if (hostname != null) {
+      conf.setBoolean(DFSConfigKeys.DFS_CLIENT_USE_DN_HOSTNAME, true);
+      conf.set("slave.host.name", hostname);
+    }
+    if (netIf != null) {
+      conf.set(DFSConfigKeys.DFS_CLIENT_LOCAL_INTERFACES, netIf);
     }
-
     if (simulatedStorage) {
       conf.setBoolean(SimulatedFSDataset.CONFIG_PROPERTY_SIMULATED, true);
     }