You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sn...@apache.org on 2015/03/17 19:33:03 UTC

[1/3] cassandra git commit: DatabaseDescriptor throws NPE when rpc_interface is used

Repository: cassandra
Updated Branches:
  refs/heads/cassandra-2.1 c0f159b82 -> 89cdfd8e0
  refs/heads/trunk ac9ccfdf7 -> 8d570fa79


DatabaseDescriptor throws NPE when rpc_interface is used

patch by Ariel Weisberg; reviewed by Carl Yeksigian for CASSANDRA-8839


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/89cdfd8e
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/89cdfd8e
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/89cdfd8e

Branch: refs/heads/cassandra-2.1
Commit: 89cdfd8e075d8883d776d7f881735f1c25e3cb54
Parents: c0f159b
Author: Ariel Weisberg <ar...@weisberg.ws>
Authored: Tue Mar 17 19:27:54 2015 +0100
Committer: Robert Stupp <sn...@snazy.de>
Committed: Tue Mar 17 19:27:54 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 conf/cassandra.yaml                             |  12 ++
 .../org/apache/cassandra/config/Config.java     |   6 +-
 .../cassandra/config/DatabaseDescriptor.java    | 205 +++++++++++--------
 .../config/YamlConfigurationLoader.java         |  16 +-
 .../config/DatabaseDescriptorTest.java          | 139 ++++++++++++-
 6 files changed, 277 insertions(+), 102 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 7b8e0ad..30bf698 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.1.4
+ * DatabaseDescriptor throws NPE when rpc_interface is used (CASSANDRA-8839)
  * Don't check if an sstable is live for offline compactions (CASSANDRA-8841)
  * Don't set clientMode in SSTableLoader (CASSANDRA-8238)
  * Fix SSTableRewriter with disabled early open (CASSANDRA-8535)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/conf/cassandra.yaml
----------------------------------------------------------------------
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index cea12b3..2b43ba7 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -370,8 +370,14 @@ ssl_storage_port: 7001
 # address associated with the hostname (it might not be).
 #
 # Setting listen_address to 0.0.0.0 is always wrong.
+#
+# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address
+# you can specify which should be chosen using listen_interface_prefer_ipv6. If false the first ipv4
+# address will be used. If true the first ipv6 address will be used. Defaults to false preferring
+# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.
 listen_address: localhost
 # listen_interface: eth0
+# listen_interface_prefer_ipv6: false
 
 # Address to broadcast to other Cassandra nodes
 # Leaving this blank will set it to the same value as listen_address
@@ -422,8 +428,14 @@ start_rpc: true
 # set broadcast_rpc_address to a value other than 0.0.0.0.
 #
 # For security reasons, you should not expose this port to the internet.  Firewall it if needed.
+#
+# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address
+# you can specify which should be chosen using rpc_interface_prefer_ipv6. If false the first ipv4
+# address will be used. If true the first ipv6 address will be used. Defaults to false preferring
+# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.
 rpc_address: localhost
 # rpc_interface: eth1
+# rpc_interface_prefer_ipv6: false
 
 # port for Thrift to listen for clients on
 rpc_port: 9160

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/src/java/org/apache/cassandra/config/Config.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java
index ccd4467..fbbd1dd 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -34,7 +34,7 @@ import org.apache.cassandra.utils.FBUtilities;
 
 /**
  * A class that contains configuration properties for the cassandra node it runs within.
- * 
+ *
  * Properties declared as volatile can be mutated via JMX.
  */
 public class Config
@@ -101,12 +101,14 @@ public class Config
     public Integer ssl_storage_port = 7001;
     public String listen_address;
     public String listen_interface;
+    public Boolean listen_interface_prefer_ipv6 = false;
     public String broadcast_address;
     public String internode_authenticator;
 
     public Boolean start_rpc = true;
     public String rpc_address;
     public String rpc_interface;
+    public Boolean rpc_interface_prefer_ipv6 = false;
     public String broadcast_rpc_address;
     public Integer rpc_port = 9160;
     public Integer rpc_listen_backlog = 50;
@@ -155,7 +157,7 @@ public class Config
     public Double commitlog_sync_batch_window_in_ms;
     public Integer commitlog_sync_period_in_ms;
     public int commitlog_segment_size_in_mb = 32;
- 
+
     @Deprecated
     public int commitlog_periodic_queue_size = -1;
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 4426f20..65cec9c 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -20,6 +20,8 @@ package org.apache.cassandra.config;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -38,6 +40,7 @@ import java.util.UUID;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.primitives.Longs;
+
 import org.apache.cassandra.thrift.ThriftServer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -158,7 +161,7 @@ public class DatabaseDescriptor
         return loader.loadConfig();
     }
 
-    private static InetAddress getNetworkInterfaceAddress(String intf, String configName) throws ConfigurationException
+    private static InetAddress getNetworkInterfaceAddress(String intf, String configName, boolean preferIPv6) throws ConfigurationException
     {
         try
         {
@@ -168,9 +171,18 @@ public class DatabaseDescriptor
             Enumeration<InetAddress> addrs = ni.getInetAddresses();
             if (!addrs.hasMoreElements())
                 throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" was found, but had no addresses");
-            InetAddress retval = listenAddress = addrs.nextElement();
-            if (addrs.hasMoreElements())
-                throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" can't have more than one address");
+
+            /*
+             * Try to return the first address of the preferred type, otherwise return the first address
+             */
+            InetAddress retval = null;
+            while (addrs.hasMoreElements())
+            {
+                InetAddress temp = addrs.nextElement();
+                if (preferIPv6 && temp.getClass() == Inet6Address.class) return temp;
+                if (!preferIPv6 && temp.getClass() == Inet4Address.class) return temp;
+                if (retval == null) retval = temp;
+            }
             return retval;
         }
         catch (SocketException e)
@@ -179,6 +191,103 @@ public class DatabaseDescriptor
         }
     }
 
