mirror of https://github.com/apache/cassandra
Use standard Amazon naming for datacenter and rack in Ec2Snitch.
patch by Gregory Ramsperger and jasobrown; reviewed by Joey Lynch for CASSANDRA-7839
This commit is contained in:
parent
8a5e1cbe29
commit
fd44a69fce
|
|
@ -1,4 +1,5 @@
|
|||
4.0
|
||||
* Use standard Amazon naming for datacenter and rack in Ec2Snitch (CASSANDRA-7839)
|
||||
* Fix junit failure for SSTableReaderTest (CASSANDRA-14387)
|
||||
* Abstract write path for pluggable storage (CASSANDRA-14118)
|
||||
* nodetool describecluster should be more informative (CASSANDRA-13853)
|
||||
|
|
|
|||
4
NEWS.txt
4
NEWS.txt
|
|
@ -141,6 +141,10 @@ Upgrading
|
|||
storage port or native port at nodes you must first upgrade the entire cluster
|
||||
and clients to 4.0 so they can handle the port not being consistent across
|
||||
the cluster.
|
||||
- Names of AWS regions/availability zones have been cleaned up to more correctly
|
||||
match the Amazon names. There is now a new option in conf/cassandra-rackdc.properties
|
||||
that lets users enable the correct names for new clusters, or use the legacy
|
||||
names for existing clusters. See conf/cassandra-rackdc.properties for details.
|
||||
|
||||
Materialized Views
|
||||
-------------------
|
||||
|
|
|
|||
|
|
@ -25,3 +25,15 @@ rack=rack1
|
|||
|
||||
# Uncomment the following line to make this snitch prefer the internal ip when possible, as the Ec2MultiRegionSnitch does.
|
||||
# prefer_local=true
|
||||
|
||||
# Datacenter and rack naming convention used by the Ec2Snitch and Ec2MultiRegionSnitch.
|
||||
# Options are:
|
||||
# legacy : datacenter name is the part of the availability zone name preceding the last "-"
|
||||
# when the zone ends in -1 and includes the number if not -1. Rack is the portion of
|
||||
# the availability zone name following the last "-".
|
||||
# Examples: us-west-1a => dc: us-west, rack: 1a; us-west-2b => dc: us-west-2, rack: 2b;
|
||||
# YOU MUST USE THIS VALUE IF YOU ARE UPGRADING A PRE-4.0 CLUSTER
|
||||
# standard : Default value. datacenter name is the standard AWS region name, including the number.
|
||||
# rack name is the region plus the availability zone letter.
|
||||
# Examples: us-west-1a => dc: us-west-1, rack: us-west-1a; us-west-2b => dc: us-west-2, rack: us-west-2b;
|
||||
# ec2_naming_scheme=standard
|
||||
|
|
|
|||
|
|
@ -448,4 +448,9 @@ public class DynamicEndpointSnitch extends AbstractEndpointSnitch implements ILa
|
|||
}
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
public boolean validate(Set<String> datacenters, Set<String> racks)
|
||||
{
|
||||
return subsnitch.validate(datacenters, racks);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ import java.net.HttpURLConnection;
|
|||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
|
|
@ -42,30 +44,63 @@ import org.apache.cassandra.utils.FBUtilities;
|
|||
public class Ec2Snitch extends AbstractNetworkTopologySnitch
|
||||
{
|
||||
protected static final Logger logger = LoggerFactory.getLogger(Ec2Snitch.class);
|
||||
protected static final String ZONE_NAME_QUERY_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone";
|
||||
|
||||
private static final String SNITCH_PROP_NAMING_SCHEME = "ec2_naming_scheme";
|
||||
static final String EC2_NAMING_LEGACY = "legacy";
|
||||
private static final String EC2_NAMING_STANDARD = "standard";
|
||||
|
||||
private static final String ZONE_NAME_QUERY_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone";
|
||||
private static final String DEFAULT_DC = "UNKNOWN-DC";
|
||||
private static final String DEFAULT_RACK = "UNKNOWN-RACK";
|
||||
|
||||
final String ec2region;
|
||||
private final String ec2zone;
|
||||
private final boolean usingLegacyNaming;
|
||||
|
||||
private Map<InetAddressAndPort, Map<String, String>> savedEndpoints;
|
||||
protected String ec2zone;
|
||||
protected String ec2region;
|
||||
|
||||
public Ec2Snitch() throws IOException, ConfigurationException
|
||||
{
|
||||
this(new SnitchProperties());
|
||||
}
|
||||
|
||||
public Ec2Snitch(SnitchProperties props) throws IOException, ConfigurationException
|
||||
{
|
||||
String az = awsApiCall(ZONE_NAME_QUERY_URL);
|
||||
// Split "us-east-1a" or "asia-1a" into "us-east"/"1a" and "asia"/"1a".
|
||||
String[] splits = az.split("-");
|
||||
ec2zone = splits[splits.length - 1];
|
||||
|
||||
// hack for CASSANDRA-4026
|
||||
ec2region = az.substring(0, az.length() - 1);
|
||||
if (ec2region.endsWith("1"))
|
||||
ec2region = az.substring(0, az.length() - 3);
|
||||
// if using the full naming scheme, region name is created by removing letters from the
|
||||
// end of the availability zone and zone is the full zone name
|
||||
usingLegacyNaming = isUsingLegacyNaming(props);
|
||||
String region;
|
||||
if (usingLegacyNaming)
|
||||
{
|
||||
// Split "us-east-1a" or "asia-1a" into "us-east"/"1a" and "asia"/"1a".
|
||||
String[] splits = az.split("-");
|
||||
ec2zone = splits[splits.length - 1];
|
||||
|
||||
String datacenterSuffix = (new SnitchProperties()).get("dc_suffix", "");
|
||||
ec2region = ec2region.concat(datacenterSuffix);
|
||||
// hack for CASSANDRA-4026
|
||||
region = az.substring(0, az.length() - 1);
|
||||
if (region.endsWith("1"))
|
||||
region = az.substring(0, az.length() - 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
// grab the region name, which is embedded in the availability zone name.
|
||||
// thus an AZ of "us-east-1a" yields the region name "us-east-1"
|
||||
region = az.replaceFirst("[a-z]+$","");
|
||||
ec2zone = az;
|
||||
}
|
||||
|
||||
String datacenterSuffix = props.get("dc_suffix", "");
|
||||
ec2region = region.concat(datacenterSuffix);
|
||||
logger.info("EC2Snitch using region: {}, zone: {}.", ec2region, ec2zone);
|
||||
}
|
||||
|
||||
private static boolean isUsingLegacyNaming(SnitchProperties props)
|
||||
{
|
||||
return props.get(SNITCH_PROP_NAMING_SCHEME, EC2_NAMING_STANDARD).equalsIgnoreCase(EC2_NAMING_LEGACY);
|
||||
}
|
||||
|
||||
String awsApiCall(String url) throws IOException, ConfigurationException
|
||||
{
|
||||
// Populate the region and zone by introspection, fail if 404 on metadata
|
||||
|
|
@ -122,4 +157,54 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch
|
|||
}
|
||||
return state.getApplicationState(ApplicationState.DC).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Set<String> datacenters, Set<String> racks)
|
||||
{
|
||||
return validate(datacenters, racks, usingLegacyNaming);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean validate(Set<String> datacenters, Set<String> racks, boolean usingLegacyNaming)
|
||||
{
|
||||
boolean valid = true;
|
||||
|
||||
for (String dc : datacenters)
|
||||
{
|
||||
// predicated on the late-2017 AWS naming 'convention' that all region names end with a digit.
|
||||
// Unfortunately, life isn't that simple. Since we allow custom datacenter suffixes (CASSANDRA-5155),
|
||||
// an operator could conceiveably make the suffix "a", and thus create a region name that looks just like
|
||||
// one of the region's availability zones. (for example, "us-east-1a").
|
||||
// Further, we can't make any assumptions of what that suffix might be by looking at this node's
|
||||
// datacenterSuffix as conceivably their could be many different suffixes in play for a given region.
|
||||
//
|
||||
// Thus, the best we can do is make sure the region name follows
|
||||
// the basic region naming pattern: "us-east-1<custom-suffix>"
|
||||
boolean dcUsesLegacyFormat = !dc.matches("[a-z]+-[a-z].+-[\\d].*");
|
||||
if (dcUsesLegacyFormat != usingLegacyNaming)
|
||||
valid = false;
|
||||
}
|
||||
|
||||
for (String rack : racks)
|
||||
{
|
||||
// predicated on late-2017 AWS naming 'convention' that AZs do not have a digit as the first char -
|
||||
// we had that in our legacy AZ (rack) names. Thus we test to see if the rack is in the legacy format.
|
||||
//
|
||||
// NOTE: the allowed custom suffix only applies to datacenter (region) names, not availability zones.
|
||||
boolean rackUsesLegacyFormat = rack.matches("[\\d][a-z]");
|
||||
if (rackUsesLegacyFormat != usingLegacyNaming)
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
logger.error("This ec2-enabled snitch appears to be using the {} naming scheme for regions, " +
|
||||
"but existing nodes in cluster are using the opposite: region(s) = {}, availability zone(s) = {}. " +
|
||||
"Please check the {} property in the {} configuration file for more details.",
|
||||
usingLegacyNaming ? "legacy" : "standard", datacenters, racks,
|
||||
SNITCH_PROP_NAMING_SCHEME, SnitchProperties.RACKDC_PROPERTY_FILENAME);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.apache.cassandra.locator;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This interface helps determine location of node in the datacenter relative to another node.
|
||||
|
|
@ -63,4 +64,12 @@ public interface IEndpointSnitch
|
|||
* to be faster than 2 sequential queries, one against l1 followed by one against l2.
|
||||
*/
|
||||
public boolean isWorthMergingForRangeQuery(List<InetAddressAndPort> merged, List<InetAddressAndPort> l1, List<InetAddressAndPort> l2);
|
||||
|
||||
/**
|
||||
* Determine if the datacenter or rack values in the current node's snitch conflict with those passed in parameters.
|
||||
*/
|
||||
default boolean validate(Set<String> datacenters, Set<String> racks)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public class SnitchProperties
|
|||
private static final Logger logger = LoggerFactory.getLogger(SnitchProperties.class);
|
||||
public static final String RACKDC_PROPERTY_FILENAME = "cassandra-rackdc.properties";
|
||||
|
||||
private Properties properties;
|
||||
private final Properties properties;
|
||||
|
||||
public SnitchProperties()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -482,6 +482,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
|||
if (epStates.get(replaceAddress) == null)
|
||||
throw new RuntimeException(String.format("Cannot replace_address %s because it doesn't exist in gossip", replaceAddress));
|
||||
|
||||
validateEndpointSnitch(epStates.values().iterator());
|
||||
|
||||
try
|
||||
{
|
||||
VersionedValue tokensVersionedValue = epStates.get(replaceAddress).getApplicationState(ApplicationState.TOKENS);
|
||||
|
|
@ -527,6 +529,8 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
|||
FBUtilities.getBroadcastAddressAndPort()));
|
||||
}
|
||||
|
||||
validateEndpointSnitch(epStates.values().iterator());
|
||||
|
||||
if (shouldBootstrap() && useStrictConsistency && !allowSimultaneousMoves())
|
||||
{
|
||||
for (Map.Entry<InetAddressAndPort, EndpointState> entry : epStates.entrySet())
|
||||
|
|
@ -550,6 +554,28 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
|
|||
}
|
||||
}
|
||||
|
||||
private static void validateEndpointSnitch(Iterator<EndpointState> endpointStates)
|
||||
{
|
||||
Set<String> datacenters = new HashSet<>();
|
||||
Set<String> racks = new HashSet<>();
|
||||
while (endpointStates.hasNext())
|
||||
{
|
||||
EndpointState state = endpointStates.next();
|
||||
VersionedValue val = state.getApplicationState(ApplicationState.DC);
|
||||
if (val != null)
|
||||
datacenters.add(val.value);
|
||||
val = state.getApplicationState(ApplicationState.RACK);
|
||||
if (val != null)
|
||||
racks.add(val.value);
|
||||
}
|
||||
|
||||
IEndpointSnitch snitch = DatabaseDescriptor.getEndpointSnitch();
|
||||
if (!snitch.validate(datacenters, racks))
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean allowSimultaneousMoves()
|
||||
{
|
||||
return allowSimultaneousMoves && DatabaseDescriptor.getNumTokens() == 1;
|
||||
|
|
|
|||
|
|
@ -22,3 +22,15 @@ rack=RAC1
|
|||
# Add a suffix to a datacenter name. Used by the Ec2Snitch and Ec2MultiRegionSnitch
|
||||
# to append a string to the EC2 region name.
|
||||
#dc_suffix=
|
||||
|
||||
# Datacenter and rack naming convention used by the Ec2Snitch and Ec2MultiRegionSnitch.
|
||||
# Options are:
|
||||
# legacy : datacenter name is the part of the availability zone name preceding the last "-"
|
||||
# when the zone ends in -1 and includes the number if not -1. Rack is the portion of
|
||||
# the availability zone name following the last "-".
|
||||
# Examples: us-west-1a => dc: us-west, rack: 1a; us-west-2b => dc: us-west-2, rack: 2b;
|
||||
# standard : datacenter name is the standard AWS region name, including the number. rack name is the
|
||||
# region plus the availability zone letter.
|
||||
# Examples: us-west-1a => dc: us-west-1, rack: us-west-1a; us-west-2b => dc: us-west-2, rack: us-west-2b;
|
||||
# default: standard
|
||||
ec2_naming_scheme=standard
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@ package org.apache.cassandra.locator;
|
|||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
|
|
@ -37,12 +40,21 @@ import org.apache.cassandra.gms.Gossiper;
|
|||
import org.apache.cassandra.gms.VersionedValue;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
|
||||
import static org.apache.cassandra.locator.Ec2Snitch.EC2_NAMING_LEGACY;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class EC2SnitchTest
|
||||
{
|
||||
private static String az;
|
||||
|
||||
private final SnitchProperties legacySnitchProps = new SnitchProperties()
|
||||
{
|
||||
public String get(String propertyName, String defaultValue)
|
||||
{
|
||||
return propertyName.equals("ec2_naming_scheme") ? EC2_NAMING_LEGACY : super.get(propertyName, defaultValue);
|
||||
}
|
||||
};
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws Exception
|
||||
{
|
||||
|
|
@ -60,6 +72,11 @@ public class EC2SnitchTest
|
|||
super();
|
||||
}
|
||||
|
||||
public TestEC2Snitch(SnitchProperties props) throws IOException, ConfigurationException
|
||||
{
|
||||
super(props);
|
||||
}
|
||||
|
||||
@Override
|
||||
String awsApiCall(String url) throws IOException, ConfigurationException
|
||||
{
|
||||
|
|
@ -68,10 +85,10 @@ public class EC2SnitchTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testRac() throws IOException, ConfigurationException
|
||||
public void testLegacyRac() throws IOException, ConfigurationException
|
||||
{
|
||||
az = "us-east-1d";
|
||||
Ec2Snitch snitch = new TestEC2Snitch();
|
||||
Ec2Snitch snitch = new TestEC2Snitch(legacySnitchProps);
|
||||
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
|
||||
InetAddressAndPort nonlocal = InetAddressAndPort.getByName("127.0.0.7");
|
||||
|
||||
|
|
@ -87,17 +104,106 @@ public class EC2SnitchTest
|
|||
assertEquals("us-east", snitch.getDatacenter(local));
|
||||
assertEquals("1d", snitch.getRack(local));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNewRegions() throws IOException, ConfigurationException
|
||||
public void testLegacyNewRegions() throws IOException, ConfigurationException
|
||||
{
|
||||
az = "us-east-2d";
|
||||
Ec2Snitch snitch = new TestEC2Snitch();
|
||||
Ec2Snitch snitch = new TestEC2Snitch(legacySnitchProps);
|
||||
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
|
||||
assertEquals("us-east-2", snitch.getDatacenter(local));
|
||||
assertEquals("2d", snitch.getRack(local));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullNamingScheme() throws IOException, ConfigurationException
|
||||
{
|
||||
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
|
||||
az = "us-east-2d";
|
||||
Ec2Snitch snitch = new TestEC2Snitch();
|
||||
|
||||
assertEquals("us-east-2", snitch.getDatacenter(local));
|
||||
assertEquals("us-east-2d", snitch.getRack(local));
|
||||
|
||||
az = "us-west-1a";
|
||||
snitch = new TestEC2Snitch();
|
||||
|
||||
assertEquals("us-west-1", snitch.getDatacenter(local));
|
||||
assertEquals("us-west-1a", snitch.getRack(local));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateDatacenter_RequiresLegacy_CorrectAmazonName()
|
||||
{
|
||||
Set<String> datacenters = new HashSet<>();
|
||||
datacenters.add("us-east-1");
|
||||
Assert.assertFalse(Ec2Snitch.validate(datacenters, Collections.emptySet(), true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateDatacenter_RequiresLegacy_LegacyName()
|
||||
{
|
||||
Set<String> datacenters = new HashSet<>();
|
||||
datacenters.add("us-east");
|
||||
Assert.assertTrue(Ec2Snitch.validate(datacenters, Collections.emptySet(), true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate_RequiresLegacy_HappyPath()
|
||||
{
|
||||
Set<String> datacenters = new HashSet<>();
|
||||
datacenters.add("us-east");
|
||||
Set<String> racks = new HashSet<>();
|
||||
racks.add("1a");
|
||||
Assert.assertTrue(Ec2Snitch.validate(datacenters, racks, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate_RequiresLegacy_HappyPathWithDCSuffix()
|
||||
{
|
||||
Set<String> datacenters = new HashSet<>();
|
||||
datacenters.add("us-east_CUSTOM_SUFFIX");
|
||||
Set<String> racks = new HashSet<>();
|
||||
racks.add("1a");
|
||||
Assert.assertTrue(Ec2Snitch.validate(datacenters, racks, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateRack_RequiresAmazonName_CorrectAmazonName()
|
||||
{
|
||||
Set<String> racks = new HashSet<>();
|
||||
racks.add("us-east-1a");
|
||||
Assert.assertTrue(Ec2Snitch.validate(Collections.emptySet(), racks, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateRack_RequiresAmazonName_LegacyName()
|
||||
{
|
||||
Set<String> racks = new HashSet<>();
|
||||
racks.add("1a");
|
||||
Assert.assertFalse(Ec2Snitch.validate(Collections.emptySet(), racks, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate_RequiresAmazonName_HappyPath()
|
||||
{
|
||||
Set<String> datacenters = new HashSet<>();
|
||||
datacenters.add("us-east-1");
|
||||
Set<String> racks = new HashSet<>();
|
||||
racks.add("us-east-1a");
|
||||
Assert.assertTrue(Ec2Snitch.validate(datacenters, racks, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validate_RequiresAmazonName_HappyPathWithDCSuffix()
|
||||
{
|
||||
Set<String> datacenters = new HashSet<>();
|
||||
datacenters.add("us-east-1_CUSTOM_SUFFIX");
|
||||
Set<String> racks = new HashSet<>();
|
||||
racks.add("us-east-1a");
|
||||
Assert.assertTrue(Ec2Snitch.validate(datacenters, racks, false));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue