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);
+
+    }
 }

Reply via email to