Skip to content

Commit df3b842

Browse files
committed
HADOOP-19681: Fix S3A failing to initialize S3 buckets having namespace with dot followed by number
1 parent 7063d58 commit df3b842

File tree

9 files changed

+69
-8
lines changed

9 files changed

+69
-8
lines changed

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/AbstractFileSystem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ private URI getUri(URI uri, String supportedScheme,
335335
int port = uri.getPort();
336336
port = (port == -1 ? defaultPort : port);
337337
if (port == -1) { // no port supplied and default port is not specified
338-
return new URI(supportedScheme, authority, "/", null);
338+
return URI.create(supportedScheme + "://" + authority);
339339
}
340340
return new URI(supportedScheme + "://" + uri.getHost() + ":" + port);
341341
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,11 @@ private static void addDeprecatedKeys() {
573573
*/
574574
public void initialize(URI name, Configuration originalConf)
575575
throws IOException {
576-
// get the host; this is guaranteed to be non-null, non-empty
576+
// get the host; fallback to authority if getHost() returns null
577577
bucket = name.getHost();
578+
if (bucket == null) {
579+
bucket = name.getAuthority();
580+
}
578581
AuditSpan span = null;
579582
// track initialization duration; will only be set after
580583
// statistics are set up.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public int run(final String[] args, final PrintStream out)
172172
final String bucketPath = parsedArgs.get(0);
173173
final Path source = new Path(bucketPath);
174174
URI fsURI = source.toUri();
175-
String bucket = fsURI.getHost();
175+
String bucket = fsURI.getHost() != null ? fsURI.getHost() : fsURI.getAuthority();
176176

177177
println(out, "Filesystem %s", fsURI);
178178
if (!"s3a".equals(fsURI.getScheme())) {

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3native/S3xLoginHelper.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,12 @@ public static URI buildFSURI(URI uri) {
5656
// look for login secrets and fail if they are present.
5757
Objects.requireNonNull(uri, "null uri");
5858
Objects.requireNonNull(uri.getScheme(), "null uri.getScheme()");
59-
Objects.requireNonNull(uri.getHost(), "null uri host.");
60-
return URI.create(uri.getScheme() + "://" + uri.getHost());
59+
String host = uri.getHost();
60+
if (host == null) {
61+
host = uri.getAuthority();
62+
}
63+
Objects.requireNonNull(host, "null uri host.");
64+
return URI.create(uri.getScheme() + "://" + host);
6165
}
6266

6367
/**

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.apache.commons.lang3.reflect.FieldUtils;
4949
import org.apache.hadoop.conf.Configuration;
5050
import org.apache.hadoop.fs.FileStatus;
51+
import org.apache.hadoop.fs.FileSystem;
5152
import org.apache.hadoop.fs.LocalDirAllocator;
5253
import org.apache.hadoop.fs.Path;
5354
import org.apache.hadoop.fs.contract.ContractTestUtils;
@@ -594,13 +595,28 @@ private static <T> T getField(Object target, Class<T> fieldType,
594595
public void testConfOptionPropagationToFS() throws Exception {
595596
Configuration config = new Configuration();
596597
String testFSName = config.getTrimmed(TEST_FS_S3A_NAME, "");
597-
String bucket = new URI(testFSName).getHost();
598+
URI uri = new URI(testFSName);
599+
String bucket = uri.getHost();
600+
if (bucket == null) {
601+
bucket = uri.getAuthority();
602+
}
598603
setBucketOption(config, bucket, "propagation", "propagated");
599604
fs = S3ATestUtils.createTestFileSystem(config);
600605
Configuration updated = fs.getConf();
601606
assertOptionEquals(updated, "fs.s3a.propagation", "propagated");
602607
}
603608

609+
@Test
610+
public void testBucketNameWithDotAndNumber() throws Exception {
611+
Configuration config = new Configuration();
612+
Path path = new Path("s3a://test-bucket-v1.1");
613+
try (FileSystem fs = path.getFileSystem(config)) {
614+
assertThat(fs instanceof S3AFileSystem)
615+
.describedAs("FileSystem should be S3AFileSystem instance")
616+
.isTrue();
617+
}
618+
}
619+
604620
@Test
605621
@Timeout(10)
606622
public void testS3SpecificSignerOverride() throws Exception {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,11 @@ public static <T extends Writable> T roundTrip(
883883
public static String getTestBucketName(final Configuration conf) {
884884
String bucket = checkNotNull(conf.get(TEST_FS_S3A_NAME),
885885
"No test bucket");
886-
return URI.create(bucket).getHost();
886+
URI uri = URI.create(bucket);
887+
if (uri.getHost() != null) {
888+
return uri.getHost();
889+
}
890+
return uri.getAuthority();
887891
}
888892

889893
/**

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import org.apache.hadoop.conf.Configuration;
3131
import org.apache.hadoop.fs.s3a.auth.delegation.EncryptionSecrets;
32+
import org.apache.hadoop.fs.s3native.S3xLoginHelper;
3233
import org.apache.hadoop.security.ProviderUtils;
3334
import org.apache.hadoop.security.alias.CredentialProvider;
3435
import org.apache.hadoop.security.alias.CredentialProviderFactory;
@@ -75,6 +76,24 @@ public void setup() throws Exception {
7576
S3AFileSystem.initializeClass();
7677
}
7778

79+
@Test
80+
public void testS3xLoginHelperWithDotInBucketName() throws Throwable {
81+
// Test buildFSURI with bucket name containing dot followed by number
82+
URI uri = URI.create("s3a://bucket-v1.1/path");
83+
URI result = S3xLoginHelper.buildFSURI(uri);
84+
assertEquals("s3a://bucket-v1.1", result.toString());
85+
86+
// Test with normal bucket name
87+
URI normalUri = URI.create("s3a://normal-bucket/path");
88+
URI normalResult = S3xLoginHelper.buildFSURI(normalUri);
89+
assertEquals("s3a://normal-bucket", normalResult.toString());
90+
91+
// Test edge case with multiple dots
92+
URI multiDotUri = URI.create("s3a://bucket.v1.2.test/path");
93+
URI multiDotResult = S3xLoginHelper.buildFSURI(multiDotUri);
94+
assertEquals("s3a://bucket.v1.2.test", multiDotResult.toString());
95+
}
96+
7897
@Test
7998
public void testBucketConfigurationPropagation() throws Throwable {
8099
Configuration config = new Configuration(false);

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,17 @@ public SdkHttpFullRequest sign(SdkHttpFullRequest request,
283283

284284
String host = request.host();
285285
String bucketName = parseBucketFromHost(host);
286+
// If host-based parsing fails (path-style requests), extract from path
287+
if (bucketName.equals("s3")) {
288+
String path = request.encodedPath();
289+
if (path != null && path.startsWith("/") && path.length() > 1) {
290+
String[] pathParts = path.substring(1).split("/", 2);
291+
if (pathParts.length > 0 && !pathParts[0].isEmpty()) {
292+
bucketName = pathParts[0];
293+
}
294+
}
295+
}
296+
286297
try {
287298
lastStoreValue = CustomSignerInitializer
288299
.getStoreValue(bucketName, UserGroupInformation.getCurrentUser());

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,12 @@ private String run(CredentialShell cs, String expected, String... args)
187187
*/
188188
private String toJceksProvider(Path keystore) {
189189
final URI uri = keystore.toUri();
190+
String bucket = uri.getHost();
191+
if (bucket == null) {
192+
bucket = uri.getAuthority();
193+
}
190194
return String.format("jceks://%s@%s%s",
191-
uri.getScheme(), uri.getHost(), uri.getPath());
195+
uri.getScheme(), bucket, uri.getPath());
192196
}
193197

194198
}

0 commit comments

Comments
 (0)