Deprecate CloudstackSnitch and remove duplicate code in snitches

The patch also refactors existing cloud snitches to get rid of the duplicate code,
this is the logical follow-up of CASSANDRA-16555 where AbstractCloudMetadataServiceConnector was introduced.

patch by Stefan Miklosovic; reviewed by Jacek Lewandowski, Jackson Fleming and Maxwell Guo for CASSANDRA-18438
This commit is contained in:
Stefan Miklosovic 2023-06-29 17:30:02 +02:00
parent 6f486f5847
commit 903857b4ef
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
19 changed files with 577 additions and 454 deletions

View File

@ -1,4 +1,5 @@
5.0
* Deprecate CloudstackSnitch and remove duplicate code in snitches (CASSANDRA-18438)
* Add support for vectors in UDFs (CASSANDRA-18613)
* Improve vector value validation errors (CASSANDRA-18652)
* Upgrade Guava to 32.0.1 (CASSANDRA-18645)

View File

@ -223,6 +223,8 @@ Deprecation
for partition tombstones. That guardrail is based on the properties `partition_tombstones_warn_threshold` and
`partition_tombstones_fail_threshold`. The warn threshold has a very similar behaviour to the old config property.
The old property is still supported for backward compatibility, but now it is disabled by default.
- CloudstackSnitch is marked as deprecated and it is not actively maintained anymore. It is scheduled to be removed
in the next major version of Cassandra.
4.1
===

View File

@ -19,8 +19,7 @@
dc=dc1
rack=rack1
# Add a suffix to a datacenter name. Used by the Ec2Snitch and Ec2MultiRegionSnitch
# to append a string to the EC2 region name.
# Add a suffix to a datacenter name. Used by all cloud-based snitches.
#dc_suffix=
# Uncomment the following line to make this snitch prefer the internal ip when possible, as the Ec2MultiRegionSnitch does.
@ -43,3 +42,15 @@ rack=rack1
# If AWS IMDS of v2 is configured, ec2_metadata_token_ttl_seconds says how many seconds a token will be valid until
# it is refreshed. Defaults to 21600. Can not be smaller than 30 and bigger than 21600. Has to be an integer.
# ec2_metadata_token_ttl_seconds=21600
# For all cloud-based snitches, there are following options available:
#
# Property to change metadata service url for a cloud-based snitch. The endpoint of a particular
# snitch will be appended to this property. A user is not normally using this property, it is here
# for tweaking the url of a service itself, e.g. for testing purposes.
# metadata_url=http://some/service
#
# Sets a specified timeout value, in duration format, to be used when opening a communications link to metadata service,
# referenced by an URLConnection. The timeout of zero (0s) is interpreted as an infinite timeout.
# Defaults to 30 seconds.
# metadata_request_timeout=30s

View File

@ -1298,6 +1298,17 @@ slow_query_log_timeout: 500ms
# Proximity is determined by rack and data center, which are
# explicitly configured in cassandra-topology.properties.
#
# AlibabaCloudSnitch:
# Snitch for getting dc and rack of a node from metadata service of Alibaba cloud.
# This snitch that assumes an ECS region is a DC and an ECS availability_zone is a rack.
#
# CloudstackSnitch:
# A snitch that assumes a Cloudstack Zone follows the typical convention
# country-location-az and uses a country/location tuple as a datacenter
# and the availability zone as a rack.
# WARNING: This snitch is deprecated and it is scheduled to be removed
# in the next major version of Cassandra.
#
# Ec2Snitch:
# Appropriate for EC2 deployments in a single Region. Loads Region
# and Availability Zone information from the EC2 API. The Region is
@ -1313,6 +1324,10 @@ slow_query_log_timeout: 500ms
# traffic, Cassandra will switch to the private IP after
# establishing a connection.)
#
# GoogleCloudSnitch:
# Snitch for getting dc and rack of a node from metadata service of Google cloud.
# This snitch that assumes an GCE region is a DC and an GCE availability_zone is a rack.
#
# RackInferringSnitch:
# Proximity is determined by rack and data center, which are
# assumed to correspond to the 3rd and 2nd octet of each node's IP

View File

@ -22,30 +22,72 @@ import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.exceptions.ConfigurationException;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
abstract class AbstractCloudMetadataServiceConnector
{
static final String METADATA_URL_PROPERTY = "metadata_url";
static final String METADATA_REQUEST_TIMEOUT_PROPERTY = "metadata_request_timeout";
static final String DEFAULT_METADATA_REQUEST_TIMEOUT = "30s";
protected final String metadataServiceUrl;
protected final int requestTimeoutMs;
protected AbstractCloudMetadataServiceConnector(String metadataServiceUrl)
public AbstractCloudMetadataServiceConnector(SnitchProperties properties)
{
this.metadataServiceUrl = metadataServiceUrl;
String parsedMetadataServiceUrl = properties.get(METADATA_URL_PROPERTY, null);
try
{
URL url = new URL(parsedMetadataServiceUrl);
url.toURI();
this.metadataServiceUrl = parsedMetadataServiceUrl;
}
catch (MalformedURLException | IllegalArgumentException | URISyntaxException ex)
{
throw new ConfigurationException(format("Snitch metadata service URL '%s' is invalid. Please review snitch properties " +
"defined in the configured '%s' configuration file.",
parsedMetadataServiceUrl,
CassandraRelevantProperties.CASSANDRA_RACKDC_PROPERTIES.getKey()),
ex);
}
String metadataRequestTimeout = properties.get(METADATA_REQUEST_TIMEOUT_PROPERTY, DEFAULT_METADATA_REQUEST_TIMEOUT);
try
{
this.requestTimeoutMs = new DurationSpec.IntMillisecondsBound(metadataRequestTimeout).toMilliseconds();
}
catch (IllegalArgumentException ex)
{
throw new ConfigurationException(format("%s as value of %s is invalid duration! " + ex.getMessage(),
metadataRequestTimeout,
METADATA_REQUEST_TIMEOUT_PROPERTY));
}
}
public String apiCall(String query) throws IOException
public final String apiCall(String query) throws IOException
{
return apiCall(metadataServiceUrl, query, 200);
return apiCall(metadataServiceUrl, query, "GET", ImmutableMap.of(), 200);
}
public String apiCall(String url, String query, int expectedResponseCode) throws IOException
public final String apiCall(String query, Map<String, String> extraHeaders) throws IOException
{
return apiCall(url, query, "GET", ImmutableMap.of(), expectedResponseCode);
return apiCall(metadataServiceUrl, query, "GET", extraHeaders, 200);
}
public String apiCall(String url,
@ -61,6 +103,7 @@ abstract class AbstractCloudMetadataServiceConnector
conn = (HttpURLConnection) new URL(url + query).openConnection();
extraHeaders.forEach(conn::setRequestProperty);
conn.setRequestMethod(method);
conn.setConnectTimeout(requestTimeoutMs);
if (conn.getResponseCode() != expectedResponseCode)
throw new HttpException(conn.getResponseCode(), conn.getResponseMessage());
@ -84,6 +127,14 @@ abstract class AbstractCloudMetadataServiceConnector
}
}
@Override
public String toString()
{
return format("%s{%s=%s,%s=%s}", getClass().getName(),
METADATA_URL_PROPERTY, metadataServiceUrl,
METADATA_REQUEST_TIMEOUT_PROPERTY, requestTimeoutMs);
}
public static final class HttpException extends IOException
{
public final int responseCode;
@ -96,4 +147,12 @@ abstract class AbstractCloudMetadataServiceConnector
this.responseMessage = responseMessage;
}
}
public static class DefaultCloudMetadataServiceConnector extends AbstractCloudMetadataServiceConnector
{
public DefaultCloudMetadataServiceConnector(SnitchProperties properties)
{
super(properties);
}
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.SystemKeyspace;
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.utils.Pair;
import static java.lang.String.format;
abstract class AbstractCloudMetadataServiceSnitch extends AbstractNetworkTopologySnitch
{
static final Logger logger = LoggerFactory.getLogger(AbstractCloudMetadataServiceSnitch.class);
static final String DEFAULT_DC = "UNKNOWN-DC";
static final String DEFAULT_RACK = "UNKNOWN-RACK";
protected final AbstractCloudMetadataServiceConnector connector;
protected final SnitchProperties snitchProperties;
private final String localRack;
private final String localDc;
private Map<InetAddressAndPort, Map<String, String>> savedEndpoints;
public AbstractCloudMetadataServiceSnitch(AbstractCloudMetadataServiceConnector connector,
SnitchProperties snitchProperties,
Pair<String, String> dcAndRack)
{
this.connector = connector;
this.snitchProperties = snitchProperties;
this.localDc = dcAndRack.left;
this.localRack = dcAndRack.right;
logger.info(format("%s using datacenter: %s, rack: %s, connector: %s, properties: %s",
getClass().getName(), getLocalDatacenter(), getLocalRack(), connector, snitchProperties));
}
@Override
public final String getLocalRack()
{
return localRack;
}
@Override
public final String getLocalDatacenter()
{
return localDc;
}
@Override
public final String getRack(InetAddressAndPort endpoint)
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return getLocalRack();
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.RACK) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("rack");
return DEFAULT_RACK;
}
return state.getApplicationState(ApplicationState.RACK).value;
}
@Override
public final String getDatacenter(InetAddressAndPort endpoint)
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return getLocalDatacenter();
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.DC) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("data_center");
return DEFAULT_DC;
}
return state.getApplicationState(ApplicationState.DC).value;
}
}

View File

@ -17,130 +17,41 @@
*/
package org.apache.cassandra.locator;
import java.io.DataInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.apache.cassandra.db.SystemKeyspace;
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.io.util.FileUtils;
import org.apache.cassandra.utils.FBUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.DefaultCloudMetadataServiceConnector;
import static org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.METADATA_URL_PROPERTY;
/**
* A snitch that assumes an ECS region is a DC and an ECS availability_zone
* is a rack. This information is available in the config for the node. the
* format of the zone-id is like :cn-hangzhou-a where cn means china, hangzhou
* means the hangzhou region, a means the az id. We use cn-hangzhou as the dc,
* and f as the zone-id.
* A snitch that assumes an ECS region is a DC and an ECS availability_zone
* is a rack. This information is available in the config for the node. the
* format of the zone-id is like 'cn-hangzhou-a' where cn means china, hangzhou
* means the hangzhou region, a means the az id. We use 'cn-hangzhou' as the dc,
* and 'a' as the zone-id.
*/
public class AlibabaCloudSnitch extends AbstractNetworkTopologySnitch
public class AlibabaCloudSnitch extends AbstractCloudMetadataServiceSnitch
{
protected static final Logger logger = LoggerFactory.getLogger(AlibabaCloudSnitch.class);
protected static final String ZONE_NAME_QUERY_URL = "http://100.100.100.200/latest/meta-data/zone-id";
private static final String DEFAULT_DC = "UNKNOWN-DC";
private static final String DEFAULT_RACK = "UNKNOWN-RACK";
private Map<InetAddressAndPort, Map<String, String>> savedEndpoints;
protected String ecsZone;
protected String ecsRegion;
private static final int HTTP_CONNECT_TIMEOUT = 30000;
public AlibabaCloudSnitch() throws MalformedURLException, IOException
static final String DEFAULT_METADATA_SERVICE_URL = "http://100.100.100.200";
static final String ZONE_NAME_QUERY_URL = "/latest/meta-data/zone-id";
public AlibabaCloudSnitch() throws IOException
{
String response = alibabaApiCall(ZONE_NAME_QUERY_URL);
String[] splits = response.split("/");
String az = splits[splits.length - 1];
// Split "us-central1-a" or "asia-east1-a" into "us-central1"/"a" and "asia-east1"/"a".
splits = az.split("-");
ecsZone = splits[splits.length - 1];
int lastRegionIndex = az.lastIndexOf("-");
ecsRegion = az.substring(0, lastRegionIndex);
String datacenterSuffix = (new SnitchProperties()).get("dc_suffix", "");
ecsRegion = ecsRegion.concat(datacenterSuffix);
logger.info("AlibabaSnitch using region: {}, zone: {}.", ecsRegion, ecsZone);
}
String alibabaApiCall(String url) throws ConfigurationException, IOException, SocketTimeoutException
{
// Populate the region and zone by introspection, fail if 404 on metadata
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
DataInputStream d = null;
try
{
conn.setConnectTimeout(HTTP_CONNECT_TIMEOUT);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if (code != HttpURLConnection.HTTP_OK)
throw new ConfigurationException("AlibabaSnitch was unable to execute the API call. Not an ecs node? and the returun code is " + code);
// Read the information. I wish I could say (String) conn.getContent() here...
int cl = conn.getContentLength();
byte[] b = new byte[cl];
d = new DataInputStream((FilterInputStream) conn.getContent());
d.readFully(b);
return new String(b, StandardCharsets.UTF_8);
}
catch (SocketTimeoutException e)
{
throw new SocketTimeoutException("Timeout occurred reading a response from the Alibaba ECS metadata");
}
finally
{
FileUtils.close(d);
conn.disconnect();
}
}
@Override
public String getRack(InetAddressAndPort endpoint)
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return ecsZone;
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.RACK) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("rack");
return DEFAULT_RACK;
}
return state.getApplicationState(ApplicationState.RACK).value;
this(new SnitchProperties());
}
@Override
public String getDatacenter(InetAddressAndPort endpoint)
public AlibabaCloudSnitch(SnitchProperties properties) throws IOException
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return ecsRegion;
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.DC) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("data_center");
return DEFAULT_DC;
}
return state.getApplicationState(ApplicationState.DC).value;
this(properties, new DefaultCloudMetadataServiceConnector(properties.putIfAbsent(METADATA_URL_PROPERTY,
DEFAULT_METADATA_SERVICE_URL)));
}
public AlibabaCloudSnitch(SnitchProperties properties, AbstractCloudMetadataServiceConnector connector) throws IOException
{
super(connector, properties, SnitchUtils.parseDcAndRack(connector.apiCall(ZONE_NAME_QUERY_URL,
ImmutableMap.of()),
properties.getDcSuffix()));
}
}

View File

@ -17,140 +17,79 @@
*/
package org.apache.cassandra.locator;
import java.io.DataInputStream;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.SystemKeyspace;
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.io.util.FileUtils;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.METADATA_URL_PROPERTY;
import static org.apache.cassandra.locator.CloudstackSnitch.CloudstackConnector.ZONE_NAME_QUERY_URI;
/**
* {@code
* A snitch that assumes a Cloudstack Zone follows the typical convention
* <country>-<location>-<availability zone> and uses the country/location
* {@code country-location-availability zone} and uses the country/location
* tuple as a datacenter and the availability zone as a rack
* }
*
* This snitch is deprecated, and it is eligible for the removal in the next major release of Cassandra.
*/
public class CloudstackSnitch extends AbstractNetworkTopologySnitch
@Deprecated
public class CloudstackSnitch extends AbstractCloudMetadataServiceSnitch
{
protected static final Logger logger = LoggerFactory.getLogger(CloudstackSnitch.class);
protected static final String ZONE_NAME_QUERY_URI = "/latest/meta-data/availability-zone";
private Map<InetAddressAndPort, Map<String, String>> savedEndpoints;
private static final String DEFAULT_DC = "UNKNOWN-DC";
private static final String DEFAULT_RACK = "UNKNOWN-RACK";
private static final String[] LEASE_FILES =
{
"file:///var/lib/dhcp/dhclient.eth0.leases",
"file:///var/lib/dhclient/dhclient.eth0.leases"
"file:///var/lib/dhcp/dhclient.eth0.leases",
"file:///var/lib/dhclient/dhclient.eth0.leases"
};
protected String csZoneDc;
protected String csZoneRack;
public CloudstackSnitch() throws IOException, ConfigurationException
public CloudstackSnitch() throws IOException
{
String endpoint = csMetadataEndpoint();
String zone = csQueryMetadata(endpoint + ZONE_NAME_QUERY_URI);
String zone_parts[] = zone.split("-");
this(new SnitchProperties(new Properties()));
}
if (zone_parts.length != 3)
public CloudstackSnitch(SnitchProperties snitchProperties) throws IOException
{
this(snitchProperties, new CloudstackConnector(snitchProperties.putIfAbsent(METADATA_URL_PROPERTY, csMetadataEndpoint())));
}
public CloudstackSnitch(SnitchProperties properties, AbstractCloudMetadataServiceConnector connector) throws IOException
{
super(connector, properties, resolveDcAndRack(connector));
logger.warn("{} is deprecated and not actively maintained. It will be removed in the next " +
"major version of Cassandra.", CloudstackSnitch.class.getName());
}
static class CloudstackConnector extends AbstractCloudMetadataServiceConnector
{
static final String ZONE_NAME_QUERY_URI = "/latest/meta-data/availability-zone";
protected CloudstackConnector(SnitchProperties properties)
{
super(properties);
}
}
private static Pair<String, String> resolveDcAndRack(AbstractCloudMetadataServiceConnector connector) throws IOException
{
String zone = connector.apiCall(ZONE_NAME_QUERY_URI);
String[] zoneParts = zone.split("-");
if (zoneParts.length != 3)
throw new ConfigurationException("CloudstackSnitch cannot handle invalid zone format: " + zone);
}
csZoneDc = zone_parts[0] + "-" + zone_parts[1];
csZoneRack = zone_parts[2];
return Pair.create(zoneParts[0] + '-' + zoneParts[1], zoneParts[2]);
}
public String getRack(InetAddressAndPort endpoint)
private static String csMetadataEndpoint() throws ConfigurationException
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return csZoneRack;
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.RACK) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("rack");
return DEFAULT_RACK;
}
return state.getApplicationState(ApplicationState.RACK).value;
}
public String getDatacenter(InetAddressAndPort endpoint)
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return csZoneDc;
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.DC) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("data_center");
return DEFAULT_DC;
}
return state.getApplicationState(ApplicationState.DC).value;
}
String csQueryMetadata(String url) throws ConfigurationException, IOException
{
HttpURLConnection conn = null;
DataInputStream is = null;
try
{
conn = (HttpURLConnection) new URL(url).openConnection();
}
catch (Exception e)
{
throw new ConfigurationException("CloudstackSnitch cannot query wrong metadata URL: " + url);
}
try
{
conn.setRequestMethod("GET");
if (conn.getResponseCode() != 200)
{
throw new ConfigurationException("CloudstackSnitch was unable to query metadata.");
}
int cl = conn.getContentLength();
byte[] b = new byte[cl];
is = new DataInputStream(new BufferedInputStream(conn.getInputStream()));
is.readFully(b);
return new String(b, StandardCharsets.UTF_8);
}
finally
{
FileUtils.close(is);
conn.disconnect();
}
}
String csMetadataEndpoint() throws ConfigurationException
{
for (String lease_uri: LEASE_FILES)
for (String lease_uri : LEASE_FILES)
{
try
{
@ -163,15 +102,13 @@ public class CloudstackSnitch extends AbstractNetworkTopologySnitch
catch (Exception e)
{
JVMStabilityInspector.inspectThrowable(e);
continue;
}
}
throw new ConfigurationException("No valid DHCP lease file could be found.");
}
String csEndpointFromLease(File lease) throws ConfigurationException
private static String csEndpointFromLease(File lease) throws ConfigurationException
{
String line;
String endpoint = null;

View File

@ -45,7 +45,7 @@ abstract class Ec2MetadataServiceConnector extends AbstractCloudMetadataServiceC
Ec2MetadataServiceConnector(SnitchProperties properties)
{
super(properties.get(EC2_METADATA_URL_PROPERTY, DEFAULT_EC2_METADATA_URL));
super(properties.putIfAbsent(METADATA_URL_PROPERTY, properties.get(EC2_METADATA_URL_PROPERTY, DEFAULT_EC2_METADATA_URL)));
}
enum EC2MetadataType
@ -94,7 +94,7 @@ abstract class Ec2MetadataServiceConnector extends AbstractCloudMetadataServiceC
@Override
public String toString()
{
return String.format("%s{%s=%s}", V1Connector.class.getName(), EC2_METADATA_URL_PROPERTY, metadataServiceUrl);
return String.format("%s{%s=%s}", V1Connector.class.getName(), METADATA_URL_PROPERTY, metadataServiceUrl);
}
}
@ -184,7 +184,7 @@ abstract class Ec2MetadataServiceConnector extends AbstractCloudMetadataServiceC
{
return String.format("%s{%s=%s,%s=%s}",
V2Connector.class.getName(),
EC2_METADATA_URL_PROPERTY, metadataServiceUrl,
METADATA_URL_PROPERTY, metadataServiceUrl,
AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY,
tokenTTL.getSeconds());
}

View File

@ -58,7 +58,7 @@ public class Ec2MultiRegionSnitch extends Ec2Snitch
this(props, Ec2MetadataServiceConnector.create(props));
}
Ec2MultiRegionSnitch(SnitchProperties props, Ec2MetadataServiceConnector connector) throws IOException
Ec2MultiRegionSnitch(SnitchProperties props, AbstractCloudMetadataServiceConnector connector) throws IOException
{
super(props, connector);
InetAddress localPublicAddress = InetAddress.getByName(connector.apiCall(PUBLIC_IP_QUERY));
@ -88,6 +88,6 @@ public class Ec2MultiRegionSnitch extends Ec2Snitch
}
Gossiper.instance.addLocalApplicationState(ApplicationState.INTERNAL_ADDRESS_AND_PORT, StorageService.instance.valueFactory.internalAddressAndPort(address));
Gossiper.instance.addLocalApplicationState(ApplicationState.INTERNAL_IP, StorageService.instance.valueFactory.internalIP(address.getAddress()));
Gossiper.instance.register(new ReconnectableSnitchHelper(this, ec2region, true));
Gossiper.instance.register(new ReconnectableSnitchHelper(this, getLocalDatacenter(), true));
}
}

View File

@ -18,19 +18,12 @@
package org.apache.cassandra.locator;
import java.io.IOException;
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;
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.utils.Pair;
/**
* A snitch that assumes an EC2 region is a DC and an EC2 availability_zone
@ -52,10 +45,8 @@ import org.apache.cassandra.utils.FBUtilities;
* which is by default set to {@link Ec2MetadataServiceConnector.V2Connector#MAX_TOKEN_TIME_IN_SECONDS}. TTL has
* to be an integer from the range [30, 21600].
*/
public class Ec2Snitch extends AbstractNetworkTopologySnitch
public class Ec2Snitch extends AbstractCloudMetadataServiceSnitch
{
protected static final Logger logger = LoggerFactory.getLogger(Ec2Snitch.class);
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";
@ -63,17 +54,8 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch
@VisibleForTesting
public static final String ZONE_NAME_QUERY = "/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 final Ec2MetadataServiceConnector connector;
public Ec2Snitch() throws IOException, ConfigurationException
{
this(new SnitchProperties());
@ -84,20 +66,27 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch
this(props, Ec2MetadataServiceConnector.create(props));
}
Ec2Snitch(SnitchProperties props, Ec2MetadataServiceConnector connector) throws IOException
Ec2Snitch(SnitchProperties props, AbstractCloudMetadataServiceConnector connector) throws IOException
{
super(connector, props, getDcAndRack(props, connector));
usingLegacyNaming = isUsingLegacyNaming(props);
}
private static Pair<String, String> getDcAndRack(SnitchProperties props, AbstractCloudMetadataServiceConnector connector) throws IOException
{
this.connector = connector;
String az = connector.apiCall(ZONE_NAME_QUERY);
// 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);
boolean usingLegacyNaming = isUsingLegacyNaming(props);
String region;
String localDc;
String localRack;
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];
localRack = splits[splits.length - 1];
// hack for CASSANDRA-4026
region = az.substring(0, az.length() - 1);
@ -109,12 +98,12 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch
// 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;
localRack = az;
}
String datacenterSuffix = props.get("dc_suffix", "");
ec2region = region.concat(datacenterSuffix);
logger.info("EC2Snitch using region: {}, zone: {}, properties: {}", ec2region, ec2zone, connector);
localDc = region.concat(props.getDcSuffix());
return Pair.create(localDc, localRack);
}
private static boolean isUsingLegacyNaming(SnitchProperties props)
@ -122,38 +111,6 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch
return props.get(SNITCH_PROP_NAMING_SCHEME, EC2_NAMING_STANDARD).equalsIgnoreCase(EC2_NAMING_LEGACY);
}
public String getRack(InetAddressAndPort endpoint)
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return ec2zone;
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.RACK) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("rack");
return DEFAULT_RACK;
}
return state.getApplicationState(ApplicationState.RACK).value;
}
public String getDatacenter(InetAddressAndPort endpoint)
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return ec2region;
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.DC) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("data_center");
return DEFAULT_DC;
}
return state.getApplicationState(ApplicationState.DC).value;
}
@Override
public boolean validate(Set<String> datacenters, Set<String> racks)
{
@ -179,7 +136,10 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch
// We can still identify as legacy the dc names without a number as a suffix like us-east"
boolean dcUsesLegacyFormat = dc.matches("^[a-z]+-[a-z]+$");
if (dcUsesLegacyFormat && !usingLegacyNaming)
{
valid = false;
break;
}
}
for (String rack : racks)
@ -190,7 +150,10 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch
// 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;
break;
}
}
if (!valid)

View File

@ -17,111 +17,38 @@
*/
package org.apache.cassandra.locator;
import java.io.DataInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.SystemKeyspace;
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.io.util.FileUtils;
import org.apache.cassandra.utils.FBUtilities;
import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.DefaultCloudMetadataServiceConnector;
import static org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.METADATA_URL_PROPERTY;
/**
* A snitch that assumes an GCE region is a DC and an GCE availability_zone
* is a rack. This information is available in the config for the node.
* is a rack. This information is available in the config for the node.
*/
public class GoogleCloudSnitch extends AbstractNetworkTopologySnitch
public class GoogleCloudSnitch extends AbstractCloudMetadataServiceSnitch
{
protected static final Logger logger = LoggerFactory.getLogger(GoogleCloudSnitch.class);
protected static final String ZONE_NAME_QUERY_URL = "http://metadata.google.internal/computeMetadata/v1/instance/zone";
private static final String DEFAULT_DC = "UNKNOWN-DC";
private static final String DEFAULT_RACK = "UNKNOWN-RACK";
private Map<InetAddressAndPort, Map<String, String>> savedEndpoints;
protected String gceZone;
protected String gceRegion;
static final String DEFAULT_METADATA_SERVICE_URL = "http://metadata.google.internal";
static final String ZONE_NAME_QUERY_URL = "/computeMetadata/v1/instance/zone";
public GoogleCloudSnitch() throws IOException, ConfigurationException
public GoogleCloudSnitch() throws IOException
{
String response = gceApiCall(ZONE_NAME_QUERY_URL);
String[] splits = response.split("/");
String az = splits[splits.length - 1];
// Split "us-central1-a" or "asia-east1-a" into "us-central1"/"a" and "asia-east1"/"a".
splits = az.split("-");
gceZone = splits[splits.length - 1];
int lastRegionIndex = az.lastIndexOf("-");
gceRegion = az.substring(0, lastRegionIndex);
String datacenterSuffix = (new SnitchProperties()).get("dc_suffix", "");
gceRegion = gceRegion.concat(datacenterSuffix);
logger.info("GCESnitch using region: {}, zone: {}.", gceRegion, gceZone);
this(new SnitchProperties());
}
String gceApiCall(String url) throws IOException, ConfigurationException
public GoogleCloudSnitch(SnitchProperties properties) throws IOException
{
// Populate the region and zone by introspection, fail if 404 on metadata
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
DataInputStream d = null;
try
{
conn.setRequestMethod("GET");
conn.setRequestProperty("Metadata-Flavor", "Google");
if (conn.getResponseCode() != 200)
throw new ConfigurationException("GoogleCloudSnitch was unable to execute the API call. Not a gce node?");
// Read the information.
int cl = conn.getContentLength();
byte[] b = new byte[cl];
d = new DataInputStream((FilterInputStream) conn.getContent());
d.readFully(b);
return new String(b, StandardCharsets.UTF_8);
}
finally
{
FileUtils.close(d);
conn.disconnect();
}
this(properties, new DefaultCloudMetadataServiceConnector(properties.putIfAbsent(METADATA_URL_PROPERTY,
DEFAULT_METADATA_SERVICE_URL)));
}
public String getRack(InetAddressAndPort endpoint)
public GoogleCloudSnitch(SnitchProperties properties, AbstractCloudMetadataServiceConnector connector) throws IOException
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return gceZone;
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.RACK) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("rack");
return DEFAULT_RACK;
}
return state.getApplicationState(ApplicationState.RACK).value;
}
public String getDatacenter(InetAddressAndPort endpoint)
{
if (endpoint.equals(FBUtilities.getBroadcastAddressAndPort()))
return gceRegion;
EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
if (state == null || state.getApplicationState(ApplicationState.DC) == null)
{
if (savedEndpoints == null)
savedEndpoints = SystemKeyspace.loadDcRackInfo();
if (savedEndpoints.containsKey(endpoint))
return savedEndpoints.get(endpoint).get("data_center");
return DEFAULT_DC;
}
return state.getApplicationState(ApplicationState.DC).value;
super(connector, properties, SnitchUtils.parseDcAndRack(connector.apiCall(ZONE_NAME_QUERY_URL,
ImmutableMap.of("Metadata-Flavor", "Google")),
properties.getDcSuffix()));
}
}

View File

@ -24,6 +24,8 @@ import java.util.Properties;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -69,6 +71,14 @@ public class SnitchProperties
this.properties = properties;
}
@SafeVarargs
public SnitchProperties(Pair<String, String>... pairs)
{
properties = new Properties();
for (Pair<String, String> pair : pairs)
properties.setProperty(pair.left, pair.right);
}
/**
* Get a snitch property value or return defaultValue if not defined.
*/
@ -77,8 +87,46 @@ public class SnitchProperties
return properties.getProperty(propertyName, defaultValue);
}
public SnitchProperties add(String key, String value)
{
properties.put(key, value);
return this;
}
/**
* Returns this instance of snitch properties if key is present
* otherwise create new instance of properties and put key with a give value into it
*
* @param key key to add
* @param value value to add
* @return same properties if key is present or new object with added key and value if not
*/
public SnitchProperties putIfAbsent(String key, String value)
{
if (contains(key))
return this;
Properties p = new Properties();
p.putAll(this.properties);
p.put(key, value);
return new SnitchProperties(p);
}
public boolean contains(String propertyName)
{
return properties.containsKey(propertyName);
}
public String getDcSuffix()
{
return properties.getProperty("dc_suffix", "");
}
@Override
public String toString()
{
return "SnitchProperties{" +
"properties=" + (properties != null ? properties.toString() : "null") +
'}';
}
}

View File