+    @VisibleForTesting
+    static void applyAddressConfig(Config config) throws ConfigurationException
+    {
+        listenAddress = null;
+        rpcAddress = null;
+        broadcastAddress = null;
+        broadcastRpcAddress = null;
+
+        /* Local IP, hostname or interface to bind services to */
+        if (config.listen_address != null && config.listen_interface != null)
+        {
+            throw new ConfigurationException("Set listen_address OR listen_interface, not both");
+        }
+        else if (config.listen_address != null)
+        {
+            try
+            {
+                listenAddress = InetAddress.getByName(config.listen_address);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConfigurationException("Unknown listen_address '" + config.listen_address + "'");
+            }
+
+            if (listenAddress.isAnyLocalAddress())
+                throw new ConfigurationException("listen_address cannot be a wildcard address (" + config.listen_address + ")!");
+        }
+        else if (config.listen_interface != null)
+        {
+            listenAddress = getNetworkInterfaceAddress(config.listen_interface, "listen_interface", config.listen_interface_prefer_ipv6);
+        }
+
+        /* Gossip Address to broadcast */
+        if (config.broadcast_address != null)
+        {
+            try
+            {
+                broadcastAddress = InetAddress.getByName(config.broadcast_address);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConfigurationException("Unknown broadcast_address '" + config.broadcast_address + "'");
+            }
+
+            if (broadcastAddress.isAnyLocalAddress())
+                throw new ConfigurationException("broadcast_address cannot be a wildcard address (" + config.broadcast_address + ")!");
+        }
+
+        /* Local IP, hostname or interface to bind RPC server to */
+        if (config.rpc_address != null && config.rpc_interface != null)
+        {
+            throw new ConfigurationException("Set rpc_address OR rpc_interface, not both");
+        }
+        else if (config.rpc_address != null)
+        {
+            try
+            {
+                rpcAddress = InetAddress.getByName(config.rpc_address);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConfigurationException("Unknown host in rpc_address " + config.rpc_address);
+            }
+        }
+        else if (config.rpc_interface != null)
+        {
+            rpcAddress = getNetworkInterfaceAddress(config.rpc_interface, "rpc_interface", config.rpc_interface_prefer_ipv6);
+        }
+        else
+        {
+            rpcAddress = FBUtilities.getLocalAddress();
+        }
+
+        /* RPC address to broadcast */
+        if (config.broadcast_rpc_address != null)
+        {
+            try
+            {
+                broadcastRpcAddress = InetAddress.getByName(config.broadcast_rpc_address);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConfigurationException("Unknown broadcast_rpc_address '" + config.broadcast_rpc_address + "'");
+            }
+
+            if (broadcastRpcAddress.isAnyLocalAddress())
+                throw new ConfigurationException("broadcast_rpc_address cannot be a wildcard address (" + config.broadcast_rpc_address + ")!");
+        }
+        else
+        {
+            if (rpcAddress.isAnyLocalAddress())
+                throw new ConfigurationException("If rpc_address is set to a wildcard address (" + config.rpc_address + "), then " +
+                                                 "you must set broadcast_rpc_address to a value other than " + config.rpc_address);
+            broadcastRpcAddress = rpcAddress;
+        }
+    }
+
     private static void applyConfig(Config config) throws ConfigurationException
     {
         conf = config;
@@ -326,93 +435,7 @@ public class DatabaseDescriptor
         else
             logger.info("Global memtable off-heap threshold is enabled at {}MB", conf.memtable_offheap_space_in_mb);
 
-        /* Local IP, hostname or interface to bind services to */
-        if (conf.listen_address != null && conf.listen_interface != null)
-        {
-            throw new ConfigurationException("Set listen_address OR listen_interface, not both");
-        }
-        else if (conf.listen_address != null)
-        {
-            try
-            {
-                listenAddress = InetAddress.getByName(conf.listen_address);
-            }
-            catch (UnknownHostException e)
-            {
-                throw new ConfigurationException("Unknown listen_address '" + conf.listen_address + "'");
-            }
-
-            if (listenAddress.isAnyLocalAddress())
-                throw new ConfigurationException("listen_address cannot be a wildcard address (" + conf.listen_address + ")!");
-        }
-        else if (conf.listen_interface != null)
-        {
-            listenAddress = getNetworkInterfaceAddress(conf.listen_interface, "listen_interface");
-        }
-
-        /* Gossip Address to broadcast */
-        if (conf.broadcast_address != null)
-        {
-            try
-            {
-                broadcastAddress = InetAddress.getByName(conf.broadcast_address);
-            }
-            catch (UnknownHostException e)
-            {
-                throw new ConfigurationException("Unknown broadcast_address '" + conf.broadcast_address + "'");
-            }
-
-            if (broadcastAddress.isAnyLocalAddress())
-                throw new ConfigurationException("broadcast_address cannot be a wildcard address (" + conf.broadcast_address + ")!");
-        }
-
-        /* Local IP, hostname or interface to bind RPC server to */
-        if (conf.rpc_address != null && conf.rpc_interface != null)
-        {
-            throw new ConfigurationException("Set rpc_address OR rpc_interface, not both");
-        }
-        else if (conf.rpc_address != null)
-        {
-            try
-            {
-                rpcAddress = InetAddress.getByName(conf.rpc_address);
-            }
-            catch (UnknownHostException e)
-            {
-                throw new ConfigurationException("Unknown host in rpc_address " + conf.rpc_address);
-            }
-        }
-        else if (conf.rpc_interface != null)
-        {
-            listenAddress = getNetworkInterfaceAddress(conf.rpc_interface, "rpc_interface");
-        }
-        else
-        {
-            rpcAddress = FBUtilities.getLocalAddress();
-        }
-
-        /* RPC address to broadcast */
-        if (conf.broadcast_rpc_address != null)
-        {
-            try
-            {
-                broadcastRpcAddress = InetAddress.getByName(conf.broadcast_rpc_address);
-            }
-            catch (UnknownHostException e)
-            {
-                throw new ConfigurationException("Unknown broadcast_rpc_address '" + conf.broadcast_rpc_address + "'");
-            }
-
-            if (broadcastRpcAddress.isAnyLocalAddress())
-                throw new ConfigurationException("broadcast_rpc_address cannot be a wildcard address (" + conf.broadcast_rpc_address + ")!");
-        }
-        else
-        {
-            if (rpcAddress.isAnyLocalAddress())
-                throw new ConfigurationException("If rpc_address is set to a wildcard address (" + conf.rpc_address + "), then " +
-                                                 "you must set broadcast_rpc_address to a value other than " + conf.rpc_address);
-            broadcastRpcAddress = rpcAddress;
-        }
+        applyAddressConfig(config);
 
         if (conf.thrift_framed_transport_size_in_mb <= 0)
             throw new ConfigurationException("thrift_framed_transport_size_in_mb must be positive");

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
index e222046..0061926 100644
--- a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
+++ b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
@@ -50,7 +50,7 @@ public class YamlConfigurationLoader implements ConfigurationLoader
     /**
      * Inspect the classpath to find storage configuration file
      */
-    private URL getStorageConfigURL() throws ConfigurationException
+    static URL getStorageConfigURL() throws ConfigurationException
     {
         String configUrl = System.getProperty("cassandra.config");
         if (configUrl == null)
@@ -100,7 +100,7 @@ public class YamlConfigurationLoader implements ConfigurationLoader
                 // getStorageConfigURL should have ruled this out
                 throw new AssertionError(e);
             }
-            
+
             logConfig(configBytes);
             
             org.yaml.snakeyaml.constructor.Constructor constructor = new org.yaml.snakeyaml.constructor.Constructor(Config.class);
@@ -134,16 +134,16 @@ public class YamlConfigurationLoader implements ConfigurationLoader
         }
         logger.info("Node configuration:[" + Joiner.on("; ").join(configMap.entrySet()) + "]");
     }
-    
-    private static class MissingPropertiesChecker extends PropertyUtils 
+
+    private static class MissingPropertiesChecker extends PropertyUtils
     {
         private final Set<String> missingProperties = new HashSet<>();
-        
+
         public MissingPropertiesChecker()
         {
             setSkipMissingProperties(true);
         }
-        
+
         @Override
         public Property getProperty(Class<? extends Object> type, String name) throws IntrospectionException
         {
@@ -154,10 +154,10 @@ public class YamlConfigurationLoader implements ConfigurationLoader
             }
             return result;
         }
-        
+
         public void check() throws ConfigurationException
         {
-            if (!missingProperties.isEmpty()) 
+            if (!missingProperties.isEmpty())
             {
                 throw new ConfigurationException("Invalid yaml. Please remove properties " + missingProperties + " from your cassandra.yaml");
             }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
index f6d4ad4..46522cc 100644
--- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
+++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
@@ -18,9 +18,17 @@
 */
 package org.apache.cassandra.config;
 
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+
+import junit.framework.Assert;
+
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
 import org.apache.cassandra.OrderedJUnit4ClassRunner;
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.db.Keyspace;
@@ -126,4 +134,133 @@ public class DatabaseDescriptorTest
             return testConfig;
         }
     }
+
+    static NetworkInterface suitableInterface = null;
+    static boolean hasIPv4andIPv6 = false;
+
+    /*
+     * Server only accepts interfaces by name if they have a single address
+     * OS X seems to always have an ipv4 and ipv6 address on all interfaces which means some tests fail
+     * if not checked for and skipped
+     */
+    @BeforeClass
+    public static void selectSuitableInterface() throws Exception {
+        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+        while(interfaces.hasMoreElements()) {
+            NetworkInterface intf = interfaces.nextElement();
+
+            System.out.println("Evaluating " + intf.getName());
+
+            if (intf.isLoopback()) {
+                suitableInterface = intf;
+
+                boolean hasIPv4 = false;
+                boolean hasIPv6 = false;
+                Enumeration<InetAddress> addresses = suitableInterface.getInetAddresses();
+                while (addresses.hasMoreElements()) {
+                    if (addresses.nextElement().getClass() == Inet6Address.class)
+                        hasIPv6 = true;
+                    else
+                        hasIPv4 = true;
+                }
+                hasIPv4andIPv6 = hasIPv4 && hasIPv6;
+                return;
+            }
+        }
+    }
+
+    @Test
+    public void testRpcInterface() throws Exception
+    {
+        Config testConfig = DatabaseDescriptor.loadConfig();
+        testConfig.rpc_interface = suitableInterface.getName();
+        testConfig.rpc_address = null;
+        DatabaseDescriptor.applyAddressConfig(testConfig);
+
+        /*
+         * Confirm ability to select between IPv4 and IPv6
+         */
+        if (hasIPv4andIPv6)
+        {
+            testConfig = DatabaseDescriptor.loadConfig();
+            testConfig.rpc_interface = suitableInterface.getName();
+            testConfig.rpc_address = null;
+            testConfig.rpc_interface_prefer_ipv6 = true;
+            DatabaseDescriptor.applyAddressConfig(testConfig);
+
+            assertEquals(DatabaseDescriptor.getRpcAddress().getClass(), Inet6Address.class);
+
+            testConfig = DatabaseDescriptor.loadConfig();
+            testConfig.rpc_interface = suitableInterface.getName();
+            testConfig.rpc_address = null;
+            testConfig.rpc_interface_prefer_ipv6 = false;
+            DatabaseDescriptor.applyAddressConfig(testConfig);
+
+            assertEquals(DatabaseDescriptor.getRpcAddress().getClass(), Inet4Address.class);
+        }
+        else
+        {
+            /*
+             * Confirm first address of interface is selected
+             */
+            assertEquals(DatabaseDescriptor.getRpcAddress(), suitableInterface.getInetAddresses().nextElement());
+        }
+    }
+
+    @Test
+    public void testListenInterface() throws Exception
+    {
+        Config testConfig = DatabaseDescriptor.loadConfig();
+        testConfig.listen_interface = suitableInterface.getName();
+        testConfig.listen_address = null;
+        DatabaseDescriptor.applyAddressConfig(testConfig);
+
+        /*
+         * Confirm ability to select between IPv4 and IPv6
+         */
+        if (hasIPv4andIPv6)
+        {
+            testConfig = DatabaseDescriptor.loadConfig();
+            testConfig.listen_interface = suitableInterface.getName();
+            testConfig.listen_address = null;
+            testConfig.listen_interface_prefer_ipv6 = true;
+            DatabaseDescriptor.applyAddressConfig(testConfig);
+
+            assertEquals(DatabaseDescriptor.getListenAddress().getClass(), Inet6Address.class);
+
+            testConfig = DatabaseDescriptor.loadConfig();
+            testConfig.listen_interface = suitableInterface.getName();
+            testConfig.listen_address = null;
+            testConfig.listen_interface_prefer_ipv6 = false;
+            DatabaseDescriptor.applyAddressConfig(testConfig);
+
+            assertEquals(DatabaseDescriptor.getListenAddress().getClass(), Inet4Address.class);
+        }
+        else
+        {
+            /*
+             * Confirm first address of interface is selected
+             */
+            assertEquals(DatabaseDescriptor.getRpcAddress(), suitableInterface.getInetAddresses().nextElement());
+        }
+    }
+
+    @Test
+    public void testListenAddress() throws Exception
+    {
+        Config testConfig = DatabaseDescriptor.loadConfig();
+        testConfig.listen_address = suitableInterface.getInterfaceAddresses().get(0).getAddress().getHostAddress();
+        testConfig.listen_interface = null;
+        DatabaseDescriptor.applyAddressConfig(testConfig);
+    }
+
+    @Test
+    public void testRpcAddress() throws Exception
+    {
+        Config testConfig = DatabaseDescriptor.loadConfig();
+        testConfig.rpc_address = suitableInterface.getInterfaceAddresses().get(0).getAddress().getHostAddress();
+        testConfig.rpc_interface = null;
+        DatabaseDescriptor.applyAddressConfig(testConfig);
+
+    }
 }


[2/3] cassandra git commit: DatabaseDescriptor throws NPE when rpc_interface is used

Posted by sn...@apache.org.
DatabaseDescriptor throws NPE when rpc_interface is used

patch by Ariel Weisberg; reviewed by Carl Yeksigian for CASSANDRA-8839


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/89cdfd8e
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/89cdfd8e
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/89cdfd8e

Branch: refs/heads/trunk
Commit: 89cdfd8e075d8883d776d7f881735f1c25e3cb54
Parents: c0f159b
Author: Ariel Weisberg <ar...@weisberg.ws>
Authored: Tue Mar 17 19:27:54 2015 +0100
Committer: Robert Stupp <sn...@snazy.de>
Committed: Tue Mar 17 19:27:54 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 conf/cassandra.yaml                             |  12 ++
 .../org/apache/cassandra/config/Config.java     |   6 +-
 .../cassandra/config/DatabaseDescriptor.java    | 205 +++++++++++--------
 .../config/YamlConfigurationLoader.java         |  16 +-
 .../config/DatabaseDescriptorTest.java          | 139 ++++++++++++-
 6 files changed, 277 insertions(+), 102 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 7b8e0ad..30bf698 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.1.4
+ * DatabaseDescriptor throws NPE when rpc_interface is used (CASSANDRA-8839)
  * Don't check if an sstable is live for offline compactions (CASSANDRA-8841)
  * Don't set clientMode in SSTableLoader (CASSANDRA-8238)
  * Fix SSTableRewriter with disabled early open (CASSANDRA-8535)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/conf/cassandra.yaml
----------------------------------------------------------------------
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index cea12b3..2b43ba7 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -370,8 +370,14 @@ ssl_storage_port: 7001
 # address associated with the hostname (it might not be).
 #
 # Setting listen_address to 0.0.0.0 is always wrong.
+#
+# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address
+# you can specify which should be chosen using listen_interface_prefer_ipv6. If false the first ipv4
+# address will be used. If true the first ipv6 address will be used. Defaults to false preferring
+# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.
 listen_address: localhost
 # listen_interface: eth0
+# listen_interface_prefer_ipv6: false
 
 # Address to broadcast to other Cassandra nodes
 # Leaving this blank will set it to the same value as listen_address
@@ -422,8 +428,14 @@ start_rpc: true
 # set broadcast_rpc_address to a value other than 0.0.0.0.
 #
 # For security reasons, you should not expose this port to the internet.  Firewall it if needed.
+#
+# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address
+# you can specify which should be chosen using rpc_interface_prefer_ipv6. If false the first ipv4
+# address will be used. If true the first ipv6 address will be used. Defaults to false preferring
+# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.
 rpc_address: localhost
 # rpc_interface: eth1
+# rpc_interface_prefer_ipv6: false
 
 # port for Thrift to listen for clients on
 rpc_port: 9160

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/src/java/org/apache/cassandra/config/Config.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java
index ccd4467..fbbd1dd 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -34,7 +34,7 @@ import org.apache.cassandra.utils.FBUtilities;
 
 /**
  * A class that contains configuration properties for the cassandra node it runs within.
- * 
+ *
  * Properties declared as volatile can be mutated via JMX.
  */
 public class Config
@@ -101,12 +101,14 @@ public class Config
     public Integer ssl_storage_port = 7001;
     public String listen_address;
     public String listen_interface;
+    public Boolean listen_interface_prefer_ipv6 = false;
     public String broadcast_address;
     public String internode_authenticator;
 
     public Boolean start_rpc = true;
     public String rpc_address;
     public String rpc_interface;
+    public Boolean rpc_interface_prefer_ipv6 = false;
     public String broadcast_rpc_address;
     public Integer rpc_port = 9160;
     public Integer rpc_listen_backlog = 50;
@@ -155,7 +157,7 @@ public class Config
     public Double commitlog_sync_batch_window_in_ms;
     public Integer commitlog_sync_period_in_ms;
     public int commitlog_segment_size_in_mb = 32;
