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