@ -0,0 +1,50 @@
/*
* 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 org.apache.cassandra.utils.Pair;
import static java.lang.String.format;
public class SnitchUtils
{
private SnitchUtils() {}
static Pair<String, String> parseDcAndRack(String response, String dcSuffix)
{
String[] splits = response.split("/");
String az = splits[splits.length - 1];
splits = az.split("-");
String localRack = splits[splits.length - 1];
int lastRegionIndex = az.lastIndexOf('-');
// we would hit StringIndexOutOfBoundsException on the az.substring method if we did not do this
if (lastRegionIndex == -1)
throw new IllegalStateException(format("%s does not contain at least one '-' to differentiate " +
"between datacenter and rack", response));
String localDc = az.substring(0, lastRegionIndex);
localDc = localDc.concat(dcSuffix);
return Pair.create(localDc, localRack);
}
}

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.locator;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Map;
import java.util.Properties;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@ -38,10 +39,14 @@ import static org.apache.cassandra.ServerTestUtils.cleanup;
import static org.apache.cassandra.ServerTestUtils.mkdirs;
import static org.apache.cassandra.config.CassandraRelevantProperties.GOSSIP_DISABLE_THREAD_VALIDATION;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class AlibabaCloudSnitchTest
public class AlibabaCloudSnitchTest
{
private static String az;
static String az;
@BeforeClass
public static void setup() throws Exception
@ -56,25 +61,15 @@ public class AlibabaCloudSnitchTest
StorageService.instance.initServer(0);
}
private class TestAlibabaCloudSnitch extends AlibabaCloudSnitch
{
public TestAlibabaCloudSnitch() throws IOException, ConfigurationException
{
super();
}
@Override
String alibabaApiCall(String url) throws IOException, ConfigurationException
{
return az;
}
}
@Test
public void testRac() throws IOException, ConfigurationException
{
az = "cn-hangzhou-f";
AlibabaCloudSnitch snitch = new TestAlibabaCloudSnitch();
AbstractCloudMetadataServiceConnector mock = mock(AbstractCloudMetadataServiceConnector.class);
when(mock.apiCall(any(), anyMap())).thenReturn(az);
AlibabaCloudSnitch snitch = new AlibabaCloudSnitch(new SnitchProperties(new Properties()), mock);
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
InetAddressAndPort nonlocal = InetAddressAndPort.getByName("127.0.0.7");
@ -90,12 +85,16 @@ public class AlibabaCloudSnitchTest
assertEquals("cn-hangzhou", snitch.getDatacenter(local));
assertEquals("f", snitch.getRack(local));
}
@Test
public void testNewRegions() throws IOException, ConfigurationException
{
az = "us-east-1a";
AlibabaCloudSnitch snitch = new TestAlibabaCloudSnitch();
AbstractCloudMetadataServiceConnector mock = mock(AbstractCloudMetadataServiceConnector.class);
when(mock.apiCall(any(), anyMap())).thenReturn(az);
AlibabaCloudSnitch snitch = new AlibabaCloudSnitch(new SnitchProperties(new Properties()), mock);
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
assertEquals("us-east", snitch.getDatacenter(local));
assertEquals("1a", snitch.getRack(local));
@ -106,5 +105,4 @@ public class AlibabaCloudSnitchTest
{
StorageService.instance.stopClient();
}
}

View File

@ -21,6 +21,7 @@ package org.apache.cassandra.locator;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Map;
import java.util.Properties;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@ -39,6 +40,9 @@ import static org.apache.cassandra.ServerTestUtils.cleanup;
import static org.apache.cassandra.ServerTestUtils.mkdirs;
import static org.apache.cassandra.config.CassandraRelevantProperties.GOSSIP_DISABLE_THREAD_VALIDATION;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class CloudstackSnitchTest
{
@ -57,31 +61,14 @@ public class CloudstackSnitchTest
StorageService.instance.initServer(0);
}
private class TestCloudstackSnitch extends CloudstackSnitch
{
public TestCloudstackSnitch() throws IOException, ConfigurationException
{
super();
}
@Override
String csMetadataEndpoint() throws ConfigurationException
{
return "";
}
@Override
String csQueryMetadata(String endpoint) throws IOException, ConfigurationException
{
return az;
}
}
@Test
public void testRacks() throws IOException, ConfigurationException
{
az = "ch-gva-1";
CloudstackSnitch snitch = new TestCloudstackSnitch();
CloudstackSnitch.CloudstackConnector mock = mock(CloudstackSnitch.CloudstackConnector.class);
when(mock.apiCall(any())).thenReturn(az);
CloudstackSnitch snitch = new CloudstackSnitch(new SnitchProperties(new Properties()), mock);
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
InetAddressAndPort nonlocal = InetAddressAndPort.getByName("127.0.0.7");
@ -96,14 +83,16 @@ public class CloudstackSnitchTest
assertEquals("ch-gva", snitch.getDatacenter(local));
assertEquals("1", snitch.getRack(local));
}
@Test
public void testNewRegions() throws IOException, ConfigurationException
{
az = "ch-gva-1";
CloudstackSnitch snitch = new TestCloudstackSnitch();
CloudstackSnitch.CloudstackConnector mock = mock(CloudstackSnitch.CloudstackConnector.class);
when(mock.apiCall(any())).thenReturn(az);
CloudstackSnitch snitch = new CloudstackSnitch(new SnitchProperties(new Properties()), mock);
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
assertEquals("ch-gva", snitch.getDatacenter(local));

View File

@ -0,0 +1,63 @@
/*
* 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 org.junit.Test;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.DefaultCloudMetadataServiceConnector;
import org.apache.cassandra.utils.Pair;
import static org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.DEFAULT_METADATA_REQUEST_TIMEOUT;
import static org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.METADATA_REQUEST_TIMEOUT_PROPERTY;
import static org.apache.cassandra.locator.AbstractCloudMetadataServiceConnector.METADATA_URL_PROPERTY;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
public class DefaultCloudMetadataServiceConnectorTest
{
@Test
public void testDefaultConnector()
{
assertEquals(new DurationSpec.IntMillisecondsBound(DEFAULT_METADATA_REQUEST_TIMEOUT).toMilliseconds(),
new DefaultCloudMetadataServiceConnector(new SnitchProperties(Pair.create(METADATA_REQUEST_TIMEOUT_PROPERTY, DEFAULT_METADATA_REQUEST_TIMEOUT),
Pair.create(METADATA_URL_PROPERTY, "http://127.0.0.1/abc"))).requestTimeoutMs);
assertEquals(0, new DefaultCloudMetadataServiceConnector(new SnitchProperties(Pair.create(METADATA_REQUEST_TIMEOUT_PROPERTY, "0s"), Pair.create(METADATA_URL_PROPERTY, "http://127.0.0.1/abc"))).requestTimeoutMs);
assertEquals(30000, new DefaultCloudMetadataServiceConnector(new SnitchProperties(Pair.create(METADATA_URL_PROPERTY, "http://127.0.0.1/abc"))).requestTimeoutMs);
}
@Test
public void testInvalidMetadataURL()
{
assertThatExceptionOfType(ConfigurationException.class)
.isThrownBy(() -> new DefaultCloudMetadataServiceConnector(new SnitchProperties(Pair.create(METADATA_URL_PROPERTY, "http:"))))
.withMessage("Snitch metadata service URL 'http:' is invalid. Please review snitch properties defined in the configured 'cassandra-rackdc.properties' configuration file.");
}
@Test
public void testInvalidTimeout()
{
assertThatExceptionOfType(ConfigurationException.class)
.isThrownBy(() -> new DefaultCloudMetadataServiceConnector(new SnitchProperties(Pair.create(METADATA_REQUEST_TIMEOUT_PROPERTY, "1x"),
Pair.create(METADATA_URL_PROPERTY, "http://127.0.0.1/abc"))))
.withMessage("1x as value of metadata_request_timeout is invalid duration! Invalid duration: 1x Accepted units:[MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS] where case matters and only non-negative values.");
}
}

View File

@ -22,6 +22,7 @@ package org.apache.cassandra.locator;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Map;
import java.util.Properties;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@ -40,11 +41,13 @@ import static org.apache.cassandra.ServerTestUtils.cleanup;
import static org.apache.cassandra.ServerTestUtils.mkdirs;
import static org.apache.cassandra.config.CassandraRelevantProperties.GOSSIP_DISABLE_THREAD_VALIDATION;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GoogleCloudSnitchTest
{
private static String az;
@BeforeClass
public static void setup() throws Exception
{
@ -58,25 +61,15 @@ public class GoogleCloudSnitchTest
StorageService.instance.initServer(0);
}
private class TestGoogleCloudSnitch extends GoogleCloudSnitch
{
public TestGoogleCloudSnitch() throws IOException, ConfigurationException
{
super();
}
@Override
String gceApiCall(String url) throws IOException, ConfigurationException
{
return az;
}
}
@Test
public void testRac() throws IOException, ConfigurationException
{
az = "us-central1-a";
GoogleCloudSnitch snitch = new TestGoogleCloudSnitch();
String az = "us-central1-a";
AbstractCloudMetadataServiceConnector mock = mock(AbstractCloudMetadataServiceConnector.class);
when(mock.apiCall(any(), anyMap())).thenReturn(az);
GoogleCloudSnitch snitch = new GoogleCloudSnitch(new SnitchProperties(new Properties()), mock);
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
InetAddressAndPort nonlocal = InetAddressAndPort.getByName("127.0.0.7");
@ -96,8 +89,11 @@ public class GoogleCloudSnitchTest
@Test
public void testNewRegions() throws IOException, ConfigurationException
{
az = "asia-east1-a";
GoogleCloudSnitch snitch = new TestGoogleCloudSnitch();
String az = "asia-east1-a";
AbstractCloudMetadataServiceConnector mock = mock(AbstractCloudMetadataServiceConnector.class);
when(mock.apiCall(any(), anyMap())).thenReturn(az);
GoogleCloudSnitch snitch = new GoogleCloudSnitch(new SnitchProperties(new Properties()), mock);
InetAddressAndPort local = InetAddressAndPort.getByName("127.0.0.1");
assertEquals("asia-east1", snitch.getDatacenter(local));
assertEquals("a", snitch.getRack(local));

View File

@ -0,0 +1,45 @@
/*
* 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 org.junit.Test;
import org.apache.cassandra.utils.Pair;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
public class SnitchUtilsTest
{
@Test
public void testSnitchUtils()
{
Pair<String, String> result = SnitchUtils.parseDcAndRack("my-dc-rack1", "");
assertEquals("my-dc", result.left);
assertEquals("rack1", result.right);
result = SnitchUtils.parseDcAndRack("my-rack", "");
assertEquals("my", result.left);
assertEquals("rack", result.right);
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> SnitchUtils.parseDcAndRack("myresponse", ""))
.withMessage("myresponse does not contain at least one '-' to differentiate between datacenter and rack");
}
}