- 
+
     @Deprecated
     public int commitlog_periodic_queue_size = -1;
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 4426f20..65cec9c 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -20,6 +20,8 @@ package org.apache.cassandra.config;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -38,6 +40,7 @@ import java.util.UUID;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.primitives.Longs;
+
 import org.apache.cassandra.thrift.ThriftServer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -158,7 +161,7 @@ public class DatabaseDescriptor
         return loader.loadConfig();
     }
 
-    private static InetAddress getNetworkInterfaceAddress(String intf, String configName) throws ConfigurationException
+    private static InetAddress getNetworkInterfaceAddress(String intf, String configName, boolean preferIPv6) throws ConfigurationException
     {
         try
         {
@@ -168,9 +171,18 @@ public class DatabaseDescriptor
             Enumeration<InetAddress> addrs = ni.getInetAddresses();
             if (!addrs.hasMoreElements())
                 throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" was found, but had no addresses");
-            InetAddress retval = listenAddress = addrs.nextElement();
-            if (addrs.hasMoreElements())
-                throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" can't have more than one address");
+
+            /*
+             * Try to return the first address of the preferred type, otherwise return the first address
+             */
+            InetAddress retval = null;
+            while (addrs.hasMoreElements())
+            {
+                InetAddress temp = addrs.nextElement();
+                if (preferIPv6 && temp.getClass() == Inet6Address.class) return temp;
+                if (!preferIPv6 && temp.getClass() == Inet4Address.class) return temp;
+                if (retval == null) retval = temp;
+            }
             return retval;
         }
         catch (SocketException e)
@@ -179,6 +191,103 @@ public class DatabaseDescriptor
         }
     }
 
+    @VisibleForTesting
+    static void applyAddressConfig(Config config) throws ConfigurationException
+    {
+        listenAddress = null;
+        rpcAddress = null;
+        broadcastAddress = null;
+        broadcastRpcAddress = null;
+
+        /* Local IP, hostname or interface to bind services to */
+        if (config.listen_address != null && config.listen_interface != null)
+        {
+            throw new ConfigurationException("Set listen_address OR listen_interface, not both");
+        }
+        else if (config.listen_address != null)
+        {
+            try
+            {
+                listenAddress = InetAddress.getByName(config.listen_address);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConfigurationException("Unknown listen_address '" + config.listen_address + "'");
+            }
+
+            if (listenAddress.isAnyLocalAddress())
+                throw new ConfigurationException("listen_address cannot be a wildcard address (" + config.listen_address + ")!");
+        }
+        else if (config.listen_interface != null)
+        {
+            listenAddress = getNetworkInterfaceAddress(config.listen_interface, "listen_interface", config.listen_interface_prefer_ipv6);
+        }
+
+        /* Gossip Address to broadcast */
+        if (config.broadcast_address != null)
+        {
+            try
+            {
+                broadcastAddress = InetAddress.getByName(config.broadcast_address);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConfigurationException("Unknown broadcast_address '" + config.broadcast_address + "'");
+            }
+
+            if (broadcastAddress.isAnyLocalAddress())
+                throw new ConfigurationException("broadcast_address cannot be a wildcard address (" + config.broadcast_address + ")!");
+        }
+
+        /* Local IP, hostname or interface to bind RPC server to */
+        if (config.rpc_address != null && config.rpc_interface != null)
+        {
+            throw new ConfigurationException("Set rpc_address OR rpc_interface, not both");
+        }
+        else if (config.rpc_address != null)
+        {
+            try
+            {
+                rpcAddress = InetAddress.getByName(config.rpc_address);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConfigurationException("Unknown host in rpc_address " + config.rpc_address);
+            }
+        }
+        else if (config.rpc_interface != null)
+        {
+            rpcAddress = getNetworkInterfaceAddress(config.rpc_interface, "rpc_interface", config.rpc_interface_prefer_ipv6);
+        }
+        else
+        {
+            rpcAddress = FBUtilities.getLocalAddress();
+        }
+
+        /* RPC address to broadcast */
+        if (config.broadcast_rpc_address != null)
+        {
+            try
+            {
+                broadcastRpcAddress = InetAddress.getByName(config.broadcast_rpc_address);
+            }
+            catch (UnknownHostException e)
+            {
+                throw new ConfigurationException("Unknown broadcast_rpc_address '" + config.broadcast_rpc_address + "'");
+            }
+
+            if (broadcastRpcAddress.isAnyLocalAddress())
+                throw new ConfigurationException("broadcast_rpc_address cannot be a wildcard address (" + config.broadcast_rpc_address + ")!");
+        }
+        else
+        {
+            if (rpcAddress.isAnyLocalAddress())
+                throw new ConfigurationException("If rpc_address is set to a wildcard address (" + config.rpc_address + "), then " +
+                                                 "you must set broadcast_rpc_address to a value other than " + config.rpc_address);
+            broadcastRpcAddress = rpcAddress;
+        }
+    }
+
     private static void applyConfig(Config config) throws ConfigurationException
     {
         conf = config;
@@ -326,93 +435,7 @@ public class DatabaseDescriptor
         else
             logger.info("Global memtable off-heap threshold is enabled at {}MB", conf.memtable_offheap_space_in_mb);
 
-        /* Local IP, hostname or interface to bind services to */
-        if (conf.listen_address != null && conf.listen_interface != null)
-        {
-            throw new ConfigurationException("Set listen_address OR listen_interface, not both");
-        }
-        else if (conf.listen_address != null)
-        {
-            try
-            {
-                listenAddress = InetAddress.getByName(conf.listen_address);
-            }
-            catch (UnknownHostException e)
-            {
-                throw new ConfigurationException("Unknown listen_address '" + conf.listen_address + "'");
-            }
-
-            if (listenAddress.isAnyLocalAddress())
-                throw new ConfigurationException("listen_address cannot be a wildcard address (" + conf.listen_address + ")!");
-        }
-        else if (conf.listen_interface != null)
-        {
-            listenAddress = getNetworkInterfaceAddress(conf.listen_interface, "listen_interface");
-        }
-
-        /* Gossip Address to broadcast */
-        if (conf.broadcast_address != null)
-        {
-            try
-            {
-                broadcastAddress = InetAddress.getByName(conf.broadcast_address);
-            }
-            catch (UnknownHostException e)
-            {
-                throw new ConfigurationException("Unknown broadcast_address '" + conf.broadcast_address + "'");
-            }
-
-            if (broadcastAddress.isAnyLocalAddress())
-                throw new ConfigurationException("broadcast_address cannot be a wildcard address (" + conf.broadcast_address + ")!");
-        }
-
-        /* Local IP, hostname or interface to bind RPC server to */
-        if (conf.rpc_address != null && conf.rpc_interface != null)
-        {
-            throw new ConfigurationException("Set rpc_address OR rpc_interface, not both");
-        }
-        else if (conf.rpc_address != null)
-        {
-            try
-            {
-                rpcAddress = InetAddress.getByName(conf.rpc_address);
-            }
-            catch (UnknownHostException e)
-            {
-                throw new ConfigurationException("Unknown host in rpc_address " + conf.rpc_address);
-            }
-        }
-        else if (conf.rpc_interface != null)
-        {
-            listenAddress = getNetworkInterfaceAddress(conf.rpc_interface, "rpc_interface");
-        }
-        else
-        {
-            rpcAddress = FBUtilities.getLocalAddress();
-        }
-
-        /* RPC address to broadcast */
-        if (conf.broadcast_rpc_address != null)
-        {
-            try
-            {
-                broadcastRpcAddress = InetAddress.getByName(conf.broadcast_rpc_address);
-            }
-            catch (UnknownHostException e)
-            {
-                throw new ConfigurationException("Unknown broadcast_rpc_address '" + conf.broadcast_rpc_address + "'");
-            }
-
-            if (broadcastRpcAddress.isAnyLocalAddress())
-                throw new ConfigurationException("broadcast_rpc_address cannot be a wildcard address (" + conf.broadcast_rpc_address + ")!");
-        }
-        else
-        {
-            if (rpcAddress.isAnyLocalAddress())
-                throw new ConfigurationException("If rpc_address is set to a wildcard address (" + conf.rpc_address + "), then " +
-                                                 "you must set broadcast_rpc_address to a value other than " + conf.rpc_address);
-            broadcastRpcAddress = rpcAddress;
-        }
+        applyAddressConfig(config);
 
         if (conf.thrift_framed_transport_size_in_mb <= 0)
             throw new ConfigurationException("thrift_framed_transport_size_in_mb must be positive");

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
index e222046..0061926 100644
--- a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
+++ b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
@@ -50,7 +50,7 @@ public class YamlConfigurationLoader implements ConfigurationLoader
     /**
      * Inspect the classpath to find storage configuration file
      */
-    private URL getStorageConfigURL() throws ConfigurationException
+    static URL getStorageConfigURL() throws ConfigurationException
     {
         String configUrl = System.getProperty("cassandra.config");
         if (configUrl == null)
@@ -100,7 +100,7 @@ public class YamlConfigurationLoader implements ConfigurationLoader
                 // getStorageConfigURL should have ruled this out
                 throw new AssertionError(e);
             }
-            
+
             logConfig(configBytes);
             
             org.yaml.snakeyaml.constructor.Constructor constructor = new org.yaml.snakeyaml.constructor.Constructor(Config.class);
@@ -134,16 +134,16 @@ public class YamlConfigurationLoader implements ConfigurationLoader
         }
         logger.info("Node configuration:[" + Joiner.on("; ").join(configMap.entrySet()) + "]");
     }
