Skip to content

Commit c398350

Browse files
committed
HADOOP-19286: S3A: Support cross region access when S3 region/endpoint is set
1 parent 9a53df2 commit c398350

File tree

5 files changed

+93
-7
lines changed

5 files changed

+93
-7
lines changed

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,6 +1362,19 @@ private Constants() {
13621362
*/
13631363
public static final String XA_HEADER_PREFIX = "header.";
13641364

1365+
/**
1366+
* S3 cross region access enabled ?
1367+
* Value: {@value}.
1368+
*/
1369+
1370+
public static final String AWS_S3_CROSS_REGION_ACCESS_ENABLED =
1371+
"fs.s3a.cross.region.access.enabled";
1372+
/**
1373+
* Default value for S3 cross region access enabled: {@value}.
1374+
*/
1375+
public static final boolean AWS_S3_CROSS_REGION_ACCESS_ENABLED_DEFAULT = true;
1376+
1377+
13651378
/**
13661379
* AWS S3 region for the bucket. When set bypasses the construction of
13671380
* region through endpoint url.

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/DefaultS3ClientFactory.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
import org.apache.hadoop.fs.store.LogExactlyOnce;
5656

5757
import static org.apache.hadoop.fs.s3a.Constants.AWS_REGION;
58+
import static org.apache.hadoop.fs.s3a.Constants.AWS_S3_CROSS_REGION_ACCESS_ENABLED;
59+
import static org.apache.hadoop.fs.s3a.Constants.AWS_S3_CROSS_REGION_ACCESS_ENABLED_DEFAULT;
5860
import static org.apache.hadoop.fs.s3a.Constants.AWS_S3_DEFAULT_REGION;
5961
import static org.apache.hadoop.fs.s3a.Constants.CENTRAL_ENDPOINT;
6062
import static org.apache.hadoop.fs.s3a.Constants.FIPS_ENDPOINT;
@@ -259,8 +261,10 @@ protected ClientOverrideConfiguration.Builder createClientOverrideConfiguration(
259261
* <li>If endpoint is configured via via fs.s3a.endpoint, set it.
260262
* If no region is configured, try to parse region from endpoint. </li>
261263
* <li> If no region is configured, and it could not be parsed from the endpoint,
262-
* set the default region as US_EAST_2 and enable cross region access. </li>
264+
* set the default region as US_EAST_2</li>
263265
* <li> If configured region is empty, fallback to SDK resolution chain. </li>
266+
* <li> S3 cross region is enabled by default irrespective of region or endpoint
267+
* is set or not.</li>
264268
* </ol>
265269
*
266270
* @param builder S3 client builder.
@@ -320,7 +324,6 @@ private <BuilderT extends S3BaseClientBuilder<BuilderT, ClientT>, ClientT> void
320324
builder.endpointOverride(endpoint);
321325
LOG.debug("Setting endpoint to {}", endpoint);
322326
} else {
323-
builder.crossRegionAccessEnabled(true);
324327
origin = "central endpoint with cross region access";
325328
LOG.debug("Enabling cross region access for endpoint {}",
326329
endpointStr);
@@ -333,7 +336,6 @@ private <BuilderT extends S3BaseClientBuilder<BuilderT, ClientT>, ClientT> void
333336
// no region is configured, and none could be determined from the endpoint.
334337
// Use US_EAST_2 as default.
335338
region = Region.of(AWS_S3_DEFAULT_REGION);
336-
builder.crossRegionAccessEnabled(true);
337339
builder.region(region);
338340
origin = "cross region access fallback";
339341
} else if (configuredRegion.isEmpty()) {
@@ -344,8 +346,14 @@ private <BuilderT extends S3BaseClientBuilder<BuilderT, ClientT>, ClientT> void
344346
LOG.debug(SDK_REGION_CHAIN_IN_USE);
345347
origin = "SDK region chain";
346348
}
347-
348-
LOG.debug("Setting region to {} from {}", region, origin);
349+
boolean isCrossRegionAccessEnabled = conf.getBoolean(AWS_S3_CROSS_REGION_ACCESS_ENABLED,
350+
AWS_S3_CROSS_REGION_ACCESS_ENABLED_DEFAULT);
351+
// s3 cross region access
352+
if (isCrossRegionAccessEnabled) {
353+
builder.crossRegionAccessEnabled(true);
354+
}
355+
LOG.debug("Setting region to {} from {} with cross region access {}",
356+
region, origin, isCrossRegionAccessEnabled);
349357
}
350358

351359
/**

hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/connecting.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ There are multiple ways to connect to an S3 bucket
4848

4949
The S3A connector supports all these; S3 Endpoints are the primary mechanism used -either explicitly declared or automatically determined from the declared region of the bucket.
5050

51+
The S3A connector supports S3 cross region access via AWS SDK which is enabled by default. This allows users to access S3 buckets in a different region than the one defined in the S3 endpoint/region configuration, as long as they are within the same AWS partition. However, S3 cross-region access can be disabled by:
52+
```xml
53+
<property>
54+
<name>fs.s3a.cross.region.access.enabled</name>
55+
<value>false</value>
56+
<description>S3 cross region access</description>
57+
</property>
58+
```
59+
60+
5161
Not supported:
5262
* AWS [Snowball](https://aws.amazon.com/snowball/).
5363

hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3AConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ public void testCustomUserAgent() throws Exception {
439439
@Test
440440
public void testRequestTimeout() throws Exception {
441441
conf = new Configuration();
442+
skipIfCrossRegionClient(conf);
442443
// remove the safety check on minimum durations.
443444
AWSClientConfig.setMinimumOperationDuration(Duration.ZERO);
444445
try {
@@ -632,8 +633,8 @@ public static boolean isSTSSignerCalled() {
632633
*/
633634
private static void skipIfCrossRegionClient(
634635
Configuration configuration) {
635-
if (configuration.get(ENDPOINT, null) == null
636-
&& configuration.get(AWS_REGION, null) == null) {
636+
if (configuration.getBoolean(AWS_S3_CROSS_REGION_ACCESS_ENABLED,
637+
AWS_S3_CROSS_REGION_ACCESS_ENABLED_DEFAULT)) {
637638
skip("Skipping test as cross region client is in use ");
638639
}
639640
}

hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3AEndpointRegion.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@
4444
import org.apache.hadoop.fs.s3a.statistics.impl.EmptyS3AStatisticsContext;
4545
import org.apache.hadoop.fs.s3a.test.PublicDatasetTestUtils;
4646

47+
import static org.apache.hadoop.fs.contract.ContractTestUtils.skip;
4748
import static org.apache.hadoop.fs.s3a.Constants.ALLOW_REQUESTER_PAYS;
4849
import static org.apache.hadoop.fs.s3a.Constants.AWS_REGION;
50+
import static org.apache.hadoop.fs.s3a.Constants.AWS_S3_CROSS_REGION_ACCESS_ENABLED;
4951
import static org.apache.hadoop.fs.s3a.Constants.CENTRAL_ENDPOINT;
5052
import static org.apache.hadoop.fs.s3a.Constants.ENDPOINT;
5153
import static org.apache.hadoop.fs.s3a.Constants.FIPS_ENDPOINT;
@@ -71,6 +73,8 @@ public class ITestS3AEndpointRegion extends AbstractS3ATestBase {
7173

7274
private static final String US_WEST_2 = "us-west-2";
7375

76+
private static final String SA_EAST_1 = "sa-east-1";
77+
7478
private static final String EU_WEST_2 = "eu-west-2";
7579

7680
private static final String CN_NORTHWEST_1 = "cn-northwest-1";
@@ -346,6 +350,46 @@ public void testCentralEndpointAndDifferentRegionThanBucket() throws Throwable {
346350
assertRequesterPaysFileExistence(newConf);
347351
}
348352

353+
@Test
354+
public void testWithOutCrossRegionAccess() throws Exception {
355+
describe("Verify cross region access fails when disabled");
356+
// skip the test if the region is sa-east-1
357+
skipCrossRegionTest();
358+
final Configuration newConf = new Configuration(getConfiguration());
359+
removeBaseAndBucketOverrides(newConf,
360+
ENDPOINT,
361+
AWS_S3_CROSS_REGION_ACCESS_ENABLED,
362+
AWS_REGION);
363+
// disable cross region access
364+
newConf.setBoolean(AWS_S3_CROSS_REGION_ACCESS_ENABLED, false);
365+
newConf.set(AWS_REGION, SA_EAST_1);
366+
try (S3AFileSystem fs = new S3AFileSystem()) {
367+
fs.initialize(getFileSystem().getUri(), newConf);
368+
intercept(AWSRedirectException.class,
369+
"does not match the AWS region containing the bucket",
370+
() -> fs.exists(getFileSystem().getWorkingDirectory()));
371+
}
372+
}
373+
374+
@Test
375+
public void testWithCrossRegionAccess() throws Exception {
376+
describe("Verify cross region access succeed when enabled");
377+
// skip the test if the region is sa-east-1
378+
skipCrossRegionTest();
379+
final Configuration newConf = new Configuration(getConfiguration());
380+
removeBaseAndBucketOverrides(newConf,
381+
ENDPOINT,
382+
AWS_S3_CROSS_REGION_ACCESS_ENABLED,
383+
AWS_REGION);
384+
// enable cross region access
385+
newConf.setBoolean(AWS_S3_CROSS_REGION_ACCESS_ENABLED, true);
386+
newConf.set(AWS_REGION, SA_EAST_1);
387+
try (S3AFileSystem fs = new S3AFileSystem()) {
388+
fs.initialize(getFileSystem().getUri(), newConf);
389+
fs.exists(getFileSystem().getWorkingDirectory());
390+
}
391+
}
392+
349393
@Test
350394
public void testCentralEndpointAndSameRegionAsBucket() throws Throwable {
351395
describe("Access public bucket using central endpoint and region "
@@ -478,6 +522,16 @@ public void testCentralEndpointAndNullRegionFipsWithCRUD() throws Throwable {
478522
assertOpsUsingNewFs();
479523
}
480524

525+
/**
526+
* Skip the test if the region is null or sa-east-1.
527+
*/
528+
private void skipCrossRegionTest() throws IOException {
529+
String region = getFileSystem().getS3AInternals().getBucketMetadata().bucketRegion();
530+
if (region == null || SA_EAST_1.equals(region)) {
531+
skip("Skipping test since region is null or it is set to sa-east-1");
532+
}
533+
}
534+
481535
private void assertOpsUsingNewFs() throws IOException {
482536
final String file = getMethodName();
483537
final Path basePath = methodPath();

0 commit comments

Comments
 (0)