-
Notifications
You must be signed in to change notification settings - Fork 9.2k
HADOOP-19044. AWS SDK V2 - Update S3A region logic #6479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
872dcac
d8c9793
cefb30b
81d345b
05225bb
2c653da
99cf2d4
12ff2f1
fcf56cf
6197723
adb10d3
54e5c66
e65a51a
2b89855
5fdb23d
b1f4df9
44760ca
12e503e
09ff933
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -267,9 +267,10 @@ protected ClientOverrideConfiguration.Builder createClientOverrideConfiguration( | |
| */ | ||
| private <BuilderT extends S3BaseClientBuilder<BuilderT, ClientT>, ClientT> void configureEndpointAndRegion( | ||
| BuilderT builder, S3ClientCreationParameters parameters, Configuration conf) { | ||
| URI endpoint = getS3Endpoint(parameters.getEndpoint(), conf); | ||
| final String endpointStr = parameters.getEndpoint(); | ||
| final URI endpoint = getS3Endpoint(endpointStr, conf); | ||
|
|
||
| String configuredRegion = parameters.getRegion(); | ||
| final String configuredRegion = parameters.getRegion(); | ||
| Region region = null; | ||
| String origin = ""; | ||
|
|
||
|
|
@@ -289,17 +290,36 @@ private <BuilderT extends S3BaseClientBuilder<BuilderT, ClientT>, ClientT> void | |
| builder.fipsEnabled(fipsEnabled); | ||
|
|
||
| if (endpoint != null) { | ||
| boolean overrideEndpoint = true; | ||
| checkArgument(!fipsEnabled, | ||
| "%s : %s", ERROR_ENDPOINT_WITH_FIPS, endpoint); | ||
| builder.endpointOverride(endpoint); | ||
| // No region was configured, try to determine it from the endpoint. | ||
| if (region == null) { | ||
| region = getS3RegionFromEndpoint(parameters.getEndpoint()); | ||
| boolean endpointEndsWithCentral = | ||
| endpointStr.endsWith(CENTRAL_ENDPOINT); | ||
| // No region was configured or the endpoint is central, | ||
| // determine the region from the endpoint. | ||
| if (region == null || endpointEndsWithCentral) { | ||
| region = getS3RegionFromEndpoint(endpointStr, | ||
| endpointEndsWithCentral); | ||
| if (region != null) { | ||
| origin = "endpoint"; | ||
| if (endpointEndsWithCentral) { | ||
| // No need to override endpoint with "s3.amazonaws.com". | ||
| // Let the client take care of endpoint resolution. Overriding | ||
| // the endpoint with "s3.amazonaws.com" causes 400 Bad Request | ||
| // errors for non-existent buckets and objects. | ||
| // ref: https://github.com/aws/aws-sdk-java-v2/issues/4846 | ||
| overrideEndpoint = false; | ||
| builder.crossRegionAccessEnabled(true); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add more detail to origin, e,g 'origin with cross-region access"
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| origin = "origin with cross region access"; | ||
| LOG.debug("Enabling cross region access for endpoint {}", | ||
| endpointStr); | ||
| } | ||
| } | ||
| } | ||
| LOG.debug("Setting endpoint to {}", endpoint); | ||
| if (overrideEndpoint) { | ||
| builder.endpointOverride(endpoint); | ||
| LOG.debug("Setting endpoint to {}", endpoint); | ||
| } | ||
| } | ||
|
|
||
| if (region != null) { | ||
|
|
@@ -354,20 +374,32 @@ private static URI getS3Endpoint(String endpoint, final Configuration conf) { | |
|
|
||
| /** | ||
| * Parses the endpoint to get the region. | ||
| * If endpoint is the central one, use US_EAST_1. | ||
| * If endpoint is the central one, use US_EAST_2. | ||
| * | ||
| * @param endpoint the configure endpoint. | ||
| * @param endpointEndsWithCentral true if the endpoint is configured as central. | ||
| * @return the S3 region, null if unable to resolve from endpoint. | ||
| */ | ||
| private static Region getS3RegionFromEndpoint(String endpoint) { | ||
| private static Region getS3RegionFromEndpoint(final String endpoint, | ||
| final boolean endpointEndsWithCentral) { | ||
|
|
||
| if(!endpoint.endsWith(CENTRAL_ENDPOINT)) { | ||
| if (!endpointEndsWithCentral) { | ||
| LOG.debug("Endpoint {} is not the default; parsing", endpoint); | ||
| return AwsHostNameUtils.parseSigningRegion(endpoint, S3_SERVICE_NAME).orElse(null); | ||
| } | ||
|
|
||
| // endpoint is for US_EAST_1; | ||
| return Region.US_EAST_1; | ||
| // Select default region here to enable cross-region access. | ||
|
steveloughran marked this conversation as resolved.
|
||
| // If both "fs.s3a.endpoint" and "fs.s3a.endpoint.region" are empty, | ||
| // Spark sets "fs.s3a.endpoint" to "s3.amazonaws.com". | ||
| // This applies to Spark versions with the changes of SPARK-35878. | ||
| // ref: | ||
| // https://github.com/apache/spark/blob/v3.5.0/core/ | ||
| // src/main/scala/org/apache/spark/deploy/SparkHadoopUtil.scala#L528 | ||
| // If we do not allow cross region access, Spark would not be able to | ||
| // access any bucket that is not present in the given region. | ||
| // Hence, we should use default region us-east-2 to allow cross-region | ||
| // access. | ||
| return Region.of(AWS_S3_DEFAULT_REGION); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,10 +38,14 @@ | |
| import software.amazon.awssdk.services.s3.model.HeadBucketResponse; | ||
|
|
||
| import org.apache.hadoop.conf.Configuration; | ||
| import org.apache.hadoop.fs.FSDataOutputStream; | ||
| import org.apache.hadoop.fs.Path; | ||
| import org.apache.hadoop.fs.contract.ContractTestUtils; | ||
| import org.apache.hadoop.fs.s3a.statistics.impl.EmptyS3AStatisticsContext; | ||
|
|
||
| import static org.apache.hadoop.fs.s3a.Constants.AWS_REGION; | ||
| import static org.apache.hadoop.fs.s3a.Constants.CENTRAL_ENDPOINT; | ||
| import static org.apache.hadoop.fs.s3a.Constants.ENDPOINT; | ||
| import static org.apache.hadoop.fs.s3a.Constants.PATH_STYLE_ACCESS; | ||
| import static org.apache.hadoop.fs.s3a.DefaultS3ClientFactory.ERROR_ENDPOINT_WITH_FIPS; | ||
| import static org.apache.hadoop.fs.s3a.S3ATestUtils.removeBaseAndBucketOverrides; | ||
|
|
@@ -146,7 +150,21 @@ public void testCentralEndpoint() throws Throwable { | |
| describe("Create a client with the central endpoint"); | ||
| Configuration conf = getConfiguration(); | ||
|
|
||
| S3Client client = createS3Client(conf, CENTRAL_ENDPOINT, null, US_EAST_1, false); | ||
| S3Client client = createS3Client(conf, CENTRAL_ENDPOINT, null, US_EAST_2, false); | ||
|
|
||
| expectInterceptorException(client); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCentralEndpointWithRegion() throws Throwable { | ||
| describe("Create a client with the central endpoint but also specify region"); | ||
| Configuration conf = getConfiguration(); | ||
|
|
||
| S3Client client = createS3Client(conf, CENTRAL_ENDPOINT, US_WEST_2, US_EAST_2, false); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for example here, if configured region is US_WEST_2, expected region should also be US_WEST_2, not US_EAST_2
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as in #6466 I'm going to propose we make the static methods accessible and unit tests to validate them, because
|
||
|
|
||
| expectInterceptorException(client); | ||
|
|
||
| client = createS3Client(conf, CENTRAL_ENDPOINT, US_EAST_1, US_EAST_2, false); | ||
|
|
||
| expectInterceptorException(client); | ||
| } | ||
|
|
@@ -257,6 +275,65 @@ public void testWithVPCE() throws Throwable { | |
| expectInterceptorException(client); | ||
| } | ||
|
|
||
| @Test | ||
|
ahmarsuhail marked this conversation as resolved.
|
||
| public void testCentralEndpointCrossRegionAccess() throws Throwable { | ||
| describe("Create bucket on different region and access it using central endpoint"); | ||
| final Configuration conf = getConfiguration(); | ||
| removeBaseAndBucketOverrides(conf, ENDPOINT); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what should region be set to here? either unset it or explicitly set it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure will set region here because null region is anyways covered below. |
||
|
|
||
| final Configuration newConf = new Configuration(conf); | ||
|
|
||
| newConf.set(ENDPOINT, CENTRAL_ENDPOINT); | ||
|
|
||
| newFS = new S3AFileSystem(); | ||
| newFS.initialize(getFileSystem().getUri(), newConf); | ||
|
|
||
| assertOpsUsingNewFs(); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCentralEndpointWithNullRegionCrossRegionAccess() throws Throwable { | ||
| describe( | ||
| "Create bucket on different region and access it using central endpoint and null region"); | ||
| final Configuration conf = getConfiguration(); | ||
| removeBaseAndBucketOverrides(conf, ENDPOINT, AWS_REGION); | ||
|
|
||
| final Configuration newConf = new Configuration(conf); | ||
|
|
||
| newConf.set(ENDPOINT, CENTRAL_ENDPOINT); | ||
|
|
||
| newFS = new S3AFileSystem(); | ||
| newFS.initialize(getFileSystem().getUri(), newConf); | ||
|
|
||
| assertOpsUsingNewFs(); | ||
| } | ||
|
|
||
| private void assertOpsUsingNewFs() throws IOException { | ||
| final String file = getMethodName(); | ||
|
ahmarsuhail marked this conversation as resolved.
|
||
| final Path basePath = methodPath(); | ||
| final Path srcDir = new Path(basePath, "srcdir"); | ||
| newFS.mkdirs(srcDir); | ||
| Path srcFilePath = new Path(srcDir, file); | ||
|
|
||
| try (FSDataOutputStream out = newFS.create(srcFilePath)) { | ||
| out.write(new byte[] {1, 2, 3}); | ||
| } | ||
|
|
||
| ContractTestUtils.assertIsFile(getFileSystem(), srcFilePath); | ||
| ContractTestUtils.assertIsFile(newFS, srcFilePath); | ||
|
|
||
| newFS.delete(srcDir, true); | ||
|
|
||
| Assertions | ||
| .assertThat(newFS.exists(srcFilePath)) | ||
| .describedAs("Existence of file: " + srcFilePath) | ||
| .isFalse(); | ||
| Assertions | ||
| .assertThat(getFileSystem().exists(srcFilePath)) | ||
| .describedAs("Existence of file: " + srcFilePath) | ||
| .isFalse(); | ||
| } | ||
|
|
||
| private final class RegionInterceptor implements ExecutionInterceptor { | ||
| private final String endpoint; | ||
| private final String region; | ||
|
|
@@ -272,7 +349,7 @@ private final class RegionInterceptor implements ExecutionInterceptor { | |
| public void beforeExecution(Context.BeforeExecution context, | ||
| ExecutionAttributes executionAttributes) { | ||
|
|
||
| if (endpoint != null) { | ||
| if (endpoint != null && !endpoint.endsWith(CENTRAL_ENDPOINT)) { | ||
| Assertions.assertThat( | ||
| executionAttributes.getAttribute(AwsExecutionAttribute.ENDPOINT_OVERRIDDEN)) | ||
| .describedAs("Endpoint not overridden").isTrue(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, not sure about this. now we're parsing region if region is null or endpoint = s3.amazonaws.com.
So if you set
s3.amazonaws.comand region to eu-west-2, you still end up with us setting the region tous-east-2and cross region enabled. My thinking here is that a lot of people may have endpoint set to s3.amazonaws.com (as atleast with SDK V1 it was harmless to do that I think) .we only want to get into this parsing if region == null. so let's revert to the previous condition here. And then we never don't want to override if the endpoint is s3.amazonaws.com. Suggested:
So now:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even for user configured region, for sdk to figure out, we still need to enable cross region access right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise we will have same problem i suppose e.g. bucket on us-west-2 won't be accessible by central endpoint and us-west-1 combination. It will only be accessible by central endpoint and null region combination.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, SDK fill figure out the endpoint even if cross region is not enabled. cross region is only if you don't know the region, so we set a random region and enable it. it doesn't effect endpoint resolution behaviour afaik
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, will test this out today. Thanks a lot for the reviews!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think anyone should set region=us-west-2 and endpoint = us-west-1 unless they like debugging things.
all we want is to handle situations where things are not set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant "central endpoint" with "us-west-1" region, to access bucket created on us-west-2. I will test out the combination. Thanks
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For case#2, if user sets region to eu-west-1, is it possible that bucket might be on different region? If the answer is yes and if we want to cover that case, we will have to enable cross-region access for
endpoint = s3.amazonaws.com, regardless of the region set (null or any particular one).We should cover this, otherwise we get redirect errors, i will update the PR.