-    
-    private static class MissingPropertiesChecker extends PropertyUtils 
+
+    private static class MissingPropertiesChecker extends PropertyUtils
     {
         private final Set<String> missingProperties = new HashSet<>();
-        
+
         public MissingPropertiesChecker()
         {
             setSkipMissingProperties(true);
         }
-        
+
         @Override
         public Property getProperty(Class<? extends Object> type, String name) throws IntrospectionException
         {
@@ -154,10 +154,10 @@ public class YamlConfigurationLoader implements ConfigurationLoader
             }
             return result;
         }
-        
+
         public void check() throws ConfigurationException
         {
-            if (!missingProperties.isEmpty()) 
+            if (!missingProperties.isEmpty())
             {
                 throw new ConfigurationException("Invalid yaml. Please remove properties " + missingProperties + " from your cassandra.yaml");
             }

http://git-wip-us.apache.org/repos/asf/cassandra/blob/89cdfd8e/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
index f6d4ad4..46522cc 100644
--- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
+++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
@@ -18,9 +18,17 @@
 */
 package org.apache.cassandra.config;
 
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.Enumeration;
+
+import junit.framework.Assert;
+
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
 import org.apache.cassandra.OrderedJUnit4ClassRunner;
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.db.Keyspace;
@@ -126,4 +134,133 @@ public class DatabaseDescriptorTest
             return testConfig;
         }
     }
+
+    static NetworkInterface suitableInterface = null;
+    static boolean hasIPv4andIPv6 = false;
+
+    /*
+     * Server only accepts interfaces by name if they have a single address
+     * OS X seems to always have an ipv4 and ipv6 address on all interfaces which means some tests fail
+     * if not checked for and skipped
+     */
+    @BeforeClass
+    public static void selectSuitableInterface() throws Exception {
+        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+        while(interfaces.hasMoreElements()) {
+            NetworkInterface intf = interfaces.nextElement();
+
+            System.out.println("Evaluating " + intf.getName());
+
+            if (intf.isLoopback()) {
+                suitableInterface = intf;
+
+                boolean hasIPv4 = false;
+                boolean hasIPv6 = false;
+                Enumeration<InetAddress> addresses = suitableInterface.getInetAddresses();
+                while (addresses.hasMoreElements()) {
+                    if (addresses.nextElement().getClass() == Inet6Address.class)
+                        hasIPv6 = true;
+                    else
+                        hasIPv4 = true;
+                }
+                hasIPv4andIPv6 = hasIPv4 && hasIPv6;
+                return;
+            }
+        }
+    }
+
+    @Test
+    public void testRpcInterface() throws Exception
+    {
+        Config testConfig = DatabaseDescriptor.loadConfig();
+        testConfig.rpc_interface = suitableInterface.getName();
+        testConfig.rpc_address = null;
+        DatabaseDescriptor.applyAddressConfig(testConfig);
+
+        /*
+         * Confirm ability to select between IPv4 and IPv6
+         */
+        if (hasIPv4andIPv6)
+        {
+            testConfig = DatabaseDescriptor.loadConfig();
+            testConfig.rpc_interface = suitableInterface.getName();
+            testConfig.rpc_address = null;
+            testConfig.rpc_interface_prefer_ipv6 = true;
+            DatabaseDescriptor.applyAddressConfig(testConfig);
+
+            assertEquals(DatabaseDescriptor.getRpcAddress().getClass(), Inet6Address.class);
+
+            testConfig = DatabaseDescriptor.loadConfig();
+            testConfig.rpc_interface = suitableInterface.getName();
+            testConfig.rpc_address = null;
+            testConfig.rpc_interface_prefer_ipv6 = false;
+            DatabaseDescriptor.applyAddressConfig(testConfig);
+
+            assertEquals(DatabaseDescriptor.getRpcAddress().getClass(), Inet4Address.class);
+        }
+        else
+        {
+            /*
+             * Confirm first address of interface is selected
+             */
+            assertEquals(DatabaseDescriptor.getRpcAddress(), suitableInterface.getInetAddresses().nextElement());
+        }
+    }
+
+    @Test
+    public void testListenInterface() throws Exception
+    {
+        Config testConfig = DatabaseDescriptor.loadConfig();
+        testConfig.listen_interface = suitableInterface.getName();
+        testConfig.listen_address = null;
+        DatabaseDescriptor.applyAddressConfig(testConfig);
+
+        /*
+         * Confirm ability to select between IPv4 and IPv6
+         */
+        if (hasIPv4andIPv6)
+        {
+            testConfig = DatabaseDescriptor.loadConfig();
+            testConfig.listen_interface = suitableInterface.getName();
+            testConfig.listen_address = null;
+            testConfig.listen_interface_prefer_ipv6 = true;
+            DatabaseDescriptor.applyAddressConfig(testConfig);
+
+            assertEquals(DatabaseDescriptor.getListenAddress().getClass(), Inet6Address.class);
+
+            testConfig = DatabaseDescriptor.loadConfig();
+            testConfig.listen_interface = suitableInterface.getName();
+            testConfig.listen_address = null;
+            testConfig.listen_interface_prefer_ipv6 = false;
+            DatabaseDescriptor.applyAddressConfig(testConfig);
+
+            assertEquals(DatabaseDescriptor.getListenAddress().getClass(), Inet4Address.class);
+        }
+        else
+        {
+            /*
+             * Confirm first address of interface is selected
+             */
+            assertEquals(DatabaseDescriptor.getRpcAddress(), suitableInterface.getInetAddresses().nextElement());
+        }
+    }
+
+    @Test
+    public void testListenAddress() throws Exception
+    {
+        Config testConfig = DatabaseDescriptor.loadConfig();
+        testConfig.listen_address = suitableInterface.getInterfaceAddresses().get(0).getAddress().getHostAddress();
+        testConfig.listen_interface = null;
+        DatabaseDescriptor.applyAddressConfig(testConfig);
+    }
+
+    @Test
+    public void testRpcAddress() throws Exception
+    {
+        Config testConfig = DatabaseDescriptor.loadConfig();
+        testConfig.rpc_address = suitableInterface.getInterfaceAddresses().get(0).getAddress().getHostAddress();
+        testConfig.rpc_interface = null;
+        DatabaseDescriptor.applyAddressConfig(testConfig);
+
+    }
 }


[3/3] cassandra git commit: Merge branch 'cassandra-2.1' into trunk

Posted by sn...@apache.org.
Merge branch 'cassandra-2.1' into trunk


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/8d570fa7
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/8d570fa7
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/8d570fa7

