Add support for AWS Ec2 IMDSv2

patch by Stefan Miklosovic; reviewed by Jacek Lewandowski and Brandon Williams for CASSANDRA-16555

Co-authored-by: Jacek Lewandowski <lewandowski.jacek@gmail.com>
Co-authored-by: Paul Rütter <paul@blueconic.com>
This commit is contained in:
Stefan Miklosovic 2022-04-25 15:02:03 +02:00
parent de7b1584f8
commit 4ea7bb25b4
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
10 changed files with 516 additions and 67 deletions

View File

@ -1,4 +1,5 @@
3.0.30
* Add support for AWS Ec2 IMDSv2 (CASSANDRA-16555)
* Suppress CVE-2023-35116 (CASSANDRA-18630)
* Pass taskId from CompactionTask to system.compaction_history (CASSANDRA-12183)
* Suppress CVE-2023-34455, CVE-2023-34454, CVE-2023-34453 (CASSANDRA-18608)

View File

@ -60,6 +60,16 @@ restore snapshots created with the previous major version using the
'sstableloader' tool. You can upgrade the file format of your snapshots
using the provided 'sstableupgrade' tool.
3.0.30
======
Upgrading
---------
- Please beware that if you use Ec2Snitch or Ec2MultiRegionSnitch, by default it will
communicate with AWS IMDS of version 2. This change is transparent, there does not need
to be done anything upon upgrade. Furthermore, IMDS of version 2 can be configured to be required in AWS EC2 console.
Consult cassandra-rackdc.properties for more details. (CASSANDRA-16555)
3.0.26
======

View File

@ -25,3 +25,9 @@ rack=rack1
# Uncomment the following line to make this snitch prefer the internal ip when possible, as the Ec2MultiRegionSnitch does.
# prefer_local=true
# Type of AWS IMDS for Ec2Snitch and Ec2MultiRegionSnitch, value might be either "v1" or "v2". Defaults to "v2".
# ec2_metadata_type=v2
# 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

View File

@ -0,0 +1,99 @@
/*
* 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.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import static java.nio.charset.StandardCharsets.UTF_8;
abstract class AbstractCloudMetadataServiceConnector
{
protected final String metadataServiceUrl;
protected AbstractCloudMetadataServiceConnector(String metadataServiceUrl)
{
this.metadataServiceUrl = metadataServiceUrl;
}
public String apiCall(String query) throws IOException
{
return apiCall(metadataServiceUrl, query, 200);
}
public String apiCall(String url, String query, int expectedResponseCode) throws IOException
{
return apiCall(url, query, "GET", ImmutableMap.of(), expectedResponseCode);
}
public String apiCall(String url,
String query,
String method,
Map<String, String> extraHeaders,
int expectedResponseCode) throws IOException
{
HttpURLConnection conn = null;
try
{
// Populate the region and zone by introspection, fail if 404 on metadata
conn = (HttpURLConnection) new URL(url + query).openConnection();
extraHeaders.forEach(conn::setRequestProperty);
conn.setRequestMethod(method);
if (conn.getResponseCode() != expectedResponseCode)
throw new HttpException(conn.getResponseCode(), conn.getResponseMessage());
// Read the information. I wish I could say (String) conn.getContent() here...
int cl = conn.getContentLength();
if (cl == -1)
return null;
byte[] b = new byte[cl];
try (DataInputStream d = new DataInputStream((InputStream) conn.getContent()))
{
d.readFully(b);
}
return new String(b, UTF_8);
}
finally
{
if (conn != null)
conn.disconnect();
}
}
public static final class HttpException extends IOException
{
public final int responseCode;
public final String responseMessage;
public HttpException(int responseCode, String responseMessage)
{
super("HTTP response code: " + responseCode + " (" + responseMessage + ')');
this.responseCode = responseCode;
this.responseMessage = responseMessage;
}
}
}

View File

@ -0,0 +1,214 @@
/*
* 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.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.utils.Pair;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
import static org.apache.cassandra.locator.Ec2MetadataServiceConnector.EC2MetadataType.v2;
abstract class Ec2MetadataServiceConnector extends AbstractCloudMetadataServiceConnector
{
static final String EC2_METADATA_TYPE_PROPERTY = "ec2_metadata_type";
static final String EC2_METADATA_URL_PROPERTY = "ec2_metadata_url";
static final String DEFAULT_EC2_METADATA_URL = "http://169.254.169.254";
Ec2MetadataServiceConnector(SnitchProperties properties)
{
super(properties.get(EC2_METADATA_URL_PROPERTY, DEFAULT_EC2_METADATA_URL));
}
enum EC2MetadataType
{
v1(props -> Ec2MetadataServiceConnector.V1Connector.create(props)),
v2(props -> Ec2MetadataServiceConnector.V2Connector.create(props));
private final Function<SnitchProperties, Ec2MetadataServiceConnector> provider;
EC2MetadataType(Function<SnitchProperties, Ec2MetadataServiceConnector> provider)
{
this.provider = provider;
}
Ec2MetadataServiceConnector create(SnitchProperties properties)
{
return provider.apply(properties);
}
}
static Ec2MetadataServiceConnector create(SnitchProperties props)
{
try
{
return EC2MetadataType.valueOf(props.get(EC2_METADATA_TYPE_PROPERTY, v2.name())).create(props);
}
catch (IllegalArgumentException ex)
{
throw new ConfigurationException(format("%s must be one of %s", EC2_METADATA_TYPE_PROPERTY,
stream(EC2MetadataType.values()).map(Enum::name).collect(joining(", "))));
}
}
static class V1Connector extends Ec2MetadataServiceConnector
{
static V1Connector create(SnitchProperties props)
{
return new V1Connector(props);
}
V1Connector(SnitchProperties props)
{
super(props);
}
@Override
public String toString()
{
return String.format("%s{%s=%s}", V1Connector.class.getName(), EC2_METADATA_URL_PROPERTY, metadataServiceUrl);
}
}
static class V2Connector extends Ec2MetadataServiceConnector
{
static final int MAX_TOKEN_TIME_IN_SECONDS = 21600;
static final int MIN_TOKEN_TIME_IN_SECONDS = 30;
static final String AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY = "ec2_metadata_token_ttl_seconds";
static final String AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER = "X-aws-ec2-metadata-token-ttl-seconds";
static final String AWS_EC2_METADATA_TOKEN_HEADER = "X-aws-ec2-metadata-token";
static final String TOKEN_QUERY = "/latest/api/token";
@VisibleForTesting
static int HTTP_REQUEST_RETRIES = 1;
private Pair<String, Long> token;
@VisibleForTesting
final Duration tokenTTL;
static V2Connector create(SnitchProperties props)
{
String tokenTTLString = props.get(AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY,
Integer.toString(MAX_TOKEN_TIME_IN_SECONDS));
Duration tokenTTL;
try
{
tokenTTL = Duration.ofSeconds(Integer.parseInt(tokenTTLString));
if (tokenTTL.getSeconds() < MIN_TOKEN_TIME_IN_SECONDS || tokenTTL.getSeconds() > MAX_TOKEN_TIME_IN_SECONDS)
{
throw new ConfigurationException(format("property %s was set to %s seconds which is not in allowed range of [%s..%s]",
AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY,
tokenTTL.getSeconds(),
MIN_TOKEN_TIME_IN_SECONDS,
MAX_TOKEN_TIME_IN_SECONDS));
}
}
catch (NumberFormatException ex)
{
throw new ConfigurationException(format("Unable to parse integer from property %s, value to parse: %s",
AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY, tokenTTLString));
}
return new V2Connector(props, tokenTTL);
}
V2Connector(SnitchProperties properties, Duration tokenTTL)
{
super(properties);
this.tokenTTL = tokenTTL;
}
@Override
public String apiCall(String url, String query, String method, Map<String, String> extraHeaders, int expectedResponseCode) throws IOException
{
Map<String, String> headers = new HashMap<>(extraHeaders);
for (int retry = 0; retry <= HTTP_REQUEST_RETRIES; retry++)
{
String resolvedToken;
if (token != null && token.right > System.currentTimeMillis())
resolvedToken = token.left;
else
resolvedToken = getToken();
try
{
headers.put(AWS_EC2_METADATA_TOKEN_HEADER, resolvedToken);
return super.apiCall(url, query, method, headers, expectedResponseCode);
}
catch (HttpException ex)
{
if (retry == HTTP_REQUEST_RETRIES)
throw ex;
if (ex.responseCode == 401) // invalidate token if it is 401
this.token = null;
}
}
throw new AssertionError();
}
@Override
public String toString()
{
return String.format("%s{%s=%s,%s=%s}",
V2Connector.class.getName(),
EC2_METADATA_URL_PROPERTY, metadataServiceUrl,
AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY,
tokenTTL.getSeconds());
}
/**
* Get a session token to enable requests to the meta data service.
* <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html">IMDSv2</a>
*/
@VisibleForTesting
public synchronized String getToken()
{
try
{
token = Pair.create(super.apiCall(metadataServiceUrl,
TOKEN_QUERY,
"PUT",
ImmutableMap.of(AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER, String.valueOf(tokenTTL.getSeconds())),
200),
System.currentTimeMillis() + tokenTTL.toMillis() - TimeUnit.SECONDS.toMillis(5));
return token.left;
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}
}

View File

@ -20,8 +20,10 @@ package org.apache.cassandra.locator;
import java.io.IOException;
import java.net.InetAddress;
import org.apache.cassandra.exceptions.ConfigurationException;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.service.StorageService;
@ -39,16 +41,29 @@ import org.apache.cassandra.service.StorageService;
*/
public class Ec2MultiRegionSnitch extends Ec2Snitch
{
private static final String PUBLIC_IP_QUERY_URL = "http://169.254.169.254/latest/meta-data/public-ipv4";
private static final String PRIVATE_IP_QUERY_URL = "http://169.254.169.254/latest/meta-data/local-ipv4";
@VisibleForTesting
static final String PUBLIC_IP_QUERY = "/latest/meta-data/public-ipv4";
@VisibleForTesting
static final String PRIVATE_IP_QUERY = "/latest/meta-data/local-ipv4";
private final String localPrivateAddress;
public Ec2MultiRegionSnitch() throws IOException, ConfigurationException
{
super();
InetAddress localPublicAddress = InetAddress.getByName(awsApiCall(PUBLIC_IP_QUERY_URL));
this(new SnitchProperties());
}
public Ec2MultiRegionSnitch(SnitchProperties props) throws IOException, ConfigurationException
{
this(props, Ec2MetadataServiceConnector.create(props));
}
@VisibleForTesting
public Ec2MultiRegionSnitch(SnitchProperties props, Ec2MetadataServiceConnector connector) throws IOException
{
super(props, connector);
InetAddress localPublicAddress = InetAddress.getByName(connector.apiCall(PUBLIC_IP_QUERY));
logger.info("EC2Snitch using publicIP as identifier: {}", localPublicAddress);
localPrivateAddress = awsApiCall(PRIVATE_IP_QUERY_URL);
localPrivateAddress = connector.apiCall(PRIVATE_IP_QUERY);
// use the Public IP to broadcast Address to other nodes.
DatabaseDescriptor.setBroadcastAddress(localPublicAddress);
if (DatabaseDescriptor.getBroadcastRpcAddress() == null)

View File

@ -17,15 +17,11 @@
*/
package org.apache.cassandra.locator;
import java.io.DataInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.SystemKeyspace;
@ -33,26 +29,56 @@ 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;
/**
* A snitch that assumes an EC2 region is a DC and an EC2 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.
* Since CASSANDRA-16555, it is possible to choose version of AWS IMDS.
*
* By default, since CASSANDRA-16555, IMDSv2 is used.
*
* The version of IMDS is driven by property {@link Ec2MetadataServiceConnector#EC2_METADATA_TYPE_PROPERTY} and
* can be of value either 'v1' or 'v2'.
*
* It is possible to specify custom URL of IMDS by {@link Ec2MetadataServiceConnector#EC2_METADATA_URL_PROPERTY}.
* A user is not meant to change this under normal circumstances, it is suitable for testing only.
*
* IMDSv2 is secured by a token which needs to be fetched from IDMSv2 first, and it has to be passed in a header
* for the actual queries to IDMSv2. Ec2Snitch is doing this automatically. The only configuration parameter exposed
* to a user is {@link Ec2MetadataServiceConnector.V2Connector#AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY}
* 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
{
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";
@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";
private Map<InetAddress, Map<String, String>> savedEndpoints;
protected String ec2zone;
protected String ec2region;
protected final Ec2MetadataServiceConnector connector;
public Ec2Snitch() throws IOException, ConfigurationException
{
String az = awsApiCall(ZONE_NAME_QUERY_URL);
this(new SnitchProperties());
}
public Ec2Snitch(SnitchProperties props) throws IOException, ConfigurationException
{
this(props, Ec2MetadataServiceConnector.create(props));
}
@VisibleForTesting
public Ec2Snitch(SnitchProperties props, Ec2MetadataServiceConnector connector) throws IOException
{
this.connector = connector;
String az = connector.apiCall(ZONE_NAME_QUERY);
// Split "us-east-1a" or "asia-1a" into "us-east"/"1a" and "asia"/"1a".
String[] splits = az.split("-");
ec2zone = splits[splits.length - 1];
@ -67,31 +93,6 @@ public class Ec2Snitch extends AbstractNetworkTopologySnitch
logger.info("EC2Snitch using region: {}, zone: {}.", ec2region, ec2zone);
}
String awsApiCall(String url) throws IOException, ConfigurationException
{
// 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");
if (conn.getResponseCode() != 200)
throw new ConfigurationException("Ec2Snitch was unable to execute the API call. Not an ec2 node?");
// 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);
}
finally
{
FileUtils.close(d);
conn.disconnect();
}
}
public String getRack(InetAddress endpoint)
{
if (endpoint.equals(FBUtilities.getBroadcastAddress()))

View File

@ -21,6 +21,8 @@ import java.io.InputStream;
import java.net.URL;
import java.util.Properties;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.io.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -59,6 +61,12 @@ public class SnitchProperties
}
}
@VisibleForTesting
public SnitchProperties(Properties properties)
{
this.properties = properties;
}
/**
* Get a snitch property value or return defaultValue if not defined.
*/

View File

@ -0,0 +1,106 @@
/*
* 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.time.Duration;
import java.util.Properties;
import org.junit.Test;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.locator.Ec2MetadataServiceConnector.EC2MetadataType;
import org.apache.cassandra.locator.Ec2MetadataServiceConnector.V1Connector;
import org.apache.cassandra.locator.Ec2MetadataServiceConnector.V2Connector;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
import static org.apache.cassandra.locator.Ec2MetadataServiceConnector.DEFAULT_EC2_METADATA_URL;
import static org.apache.cassandra.locator.Ec2MetadataServiceConnector.EC2_METADATA_TYPE_PROPERTY;
import static org.apache.cassandra.locator.Ec2MetadataServiceConnector.V2Connector.AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY;
import static org.apache.cassandra.locator.Ec2MetadataServiceConnector.V2Connector.MAX_TOKEN_TIME_IN_SECONDS;
import static org.apache.cassandra.locator.Ec2MetadataServiceConnector.V2Connector.MIN_TOKEN_TIME_IN_SECONDS;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class Ec2ConnectorTest
{
@Test
public void testV1Configuration()
{
Properties p = new Properties();
p.setProperty(EC2_METADATA_TYPE_PROPERTY, EC2MetadataType.v1.name());
Ec2MetadataServiceConnector ec2Connector = Ec2MetadataServiceConnector.create(new SnitchProperties(p));
assertTrue(ec2Connector instanceof V1Connector);
assertEquals(DEFAULT_EC2_METADATA_URL, ec2Connector.metadataServiceUrl);
}
@Test
public void testV2Configuration()
{
Ec2MetadataServiceConnector ec2Connector = Ec2MetadataServiceConnector.create(new SnitchProperties(new Properties()));
// v2 connector by default
assertTrue(ec2Connector instanceof V2Connector);
assertEquals(DEFAULT_EC2_METADATA_URL, ec2Connector.metadataServiceUrl);
assertEquals(Duration.ofSeconds(MAX_TOKEN_TIME_IN_SECONDS), ((V2Connector) ec2Connector).tokenTTL);
}
@Test
public void testInvalidConfiguration()
{
assertThatExceptionOfType(ConfigurationException.class)
.describedAs("it should not be possible create a connector of type non-existent")
.isThrownBy(() -> {
Properties p = new Properties();
p.setProperty(EC2_METADATA_TYPE_PROPERTY, "non-existent");
Ec2MetadataServiceConnector.create(new SnitchProperties(p));
}).withMessage(format("%s must be one of %s", EC2_METADATA_TYPE_PROPERTY,
stream(EC2MetadataType.values()).map(Enum::name).collect(joining(", "))));
assertThatExceptionOfType(ConfigurationException.class)
.describedAs(format("it should not be possible to set %s to more than %s",
AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY,
MAX_TOKEN_TIME_IN_SECONDS))
.isThrownBy(() -> {
Properties p = new Properties();
p.setProperty(EC2_METADATA_TYPE_PROPERTY, EC2MetadataType.v2.name());
p.setProperty(AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY, "50000");
Ec2MetadataServiceConnector.create(new SnitchProperties(p));
}).withMessage(format("property %s was set to 50000 seconds which is not in allowed range of [%s..%s]",
AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY,
MIN_TOKEN_TIME_IN_SECONDS,
MAX_TOKEN_TIME_IN_SECONDS));
assertThatExceptionOfType(ConfigurationException.class)
.describedAs("ttl value should be integer")
.isThrownBy(() -> {
Properties p = new Properties();
p.setProperty(EC2_METADATA_TYPE_PROPERTY, EC2MetadataType.v2.name());
p.setProperty(AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY, "abc");
Ec2MetadataServiceConnector.create(new SnitchProperties(p));
}).withMessage(format("Unable to parse integer from property %s, value to parse: abc",
AWS_EC2_METADATA_TOKEN_TTL_SECONDS_HEADER_PROPERTY));
}
}

View File

@ -18,15 +18,14 @@
package org.apache.cassandra.locator;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.EnumMap;
import java.util.Map;
import java.util.Properties;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@ -42,11 +41,12 @@ import org.apache.cassandra.net.OutboundTcpConnectionPool;
import org.apache.cassandra.service.StorageService;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class EC2SnitchTest
public class Ec2SnitchTest
{
private static String az;
@BeforeClass
public static void setup() throws Exception
{
@ -58,25 +58,13 @@ public class EC2SnitchTest
StorageService.instance.initServer(0);
}
private class TestEC2Snitch extends Ec2Snitch
{
public TestEC2Snitch() throws IOException, ConfigurationException
{
super();
}
@Override
String awsApiCall(String url) throws IOException, ConfigurationException
{
return az;
}
}
@Test
public void testRac() throws IOException, ConfigurationException
{
az = "us-east-1d";
Ec2Snitch snitch = new TestEC2Snitch();
Ec2MetadataServiceConnector connectorMock = mock(Ec2MetadataServiceConnector.class);
when(connectorMock.apiCall(anyString())).thenReturn("us-east-1d");
Ec2Snitch snitch = new Ec2Snitch(new SnitchProperties(new Properties()), connectorMock);
InetAddress local = InetAddress.getByName("127.0.0.1");
InetAddress nonlocal = InetAddress.getByName("127.0.0.7");
@ -92,12 +80,13 @@ public class EC2SnitchTest
assertEquals("us-east", snitch.getDatacenter(local));
assertEquals("1d", snitch.getRack(local));
}
@Test
public void testNewRegions() throws IOException, ConfigurationException
{
az = "us-east-2d";
Ec2Snitch snitch = new TestEC2Snitch();
Ec2MetadataServiceConnector connectorMock = mock(Ec2MetadataServiceConnector.class);
when(connectorMock.apiCall(anyString())).thenReturn( "us-east-2d");
Ec2Snitch snitch = new Ec2Snitch(new SnitchProperties(new Properties()), connectorMock);
InetAddress local = InetAddress.getByName("127.0.0.1");
assertEquals("us-east-2", snitch.getDatacenter(local));
assertEquals("2d", snitch.getRack(local));
@ -110,13 +99,13 @@ public class EC2SnitchTest
InetAddress com_ip = InetAddress.getByName("127.0.0.3");
OutboundTcpConnectionPool pool = MessagingService.instance().getConnectionPool(me);
Assert.assertEquals(me, pool.endPoint());
assertEquals(me, pool.endPoint());
pool.reset(com_ip);
Assert.assertEquals(com_ip, pool.endPoint());
assertEquals(com_ip, pool.endPoint());
MessagingService.instance().destroyConnectionPool(me);
pool = MessagingService.instance().getConnectionPool(me);
Assert.assertEquals(com_ip, pool.endPoint());
assertEquals(com_ip, pool.endPoint());
}
@AfterClass