diff --git a/CHANGES.txt b/CHANGES.txt index b933bad0c8..af7f2fd913 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.1.0-beta2 + * Auto reload GossipingPropertyFileSnitch config (CASSANDRA-5897) * Fix overflow of memtable_total_space_in_mb (CASSANDRA-6573) * Fix ABTC NPE (CASSANDRA-6692) * Allow nodetool to use a file or prompt for password (CASSANDRA-6660) diff --git a/src/java/org/apache/cassandra/locator/Ec2Snitch.java b/src/java/org/apache/cassandra/locator/Ec2Snitch.java index 216a224b65..59eb27bb2b 100644 --- a/src/java/org/apache/cassandra/locator/Ec2Snitch.java +++ b/src/java/org/apache/cassandra/locator/Ec2Snitch.java @@ -62,7 +62,7 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch if (ec2region.endsWith("1")) ec2region = az.substring(0, az.length() - 3); - String datacenterSuffix = SnitchProperties.get("dc_suffix", ""); + String datacenterSuffix = (new SnitchProperties()).get("dc_suffix", ""); ec2region = ec2region.concat(datacenterSuffix); logger.info("EC2Snitch using region: {}, zone: {}.", ec2region, ec2zone); } diff --git a/src/java/org/apache/cassandra/locator/GossipingPropertyFileSnitch.java b/src/java/org/apache/cassandra/locator/GossipingPropertyFileSnitch.java index 83c1efe72d..720e804ea5 100644 --- a/src/java/org/apache/cassandra/locator/GossipingPropertyFileSnitch.java +++ b/src/java/org/apache/cassandra/locator/GossipingPropertyFileSnitch.java @@ -19,6 +19,7 @@ package org.apache.cassandra.locator; import java.net.InetAddress; +import java.util.concurrent.atomic.AtomicReference; import java.util.Map; import org.slf4j.Logger; @@ -29,8 +30,10 @@ import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.gms.ApplicationState; import org.apache.cassandra.gms.EndpointState; import org.apache.cassandra.gms.Gossiper; -import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.utils.FBUtilities; +import org.apache.cassandra.utils.ResourceWatcher; +import org.apache.cassandra.utils.WrappedRunnable; public class GossipingPropertyFileSnitch extends AbstractNetworkTopologySnitch// implements IEndpointStateChangeSubscriber @@ -38,23 +41,30 @@ public class GossipingPropertyFileSnitch extends AbstractNetworkTopologySnitch// private static final Logger logger = LoggerFactory.getLogger(GossipingPropertyFileSnitch.class); private PropertyFileSnitch psnitch; - private String myDC; - private String myRack; - private Map> savedEndpoints; - private String DEFAULT_DC = "UNKNOWN_DC"; - private String DEFAULT_RACK = "UNKNOWN_RACK"; - private final boolean preferLocal; + private volatile String myDC; + private volatile String myRack; + private volatile boolean preferLocal; + private AtomicReference snitchHelperReference; + private volatile boolean gossipStarted; + + private Map> savedEndpoints; + private static final String DEFAULT_DC = "UNKNOWN_DC"; + private static final String DEFAULT_RACK = "UNKNOWN_RACK"; + + private static final int DEFAULT_REFRESH_PERIOD_IN_SECONDS = 60; + public GossipingPropertyFileSnitch() throws ConfigurationException { - myDC = SnitchProperties.get("dc", null); - myRack = SnitchProperties.get("rack", null); - if (myDC == null || myRack == null) - throw new ConfigurationException("DC or rack not found in snitch properties, check your configuration in: " + SnitchProperties.RACKDC_PROPERTY_FILENAME); + this(DEFAULT_REFRESH_PERIOD_IN_SECONDS); + } + + public GossipingPropertyFileSnitch(int refreshPeriodInSeconds) throws ConfigurationException + { + snitchHelperReference = new AtomicReference(); + + reloadConfiguration(); - myDC = myDC.trim(); - myRack = myRack.trim(); - preferLocal = Boolean.parseBoolean(SnitchProperties.get("prefer_local", "false")); try { psnitch = new PropertyFileSnitch(); @@ -64,6 +74,23 @@ public class GossipingPropertyFileSnitch extends AbstractNetworkTopologySnitch// { logger.info("Unable to load {}; compatibility mode disabled", PropertyFileSnitch.SNITCH_PROPERTIES_FILENAME); } + + try + { + FBUtilities.resourceToFile(SnitchProperties.RACKDC_PROPERTY_FILENAME); + Runnable runnable = new WrappedRunnable() + { + protected void runMayThrow() throws ConfigurationException + { + reloadConfiguration(); + } + }; + ResourceWatcher.watch(SnitchProperties.RACKDC_PROPERTY_FILENAME, runnable, refreshPeriodInSeconds * 1000); + } + catch (ConfigurationException ex) + { + logger.error("{} found, but does not look like a plain file. Will not watch it for changes", SnitchProperties.RACKDC_PROPERTY_FILENAME); + } } /** @@ -125,8 +152,55 @@ public class GossipingPropertyFileSnitch extends AbstractNetworkTopologySnitch// public void gossiperStarting() { super.gossiperStarting(); + Gossiper.instance.addLocalApplicationState(ApplicationState.INTERNAL_IP, - StorageService.instance.valueFactory.internalIP(FBUtilities.getLocalAddress().getHostAddress())); - Gossiper.instance.register(new ReconnectableSnitchHelper(this, myDC, preferLocal)); + StorageService.instance.valueFactory.internalIP(FBUtilities.getLocalAddress().getHostAddress())); + + reloadGossiperState(); + + gossipStarted = true; + } + + private void reloadConfiguration() throws ConfigurationException + { + final SnitchProperties properties = new SnitchProperties(); + + String newDc = properties.get("dc", null); + String newRack = properties.get("rack", null); + if (newDc == null || newRack == null) + throw new ConfigurationException("DC or rack not found in snitch properties, check your configuration in: " + SnitchProperties.RACKDC_PROPERTY_FILENAME); + + newDc = newDc.trim(); + newRack = newRack.trim(); + final boolean newPreferLocal = Boolean.parseBoolean(properties.get("prefer_local", "false")); + + if (myDC != newDc || myRack != newRack || (preferLocal != newPreferLocal)) + { + myDC = newDc; + myRack = newRack; + preferLocal = newPreferLocal; + + reloadGossiperState(); + + if (StorageService.instance != null) + StorageService.instance.getTokenMetadata().invalidateCachedRings(); + + if (gossipStarted) + StorageService.instance.gossipSnitchInfo(); + } + } + + private void reloadGossiperState() + { + if (Gossiper.instance != null) + { + ReconnectableSnitchHelper pendingHelper = new ReconnectableSnitchHelper(this, myDC, preferLocal); + Gossiper.instance.register(pendingHelper); + + pendingHelper = snitchHelperReference.getAndSet(pendingHelper); + if (pendingHelper != null) + Gossiper.instance.unregister(pendingHelper); + } + // else this will eventually rerun at gossiperStarting() } } diff --git a/src/java/org/apache/cassandra/locator/PropertyFileSnitch.java b/src/java/org/apache/cassandra/locator/PropertyFileSnitch.java index 90dddcc004..d235369605 100644 --- a/src/java/org/apache/cassandra/locator/PropertyFileSnitch.java +++ b/src/java/org/apache/cassandra/locator/PropertyFileSnitch.java @@ -73,7 +73,7 @@ public class PropertyFileSnitch extends AbstractNetworkTopologySnitch } catch (ConfigurationException ex) { - logger.debug("{} found, but does not look like a plain file. Will not watch it for changes", SNITCH_PROPERTIES_FILENAME); + logger.error("{} found, but does not look like a plain file. Will not watch it for changes", SNITCH_PROPERTIES_FILENAME); } } diff --git a/src/java/org/apache/cassandra/locator/SnitchProperties.java b/src/java/org/apache/cassandra/locator/SnitchProperties.java index a8b2d0353d..57541d4014 100644 --- a/src/java/org/apache/cassandra/locator/SnitchProperties.java +++ b/src/java/org/apache/cassandra/locator/SnitchProperties.java @@ -28,10 +28,12 @@ public class SnitchProperties { private static final Logger logger = LoggerFactory.getLogger(SnitchProperties.class); public static final String RACKDC_PROPERTY_FILENAME = "cassandra-rackdc.properties"; - private static Properties properties = new Properties(); - static + private Properties properties; + + public SnitchProperties() { + properties = new Properties(); InputStream stream = SnitchProperties.class.getClassLoader().getResourceAsStream(RACKDC_PROPERTY_FILENAME); try { @@ -49,9 +51,9 @@ public class SnitchProperties } /** - * Get a snitch property value or return null if not defined. + * Get a snitch property value or return defaultValue if not defined. */ - public static String get(String propertyName, String defaultValue) + public String get(String propertyName, String defaultValue) { return properties.getProperty(propertyName, defaultValue); } diff --git a/test/conf/cassandra-rackdc.properties.mod b/test/conf/cassandra-rackdc.properties.mod new file mode 100644 index 0000000000..457ad65c2d --- /dev/null +++ b/test/conf/cassandra-rackdc.properties.mod @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +dc=DC2 +rack=RAC2 diff --git a/test/unit/org/apache/cassandra/locator/GossipingPropertyFileSnitchTest.java b/test/unit/org/apache/cassandra/locator/GossipingPropertyFileSnitchTest.java new file mode 100644 index 0000000000..9026ebf3fc --- /dev/null +++ b/test/unit/org/apache/cassandra/locator/GossipingPropertyFileSnitchTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.locator; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.cassandra.utils.FBUtilities; +import org.junit.Test; + +/** + * Unit tests for {@link GossipingPropertyFileSnitch}. + */ +public class GossipingPropertyFileSnitchTest +{ + @Test + public void testAutoReloadConfig() throws Exception + { + String confFile = FBUtilities.resourceToFile(SnitchProperties.RACKDC_PROPERTY_FILENAME); + + final GossipingPropertyFileSnitch snitch = new GossipingPropertyFileSnitch(/*refreshPeriodInSeconds*/1); + YamlFileNetworkTopologySnitchTest.checkEndpoint(snitch, FBUtilities.getBroadcastAddress().getHostAddress(), "DC1", "RAC1"); + + final Path effectiveFile = Paths.get(confFile); + final Path backupFile = Paths.get(confFile + ".bak"); + final Path modifiedFile = Paths.get(confFile + ".mod"); + + try + { + Files.copy(effectiveFile, backupFile); + Files.copy(modifiedFile, effectiveFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + + Thread.sleep(1500); + + YamlFileNetworkTopologySnitchTest.checkEndpoint(snitch, FBUtilities.getBroadcastAddress().getHostAddress(), "DC2", "RAC2"); + } + finally + { + Files.copy(backupFile, effectiveFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING); + Files.delete(backupFile); + } + } +} diff --git a/test/unit/org/apache/cassandra/locator/YamlFileNetworkTopologySnitchTest.java b/test/unit/org/apache/cassandra/locator/YamlFileNetworkTopologySnitchTest.java index be1c24b49a..af1a7e94cd 100644 --- a/test/unit/org/apache/cassandra/locator/YamlFileNetworkTopologySnitchTest.java +++ b/test/unit/org/apache/cassandra/locator/YamlFileNetworkTopologySnitchTest.java @@ -88,7 +88,7 @@ public class YamlFileNetworkTopologySnitchTest * @param expectedRack * expected rack */ - private void checkEndpoint(final AbstractNetworkTopologySnitch snitch, + public static void checkEndpoint(final AbstractNetworkTopologySnitch snitch, final String endpointString, final String expectedDatacenter, final String expectedRack) {