Branch: refs/heads/trunk
Commit: 8d570fa7992f8eaae55e8c13c57e6e84491062ee
Parents: ac9ccfd 89cdfd8
Author: Ariel Weisberg <ar...@weisberg.ws>
Authored: Tue Mar 17 19:31:54 2015 +0100
Committer: Robert Stupp <sn...@snazy.de>
Committed: Tue Mar 17 19:31:54 2015 +0100

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 conf/cassandra.yaml                             |  12 ++
 .../org/apache/cassandra/config/Config.java     |   6 +-
 .../cassandra/config/DatabaseDescriptor.java    | 202 ++++++++++---------
 .../config/YamlConfigurationLoader.java         |  16 +-
 .../config/DatabaseDescriptorTest.java          | 139 ++++++++++++-
 6 files changed, 274 insertions(+), 102 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/8d570fa7/CHANGES.txt
----------------------------------------------------------------------
diff --cc CHANGES.txt
index be57f1b,30bf698..f813496
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@@ -1,76 -1,5 +1,77 @@@
 +3.0
 + * Add WriteFailureException to native protocol, notify coordinator of
 +   write failures (CASSANDRA-8592)
 + * Convert SequentialWriter to nio (CASSANDRA-8709)
 + * Add role based access control (CASSANDRA-7653, 8650, 7216, 8760, 8849, 8761, 8850)
 + * Record client ip address in tracing sessions (CASSANDRA-8162)
 + * Indicate partition key columns in response metadata for prepared
 +   statements (CASSANDRA-7660)
 + * Merge UUIDType and TimeUUIDType parse logic (CASSANDRA-8759)
 + * Avoid memory allocation when searching index summary (CASSANDRA-8793)
 + * Optimise (Time)?UUIDType Comparisons (CASSANDRA-8730)
 + * Make CRC32Ex into a separate maven dependency (CASSANDRA-8836)
 + * Use preloaded jemalloc w/ Unsafe (CASSANDRA-8714)
 + * Avoid accessing partitioner through StorageProxy (CASSANDRA-8244, 8268)
 + * Upgrade Metrics library and remove depricated metrics (CASSANDRA-5657)
 + * Serializing Row cache alternative, fully off heap (CASSANDRA-7438)
 + * Duplicate rows returned when in clause has repeated values (CASSANDRA-6707)
 + * Make CassandraException unchecked, extend RuntimeException (CASSANDRA-8560)
 + * Support direct buffer decompression for reads (CASSANDRA-8464)
 + * DirectByteBuffer compatible LZ4 methods (CASSANDRA-7039)
 + * Group sstables for anticompaction correctly (CASSANDRA-8578)
 + * Add ReadFailureException to native protocol, respond
 +   immediately when replicas encounter errors while handling
 +   a read request (CASSANDRA-7886)
 + * Switch CommitLogSegment from RandomAccessFile to nio (CASSANDRA-8308)
 + * Allow mixing token and partition key restrictions (CASSANDRA-7016)
 + * Support index key/value entries on map collections (CASSANDRA-8473)
 + * Modernize schema tables (CASSANDRA-8261)
 + * Support for user-defined aggregation functions (CASSANDRA-8053)
 + * Fix NPE in SelectStatement with empty IN values (CASSANDRA-8419)
 + * Refactor SelectStatement, return IN results in natural order instead
 +   of IN value list order and ignore duplicate values in partition key IN restrictions (CASSANDRA-7981)
 + * Support UDTs, tuples, and collections in user-defined
 +   functions (CASSANDRA-7563)
 + * Fix aggregate fn results on empty selection, result column name,
 +   and cqlsh parsing (CASSANDRA-8229)
 + * Mark sstables as repaired after full repair (CASSANDRA-7586)
 + * Extend Descriptor to include a format value and refactor reader/writer
 +   APIs (CASSANDRA-7443)
 + * Integrate JMH for microbenchmarks (CASSANDRA-8151)
 + * Keep sstable levels when bootstrapping (CASSANDRA-7460)
 + * Add Sigar library and perform basic OS settings check on startup (CASSANDRA-7838)
 + * Support for aggregation functions (CASSANDRA-4914)
 + * Remove cassandra-cli (CASSANDRA-7920)
 + * Accept dollar quoted strings in CQL (CASSANDRA-7769)
 + * Make assassinate a first class command (CASSANDRA-7935)
 + * Support IN clause on any partition key column (CASSANDRA-7855)
 + * Support IN clause on any clustering column (CASSANDRA-4762)
 + * Improve compaction logging (CASSANDRA-7818)
 + * Remove YamlFileNetworkTopologySnitch (CASSANDRA-7917)
 + * Do anticompaction in groups (CASSANDRA-6851)
 + * Support user-defined functions (CASSANDRA-7395, 7526, 7562, 7740, 7781, 7929,
 +   7924, 7812, 8063, 7813, 7708)
 + * Permit configurable timestamps with cassandra-stress (CASSANDRA-7416)
 + * Move sstable RandomAccessReader to nio2, which allows using the
 +   FILE_SHARE_DELETE flag on Windows (CASSANDRA-4050)
 + * Remove CQL2 (CASSANDRA-5918)
 + * Add Thrift get_multi_slice call (CASSANDRA-6757)
 + * Optimize fetching multiple cells by name (CASSANDRA-6933)
 + * Allow compilation in java 8 (CASSANDRA-7028)
 + * Make incremental repair default (CASSANDRA-7250)
 + * Enable code coverage thru JaCoCo (CASSANDRA-7226)
 + * Switch external naming of 'column families' to 'tables' (CASSANDRA-4369) 
 + * Shorten SSTable path (CASSANDRA-6962)
 + * Use unsafe mutations for most unit tests (CASSANDRA-6969)
 + * Fix race condition during calculation of pending ranges (CASSANDRA-7390)
 + * Fail on very large batch sizes (CASSANDRA-8011)
 + * Improve concurrency of repair (CASSANDRA-6455, 8208)
 + * Select optimal CRC32 implementation at runtime (CASSANDRA-8614)
 + * Evaluate MurmurHash of Token once per query (CASSANDRA-7096)
 + * Generalize progress reporting (CASSANDRA-8901)
 +
  2.1.4
+  * DatabaseDescriptor throws NPE when rpc_interface is used (CASSANDRA-8839)
   * Don't check if an sstable is live for offline compactions (CASSANDRA-8841)
   * Don't set clientMode in SSTableLoader (CASSANDRA-8238)
   * Fix SSTableRewriter with disabled early open (CASSANDRA-8535)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/8d570fa7/conf/cassandra.yaml
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/8d570fa7/src/java/org/apache/cassandra/config/Config.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cassandra/blob/8d570fa7/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 35ce728,65cec9c..fa88de8
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@@ -131,13 -167,22 +131,22 @@@ public class DatabaseDescripto
          {
              NetworkInterface ni = NetworkInterface.getByName(intf);
              if (ni == null)
 -                throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" could not be found");
 +                throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" could not be found", false);
              Enumeration<InetAddress> addrs = ni.getInetAddresses();
              if (!addrs.hasMoreElements())
 -                throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" was found, but had no addresses");
 +                throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" was found, but had no addresses", false);
-             InetAddress retval = listenAddress = addrs.nextElement();
-             if (addrs.hasMoreElements())
-                 throw new ConfigurationException("Configured " + configName + " \"" + intf + "\" can't have more than one address", false);
+ 
+             /*
+              * Try to return the first address of the preferred type, otherwise return the first address
+              */
+             InetAddress retval = null;
+             while (addrs.hasMoreElements())
+             {
+                 InetAddress temp = addrs.nextElement();
 -                if (preferIPv6 && temp.getClass() == Inet6Address.class) return temp;
 -                if (!preferIPv6 && temp.getClass() == Inet4Address.class) return temp;
++                if (preferIPv6 && temp instanceof Inet6Address) return temp;
++                if (!preferIPv6 && temp instanceof Inet4Address) return temp;
+                 if (retval == null) retval = temp;
+             }
              return retval;
          }
          catch (SocketException e)
