This is an automated email from the ASF dual-hosted git repository.
anujmodi2021 pushed a commit to branch branch-3.5
in repository https://gitbox.apache.org/repos/asf/hadoop.git
The following commit(s) were added to refs/heads/branch-3.5 by this push:
new b0c3d89a0e5 HADOOP-19854: [ABFS] Fixing Namespace Detection for FNS
Accounts with Soft Delete Enabled (#8410) (#8459)
b0c3d89a0e5 is described below
commit b0c3d89a0e5c24049750390b68717335be1d839d
Author: manika137 <[email protected]>
AuthorDate: Mon May 4 21:29:27 2026 +0530
HADOOP-19854: [ABFS] Fixing Namespace Detection for FNS Accounts with Soft
Delete Enabled (#8410) (#8459)
Contributed by Manika Joshi
---
.../fs/azurebfs/AzureBlobFileSystemStore.java | 29 +++++-
.../hadoop/fs/azurebfs/services/AbfsErrors.java | 4 +
.../fs/azurebfs/ITestGetNameSpaceEnabled.java | 103 ++++++++++++++++++++-
3 files changed, 130 insertions(+), 6 deletions(-)
diff --git
a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java
b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java
index 055cee06256..86416cc5206 100644
---
a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java
+++
b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java
@@ -162,6 +162,7 @@
import static
org.apache.hadoop.fs.azurebfs.constants.FileSystemConfigurations.INFINITE_LEASE_DURATION;
import static
org.apache.hadoop.fs.azurebfs.constants.FileSystemUriSchemes.ABFS_BLOB_DOMAIN_NAME;
import static
org.apache.hadoop.fs.azurebfs.constants.HttpHeaderConfigurations.X_MS_ENCRYPTION_CONTEXT;
+import static
org.apache.hadoop.fs.azurebfs.services.AbfsErrors.ERR_SOFT_DELETE_NOT_SUPPORTED;
import static
org.apache.hadoop.fs.azurebfs.utils.UriUtils.isKeyForDirectorySet;
/**
@@ -422,17 +423,35 @@ private synchronized boolean
getNamespaceEnabledInformationFromServer(
} catch (AbfsRestOperationException ex) {
// Get ACL status is a HEAD request, its response doesn't contain
errorCode
// So can only rely on its status code to determine account type.
- if (HttpURLConnection.HTTP_BAD_REQUEST != ex.getStatusCode()) {
+ int status = ex.getStatusCode();
+ String message = ex.getMessage();
+
+ // Case 1: 409: Soft delete not supported
+ if (status == HttpURLConnection.HTTP_CONFLICT
+ && message != null
+ && message.contains(ERR_SOFT_DELETE_NOT_SUPPORTED)) {
+ /*
+ * HTTP_CONFLICT with soft delete not supported error indicates a FNS
account.
+ * This occurs when:
+ * 1. FNS-Blob endpoint is used (due to DFS endpoint usage for getAcl
call)
+ * 2. FNS-DFS endpoint is used (later internally converted to Blob)
+ * For both cases, namespace should be disabled. The exception is
irrelevant here
+ * since we'll be using Blob endpoint ultimately for both the cases
here.
+ * No need to throw the exception.
+ */
+ LOG.debug("Ignore soft-delete error. Setting namespace enabled to
false.");
+ setNamespaceEnabled(false);
+ } else if (status == HttpURLConnection.HTTP_BAD_REQUEST) { // Case 2: 400
+ // If getAcl fails with 400, namespace is disabled.
+ LOG.debug("Failed to get ACL status with 400. Inferring namespace
disabled and ignoring error", ex);
+ setNamespaceEnabled(false);
+ } else {
// If getAcl fails with anything other than 400, namespace is enabled.
setNamespaceEnabled(true);
// Continue to throw exception as earlier.
LOG.debug("Failed to get ACL status with non 400. Inferring namespace
enabled", ex);
throw ex;
}
- // If getAcl fails with 400, namespace is disabled.
- LOG.debug("Failed to get ACL status with 400. "
- + "Inferring namespace disabled and ignoring error", ex);
- setNamespaceEnabled(false);
} catch (AzureBlobFileSystemException ex) {
throw ex;
}
diff --git
a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsErrors.java
b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsErrors.java
index fe7f3b5cb1b..b5744e1ba3a 100644
---
a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsErrors.java
+++
b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/services/AbfsErrors.java
@@ -59,6 +59,10 @@ public final class AbfsErrors {
+ "and cannot be appended to by the Azure Data Lake Storage Service API";
public static final String CONDITION_NOT_MET = "The condition specified
using "
+ "HTTP conditional header(s) is not met.";
+ public static final String ERR_READ_ON_DIRECTORY = "Read operation not
permitted on a directory.";
+ public static final String ERR_OPENFILE_ON_DIRECTORY = "openFileForRead must
be used with files and not directories";
+ public static final String ERR_SOFT_DELETE_NOT_SUPPORTED = "This endpoint
does not support BlobStorageEvents or SoftDelete.";
+
/**
* Exception message on filesystem init if token-provider-auth-type configs
are provided
*/
diff --git
a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestGetNameSpaceEnabled.java
b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestGetNameSpaceEnabled.java
index 9e310a8e9cd..38cde0b4137 100644
---
a/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestGetNameSpaceEnabled.java
+++
b/hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/ITestGetNameSpaceEnabled.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.util.UUID;
+import org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants;
import org.junit.jupiter.api.Test;
import org.assertj.core.api.Assertions;
import org.mockito.Mockito;
@@ -38,6 +39,7 @@
import org.apache.hadoop.fs.azurebfs.utils.TracingContext;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
+import static java.net.HttpURLConnection.HTTP_CONFLICT;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
@@ -47,6 +49,7 @@
import static
org.apache.hadoop.fs.azurebfs.constants.FileSystemUriSchemes.ABFS_BLOB_DOMAIN_NAME;
import static
org.apache.hadoop.fs.azurebfs.constants.FileSystemUriSchemes.ABFS_DFS_DOMAIN_NAME;
import static
org.apache.hadoop.fs.azurebfs.constants.TestConfigurationKeys.FS_AZURE_ACCOUNT_KEY;
+import static
org.apache.hadoop.fs.azurebfs.services.AbfsErrors.ERR_SOFT_DELETE_NOT_SUPPORTED;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
@@ -266,6 +269,8 @@ public void ensureGetAclDetermineHnsStatusAccurately()
throws Exception {
true, true);
ensureGetAclDetermineHnsStatusAccuratelyInternal(HTTP_UNAVAILABLE,
true, true);
+ ensureGetAclDetermineHnsStatusAccuratelyInternal(HTTP_CONFLICT,
+ false, false);
}
private void ensureGetAclDetermineHnsStatusAccuratelyInternal(int statusCode,
@@ -274,8 +279,11 @@ private void
ensureGetAclDetermineHnsStatusAccuratelyInternal(int statusCode,
AbfsClient mockClient = mock(AbfsClient.class);
store.getAbfsConfiguration().setIsNamespaceEnabledAccountForTesting(Trilean.UNKNOWN);
doReturn(mockClient).when(store).getClient(AbfsServiceType.DFS);
+ String errorMsg = statusCode == HTTP_CONFLICT
+ ? ERR_SOFT_DELETE_NOT_SUPPORTED
+ : Integer.toString(statusCode);
AbfsRestOperationException ex = new AbfsRestOperationException(
- statusCode, null, Integer.toString(statusCode), null);
+ statusCode, null, errorMsg, null);
doThrow(ex).when(mockClient).getAclStatus(anyString(),
any(TracingContext.class));
if (isExceptionExpected) {
@@ -301,6 +309,99 @@ private void
ensureGetAclDetermineHnsStatusAccuratelyInternal(int statusCode,
.getAclStatus(anyString(), any(TracingContext.class));
}
+ /**
+ * Verify that for FNS accounts, the error code returned by the server
+ * is either HTTP 400 (Bad Request) or HTTP 409 (if soft-delete enabled).
This validates the expected
+ * server behavior when getAcl is called on FNS accounts.
+ */
+ @Test
+ public void testFNSAccountReturnsExpectedErrorCodes() throws Exception {
+ assumeHnsDisabled();
+ Configuration config = getConfigurationWithoutHnsConfig();
+
+ // Spy on the client to capture the actual exception thrown
+ try (AzureBlobFileSystem fs = (AzureBlobFileSystem)
FileSystem.newInstance(config)) {
+ AzureBlobFileSystemStore spyStore = Mockito.spy(fs.getAbfsStore());
+ AbfsClient spyClient =
Mockito.spy(spyStore.getClient(AbfsServiceType.DFS));
+
+ doReturn(spyClient).when(spyStore).getClient(AbfsServiceType.DFS);
+
+ // Force namespace to be unknown to trigger server call
+
spyStore.getAbfsConfiguration().setIsNamespaceEnabledAccountForTesting(Trilean.UNKNOWN);
+
+ // Capture any exception that might be thrown during getAclStatus
+ AbfsRestOperationException capturedException = null;
+ try {
+ spyClient.getAclStatus(AbfsHttpConstants.ROOT_PATH,
+ getTestTracingContext(fs, false));
+ } catch (AbfsRestOperationException ex) {
+ capturedException = ex;
+ }
+
+ // For FNS accounts, we expect either 400 or 409
+
+ // NOTE: 409 (soft-delete not supported error) would come explicitly
when we have
+ // the test account's soft-delete enabled. If soft-delete is disabled
for the test account,
+ // we should get 400 instead.
+ if (capturedException != null) {
+ int statusCode = capturedException.getStatusCode();
+ String errorMessage = capturedException.getMessage();
+
+ Assertions.assertThat(statusCode)
+ .describedAs("FNS account should return either 400 or 409 status
code")
+ .isIn(HTTP_BAD_REQUEST, HTTP_CONFLICT);
+
+ // If it's 409, verify it contains unsupported soft delete error
message
+ if (statusCode == HTTP_CONFLICT) {
+ Assertions.assertThat(errorMessage)
+ .describedAs("HTTP 409 response should contain soft delete
error message")
+ .contains(ERR_SOFT_DELETE_NOT_SUPPORTED);
+ }
+ }
+
+ // Verify that namespace is set to false regardless of which error was
returned
+ boolean isHnsEnabled =
spyStore.getIsNamespaceEnabled(getTestTracingContext(fs, false));
+ Assertions.assertThat(isHnsEnabled)
+ .describedAs("FNS account should have namespace disabled")
+ .isFalse();
+ }
+ }
+
+ /**
+ * Verify behavior when getAcl call fails with unexpected error codes
+ * (neither 400 nor 409). In such cases, namespace should be set to true
and exception
+ * should be propagated.
+ */
+ @Test
+ public void testErrorCodeSetsNamespaceToTrueAndThrowsException() throws
Exception {
+ // Create mock setup to simulate unexpected error code
+ AzureBlobFileSystemStore store =
Mockito.spy(getFileSystem().getAbfsStore());
+ AbfsClient mockClient = mock(AbfsClient.class);
+
store.getAbfsConfiguration().setIsNamespaceEnabledAccountForTesting(Trilean.UNKNOWN);
+ doReturn(mockClient).when(store).getClient(AbfsServiceType.DFS);
+
+ // Simulate 500 Internal Server Error (unexpected error code)
+ AbfsRestOperationException unexpectedException = new
AbfsRestOperationException(
+ HTTP_INTERNAL_ERROR, null, "Internal Server Error", null);
+ doThrow(unexpectedException).when(mockClient)
+ .getAclStatus(anyString(), any(TracingContext.class));
+
+ // Attempt to get namespace status should throw the exception
+ try {
+ store.getIsNamespaceEnabled(getTestTracingContext(getFileSystem(),
false));
+ Assertions.fail("Expected AbfsRestOperationException to be thrown");
+ } catch (AbfsRestOperationException ex) {
+ Assertions.assertThat(ex.getStatusCode())
+ .describedAs("Exception should have 500 status code")
+ .isEqualTo(HTTP_INTERNAL_ERROR);
+ }
+
+ // Even though exception was thrown, namespace should be set to true
+
Assertions.assertThat(store.getAbfsConfiguration().getIsNamespaceEnabledAccount())
+ .describedAs("Namespace should be set to TRUE for unexpected error
codes")
+ .isEqualTo(Trilean.TRUE);
+ }
+
@Test
public void testAccountSpecificConfig() throws Exception {
Configuration rawConfig = new Configuration();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]