@@@ -146,6 -191,103 +155,103 @@@
          }
      }
  
+     @VisibleForTesting
+     static void applyAddressConfig(Config config) throws ConfigurationException
+     {
+         listenAddress = null;
+         rpcAddress = null;
+         broadcastAddress = null;
+         broadcastRpcAddress = null;
+ 
+         /* Local IP, hostname or interface to bind services to */
+         if (config.listen_address != null && config.listen_interface != null)
+         {
 -            throw new ConfigurationException("Set listen_address OR listen_interface, not both");
++            throw new ConfigurationException("Set listen_address OR listen_interface, not both", false);
+         }
+         else if (config.listen_address != null)
+         {
+             try
+             {
+                 listenAddress = InetAddress.getByName(config.listen_address);
+             }
+             catch (UnknownHostException e)
+             {
 -                throw new ConfigurationException("Unknown listen_address '" + config.listen_address + "'");
++                throw new ConfigurationException("Unknown listen_address '" + config.listen_address + "'", false);
+             }
+ 
+             if (listenAddress.isAnyLocalAddress())
 -                throw new ConfigurationException("listen_address cannot be a wildcard address (" + config.listen_address + ")!");
++                throw new ConfigurationException("listen_address cannot be a wildcard address (" + config.listen_address + ")!", false);
+         }
+         else if (config.listen_interface != null)
+         {
+             listenAddress = getNetworkInterfaceAddress(config.listen_interface, "listen_interface", config.listen_interface_prefer_ipv6);
+         }
+ 
+         /* Gossip Address to broadcast */
+         if (config.broadcast_address != null)
+         {
+             try
+             {
+                 broadcastAddress = InetAddress.getByName(config.broadcast_address);
+             }
+             catch (UnknownHostException e)
+             {
 -                throw new ConfigurationException("Unknown broadcast_address '" + config.broadcast_address + "'");
++                throw new ConfigurationException("Unknown broadcast_address '" + config.broadcast_address + "'", false);
+             }
+ 
+             if (broadcastAddress.isAnyLocalAddress())
 -                throw new ConfigurationException("broadcast_address cannot be a wildcard address (" + config.broadcast_address + ")!");
++                throw new ConfigurationException("broadcast_address cannot be a wildcard address (" + config.broadcast_address + ")!", false);
+         }
+ 
+         /* Local IP, hostname or interface to bind RPC server to */
+         if (config.rpc_address != null && config.rpc_interface != null)
+         {
 -            throw new ConfigurationException("Set rpc_address OR rpc_interface, not both");
++            throw new ConfigurationException("Set rpc_address OR rpc_interface, not both", false);
+         }
+         else if (config.rpc_address != null)
+         {
+             try
+             {
+                 rpcAddress = InetAddress.getByName(config.rpc_address);
+             }
+             catch (UnknownHostException e)
+             {
 -                throw new ConfigurationException("Unknown host in rpc_address " + config.rpc_address);
++                throw new ConfigurationException("Unknown host in rpc_address " + config.rpc_address, false);
+             }
+         }
+         else if (config.rpc_interface != null)
+         {
+             rpcAddress = getNetworkInterfaceAddress(config.rpc_interface, "rpc_interface", config.rpc_interface_prefer_ipv6);
+         }
+         else
+         {
+             rpcAddress = FBUtilities.getLocalAddress();
+         }
+ 
+         /* RPC address to broadcast */
+         if (config.broadcast_rpc_address != null)
+         {
+             try
+             {
+                 broadcastRpcAddress = InetAddress.getByName(config.broadcast_rpc_address);
+             }
+             catch (UnknownHostException e)
+             {
 -                throw new ConfigurationException("Unknown broadcast_rpc_address '" + config.broadcast_rpc_address + "'");
++                throw new ConfigurationException("Unknown broadcast_rpc_address '" + config.broadcast_rpc_address + "'", false);
+             }
+ 
+             if (broadcastRpcAddress.isAnyLocalAddress())
 -                throw new ConfigurationException("broadcast_rpc_address cannot be a wildcard address (" + config.broadcast_rpc_address + ")!");
++                throw new ConfigurationException("broadcast_rpc_address cannot be a wildcard address (" + config.broadcast_rpc_address + ")!", false);
+         }
+         else
+         {
+             if (rpcAddress.isAnyLocalAddress())
+                 throw new ConfigurationException("If rpc_address is set to a wildcard address (" + config.rpc_address + "), then " +
 -                                                 "you must set broadcast_rpc_address to a value other than " + config.rpc_address);
++                                                 "you must set broadcast_rpc_address to a value other than " + config.rpc_address, false);
+             broadcastRpcAddress = rpcAddress;
+         }
+     }
+ 
      private static void applyConfig(Config config) throws ConfigurationException
      {
          conf = config;
@@@ -300,99 -435,13 +406,13 @@@
          else
              logger.info("Global memtable off-heap threshold is enabled at {}MB", conf.memtable_offheap_space_in_mb);
  
-         /* Local IP, hostname or interface to bind services to */
-         if (conf.listen_address != null && conf.listen_interface != null)
-         {
-             throw new ConfigurationException("Set listen_address OR listen_interface, not both", false);
-         }
-         else if (conf.listen_address != null)
-         {
-             try
-             {
-                 listenAddress = InetAddress.getByName(conf.listen_address);
-             }
-             catch (UnknownHostException e)
-             {
-                 throw new ConfigurationException("Unknown listen_address '" + conf.listen_address + "'", false);
-             }
- 
-             if (listenAddress.isAnyLocalAddress())
-                 throw new ConfigurationException("listen_address cannot be a wildcard address (" + conf.listen_address + ")!", false);
-         }
-         else if (conf.listen_interface != null)
-         {
-             listenAddress = getNetworkInterfaceAddress(conf.listen_interface, "listen_interface");
-         }
- 
-         /* Gossip Address to broadcast */
-         if (conf.broadcast_address != null)
-         {
-             try
-             {
-                 broadcastAddress = InetAddress.getByName(conf.broadcast_address);
-             }
-             catch (UnknownHostException e)
-             {
-                 throw new ConfigurationException("Unknown broadcast_address '" + conf.broadcast_address + "'", false);
-             }
- 
-             if (broadcastAddress.isAnyLocalAddress())
-                 throw new ConfigurationException("broadcast_address cannot be a wildcard address (" + conf.broadcast_address + ")!", false);
-         }
- 
-         /* Local IP, hostname or interface to bind RPC server to */
-         if (conf.rpc_address != null && conf.rpc_interface != null)
-         {
-             throw new ConfigurationException("Set rpc_address OR rpc_interface, not both", false);
-         }
-         else if (conf.rpc_address != null)
-         {
-             try
-             {
-                 rpcAddress = InetAddress.getByName(conf.rpc_address);
-             }
-             catch (UnknownHostException e)
-             {
-                 throw new ConfigurationException("Unknown host in rpc_address " + conf.rpc_address, false);
-             }
-         }
-         else if (conf.rpc_interface != null)
-         {
-             rpcAddress = getNetworkInterfaceAddress(conf.rpc_interface, "rpc_interface");
-         }
-         else
-         {
-             rpcAddress = FBUtilities.getLocalAddress();
-         }
- 
-         /* RPC address to broadcast */
-         if (conf.broadcast_rpc_address != null)
-         {
-             try
-             {
-                 broadcastRpcAddress = InetAddress.getByName(conf.broadcast_rpc_address);
-             }
-             catch (UnknownHostException e)
-             {
-                 throw new ConfigurationException("Unknown broadcast_rpc_address '" + conf.broadcast_rpc_address + "'", false);
-             }
- 
-             if (broadcastRpcAddress.isAnyLocalAddress())
-                 throw new ConfigurationException("broadcast_rpc_address cannot be a wildcard address (" + conf.broadcast_rpc_address + ")!", false);
-         }
-         else
-         {
-             if (rpcAddress.isAnyLocalAddress())
-                 throw new ConfigurationException("If rpc_address is set to a wildcard address (" + conf.rpc_address + "), then " +
-                                                  "you must set broadcast_rpc_address to a value other than " + conf.rpc_address, false);
-             broadcastRpcAddress = rpcAddress;
-         }
+         applyAddressConfig(config);
  
          if (conf.thrift_framed_transport_size_in_mb <= 0)
 -            throw new ConfigurationException("thrift_framed_transport_size_in_mb must be positive");
 +            throw new ConfigurationException("thrift_framed_transport_size_in_mb must be positive", false);
  
          if (conf.native_transport_max_frame_size_in_mb <= 0)
 -            throw new ConfigurationException("native_transport_max_frame_size_in_mb must be positive");
 +            throw new ConfigurationException("native_transport_max_frame_size_in_mb must be positive", false);
  
          // fail early instead of OOMing (see CASSANDRA-8116)
          if (ThriftServer.HSHA.equals(conf.rpc_server_type) && conf.rpc_max_threads == Integer.MAX_VALUE)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/8d570fa7/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
----------------------------------------------------------------------
diff --cc src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
index e8847ba,0061926..8b8d75f
--- a/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
+++ b/src/java/org/apache/cassandra/config/YamlConfigurationLoader.java
@@@ -100,9 -100,9 +100,9 @@@ public class YamlConfigurationLoader im
                  // getStorageConfigURL should have ruled this out
                  throw new AssertionError(e);
              }
-             
+ 
              logConfig(configBytes);
 -            
 +
              org.yaml.snakeyaml.constructor.Constructor constructor = new org.yaml.snakeyaml.constructor.Constructor(Config.class);
              TypeDescription seedDesc = new TypeDescription(SeedProviderDef.class);
              seedDesc.putMapPropertyType("parameters", String.class, String.class);
@@@ -132,13 -132,13 +132,13 @@@
                  configMap.put(sensitiveKey, "<REDACTED>");
              }
          }
 -        logger.info("Node configuration:[" + Joiner.on("; ").join(configMap.entrySet()) + "]");
 +        logger.info("Node configuration:[{}]", Joiner.on("; ").join(configMap.entrySet()));
      }
-     
-     private static class MissingPropertiesChecker extends PropertyUtils 
+ 
+     private static class MissingPropertiesChecker extends PropertyUtils
      {
          private final Set<String> missingProperties = new HashSet<>();
-         
+ 
          public MissingPropertiesChecker()
          {
              setSkipMissingProperties(true);

http://git-wip-us.apache.org/repos/asf/cassandra/blob/8d570fa7/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
----------------------------------------------------------------------
diff --cc test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
index 8ca7a79,46522cc..57c4194
--- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
+++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
@@@ -128,4 -134,133 +136,133 @@@ public class DatabaseDescriptorTes
              return testConfig;
          }
      }
+ 
+     static NetworkInterface suitableInterface = null;
+     static boolean hasIPv4andIPv6 = false;
+ 
+     /*
+      * Server only accepts interfaces by name if they have a single address
+      * OS X seems to always have an ipv4 and ipv6 address on all interfaces which means some tests fail
+      * if not checked for and skipped
+      */
+     @BeforeClass
+     public static void selectSuitableInterface() throws Exception {
+         Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+         while(interfaces.hasMoreElements()) {
+             NetworkInterface intf = interfaces.nextElement();
+ 
+             System.out.println("Evaluating " + intf.getName());
+ 
+             if (intf.isLoopback()) {
+                 suitableInterface = intf;
+ 
+                 boolean hasIPv4 = false;
+                 boolean hasIPv6 = false;
+                 Enumeration<InetAddress> addresses = suitableInterface.getInetAddresses();
+                 while (addresses.hasMoreElements()) {
 -                    if (addresses.nextElement().getClass() == Inet6Address.class)
++                    if (addresses.nextElement() instanceof Inet6Address)
+                         hasIPv6 = true;
+                     else
+                         hasIPv4 = true;
+                 }
+                 hasIPv4andIPv6 = hasIPv4 && hasIPv6;
+                 return;
+             }
+         }
+     }
+ 
+     @Test
+     public void testRpcInterface() throws Exception
+     {
+         Config testConfig = DatabaseDescriptor.loadConfig();
+         testConfig.rpc_interface = suitableInterface.getName();
+         testConfig.rpc_address = null;
+         DatabaseDescriptor.applyAddressConfig(testConfig);
+ 
+         /*
+          * Confirm ability to select between IPv4 and IPv6
+          */
+         if (hasIPv4andIPv6)
+         {
+             testConfig = DatabaseDescriptor.loadConfig();
+             testConfig.rpc_interface = suitableInterface.getName();
+             testConfig.rpc_address = null;
+             testConfig.rpc_interface_prefer_ipv6 = true;
+             DatabaseDescriptor.applyAddressConfig(testConfig);
+ 
+             assertEquals(DatabaseDescriptor.getRpcAddress().getClass(), Inet6Address.class);
+ 
+             testConfig = DatabaseDescriptor.loadConfig();
+             testConfig.rpc_interface = suitableInterface.getName();
+             testConfig.rpc_address = null;
+             testConfig.rpc_interface_prefer_ipv6 = false;
+             DatabaseDescriptor.applyAddressConfig(testConfig);
+ 
+             assertEquals(DatabaseDescriptor.getRpcAddress().getClass(), Inet4Address.class);
+         }
+         else
+         {
+             /*
+              * Confirm first address of interface is selected
+              */
+             assertEquals(DatabaseDescriptor.getRpcAddress(), suitableInterface.getInetAddresses().nextElement());
+         }
+     }
+ 
+     @Test
+     public void testListenInterface() throws Exception
+     {
+         Config testConfig = DatabaseDescriptor.loadConfig();
+         testConfig.listen_interface = suitableInterface.getName();
+         testConfig.listen_address = null;
+         DatabaseDescriptor.applyAddressConfig(testConfig);
+ 
+         /*
+          * Confirm ability to select between IPv4 and IPv6
+          */
+         if (hasIPv4andIPv6)
+         {
+             testConfig = DatabaseDescriptor.loadConfig();
+             testConfig.listen_interface = suitableInterface.getName();
+             testConfig.listen_address = null;
+             testConfig.listen_interface_prefer_ipv6 = true;
+             DatabaseDescriptor.applyAddressConfig(testConfig);
+ 
+             assertEquals(DatabaseDescriptor.getListenAddress().getClass(), Inet6Address.class);
+ 
+             testConfig = DatabaseDescriptor.loadConfig();
+             testConfig.listen_interface = suitableInterface.getName();
+             testConfig.listen_address = null;
+             testConfig.listen_interface_prefer_ipv6 = false;
+             DatabaseDescriptor.applyAddressConfig(testConfig);
+ 
+             assertEquals(DatabaseDescriptor.getListenAddress().getClass(), Inet4Address.class);
+         }
+         else
+         {
+             /*
+              * Confirm first address of interface is selected
+              */
+             assertEquals(DatabaseDescriptor.getRpcAddress(), suitableInterface.getInetAddresses().nextElement());
+         }
+     }
+ 
+     @Test
+     public void testListenAddress() throws Exception
+     {
+         Config testConfig = DatabaseDescriptor.loadConfig();
+         testConfig.listen_address = suitableInterface.getInterfaceAddresses().get(0).getAddress().getHostAddress();
+         testConfig.listen_interface = null;
+         DatabaseDescriptor.applyAddressConfig(testConfig);
+     }
+ 
+     @Test
+     public void testRpcAddress() throws Exception
+     {
+         Config testConfig = DatabaseDescriptor.loadConfig();
+         testConfig.rpc_address = suitableInterface.getInterfaceAddresses().get(0).getAddress().getHostAddress();
+         testConfig.rpc_interface = null;
+         DatabaseDescriptor.applyAddressConfig(testConfig);
+ 
+     }
  }