Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3f6386a
Add retry for HTTP 429 and HTTP 410
anmolasrani123 Jan 4, 2023
56c6f7d
Add retry for HTTP 429 and HTTP 410
anmolasrani123 Jan 4, 2023
2c25b39
Add retry for HTTP 429 and HTTP 410
anmolasrani123 Jan 4, 2023
88e51b6
Added tests to verify retry
anmolasrani123 Jan 5, 2023
3be0b00
Removing unused imports
anmolasrani123 Jan 5, 2023
e04011c
Adding negative tests
anmolasrani123 Jan 5, 2023
6283410
Merge branch 'trunk' into HADOOP-17377
anmolanmol1234 Apr 3, 2023
cf73a70
Merge branch 'apache:trunk' into HADOOP-17377
anmolanmol1234 Aug 22, 2023
e6740a3
Resolving PR comments
anmolanmol1234 Aug 22, 2023
0688809
Mockito fix
anmolanmol1234 Aug 22, 2023
115e88a
Added in other PR
anmolanmol1234 Aug 23, 2023
fe15f44
Removed
anmolanmol1234 Aug 23, 2023
01ec633
Removed new version changes
anmolanmol1234 Aug 23, 2023
4faa92d
Merge branch 'HADOOP-17377' of https://github.com/anmolanmol1234/hado…
anmolanmol1234 Aug 23, 2023
24c0483
Reverted
anmolanmol1234 Aug 23, 2023
99e66fa
Reverted mockito changes
anmolanmol1234 Aug 23, 2023
8b5e883
Merge branch 'apache:trunk' into HADOOP-17377
anmolanmol1234 Aug 23, 2023
1472eb8
POM
anmolanmol1234 Aug 23, 2023
7ba573f
Merge branch 'apache:trunk' into HADOOP-17377
anmolanmol1234 Aug 29, 2023
3f80d00
Merge branch 'HADOOP-17377' of https://github.com/anmolanmol1234/hado…
anmolanmol1234 Aug 30, 2023
462a3b6
Checkstyle fix
anmolanmol1234 Aug 30, 2023
78329de
PR comments
anmolanmol1234 Aug 31, 2023
240965d
Correct default value for backoff interval
anmolanmol1234 Nov 15, 2023
b0563a1
Merge branch 'apache:trunk' into HADOOP-17377
anmolanmol1234 Nov 15, 2023
225541e
Merge branch 'HADOOP-17377' of https://github.com/anmolanmol1234/hado…
anmolanmol1234 Nov 15, 2023
c7d4671
Modify tests and backmerge
anmolanmol1234 Oct 23, 2025
588ffb9
Checkstyle and mockito fix
anmolanmol1234 Oct 23, 2025
fd26066
fix config value
anmolanmol1234 Oct 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions hadoop-tools/hadoop-azure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,23 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, hadoop-project defines the version, and through properties. revert this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, cut this now; the version in hadoop project is the one you now expect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken

<scope>test</scope>
</dependency>

<dependency>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed? because its not in the base project pom.

I would rather this PR doesn't need that mockito upgrade as mockito upgrades are always a painful piece of work which never gets backported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed dependency

<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.11.0</version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is new to hadoop, declare it in hadoop-project/pom.xml, with versions and exclusions, then declare here without those

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed dependency

<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-minikdc</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public String getRequestId() {
return this.requestId;
}

protected HttpException(
public HttpException(
final int httpErrorCode,
final String requestId,
final String message,
Expand Down Expand Up @@ -341,7 +341,7 @@ private static boolean isRecoverableFailure(IOException e) {
|| e instanceof FileNotFoundException);
}

private static AzureADToken getTokenSingleCall(String authEndpoint,
public static AzureADToken getTokenSingleCall(String authEndpoint,
String payload, Hashtable<String, String> headers, String httpMethod,
boolean isMsi)
throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ public class ExponentialRetryPolicy {
*/
private static final double MAX_RANDOM_RATIO = 1.2;

/**
* Qualifies for retry based on
* https://learn.microsoft.com/en-us/azure/active-directory/
* managed-identities-azure-resources/how-to-use-vm-token#error-handling
*/
private static final int HTTP_TOO_MANY_REQUESTS = 429;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make public and refer from tests, maybe put in a different file for this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken


/**
* Holds the random number generator used to calculate randomized backoff intervals
*/
Expand Down Expand Up @@ -123,6 +130,9 @@ public ExponentialRetryPolicy(final int retryCount, final int minBackoff, final
* and the current strategy. The valid http status code lies in the range of 1xx-5xx.
* But an invalid status code might be set due to network or timeout kind of issues.
* Such invalid status code also qualify for retry.
* HTTP status code 410 qualifies for retry based on
* https://docs.microsoft.com/en-in/azure/virtual-machines/linux/
* instance-metadata-service?tabs=windows#errors-and-debugging
*
* @param retryCount The current retry attempt count.
* @param statusCode The status code of the response, or -1 for socket error.
Expand All @@ -132,6 +142,8 @@ public boolean shouldRetry(final int retryCount, final int statusCode) {
return retryCount < this.retryCount
&& (statusCode < HTTP_CONTINUE
|| statusCode == HttpURLConnection.HTTP_CLIENT_TIMEOUT
|| statusCode == HttpURLConnection.HTTP_GONE
|| statusCode == HTTP_TOO_MANY_REQUESTS
|| (statusCode >= HttpURLConnection.HTTP_INTERNAL_ERROR
&& statusCode != HttpURLConnection.HTTP_NOT_IMPLEMENTED
&& statusCode != HttpURLConnection.HTTP_VERSION));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,22 @@
package org.apache.hadoop.fs.azurebfs;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.util.Date;

import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.fs.azurebfs.oauth2.AccessTokenProvider;
import org.apache.hadoop.fs.azurebfs.oauth2.AzureADAuthenticator;
import org.apache.hadoop.fs.azurebfs.oauth2.AzureADToken;
import org.apache.hadoop.fs.azurebfs.oauth2.MsiTokenProvider;
import org.apache.hadoop.fs.azurebfs.services.ExponentialRetryPolicy;

import static org.apache.hadoop.fs.azurebfs.constants.FileSystemConfigurations.DEFAULT_AZURE_OAUTH_TOKEN_FETCH_RETRY_MAX_ATTEMPTS;
import static org.apache.hadoop.test.LambdaTestUtils.intercept;
import static org.junit.Assume.assumeThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
Expand All @@ -40,13 +47,16 @@
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_OAUTH_MSI_AUTHORITY;
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_OAUTH_MSI_ENDPOINT;
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_OAUTH_MSI_TENANT;
import static org.mockito.Mockito.times;

/**
* Test MsiTokenProvider.
*/
public final class ITestAbfsMsiTokenProvider
extends AbstractAbfsIntegrationTest {

private static final int HTTP_TOO_MANY_REQUESTS = 429;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refer to the value in the src/main code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken


public ITestAbfsMsiTokenProvider() throws Exception {
super();
}
Expand Down Expand Up @@ -90,4 +100,109 @@ private String getTrimmedPasswordString(AbfsConfiguration conf, String key,
return value.trim();
}

/**
* Test to verify that token fetch is retried for throttling errors (too many requests 429).
* @throws Exception
*/
@Test
public void testRetryForThrottling() throws Exception {
AbfsConfiguration conf = getConfiguration();

// Exception to be thrown with throttling error code 429.
AzureADAuthenticator.HttpException httpException
= new AzureADAuthenticator.HttpException(HTTP_TOO_MANY_REQUESTS,
"abc", "abc", "abc", "abc", "abc");

String tenantGuid = "abcd";
String clientId = "abcd";
String authEndpoint = getTrimmedPasswordString(conf,
FS_AZURE_ACCOUNT_OAUTH_MSI_ENDPOINT,
DEFAULT_FS_AZURE_ACCOUNT_OAUTH_MSI_ENDPOINT);
String authority = getTrimmedPasswordString(conf,
FS_AZURE_ACCOUNT_OAUTH_MSI_AUTHORITY,
DEFAULT_FS_AZURE_ACCOUNT_OAUTH_MSI_AUTHORITY);

// Mock the getTokenSingleCall to throw exception so the retry logic comes into place.
try (MockedStatic<AzureADAuthenticator> adAuthenticator = Mockito.mockStatic(
AzureADAuthenticator.class, Mockito.CALLS_REAL_METHODS)) {
adAuthenticator.when(
() -> AzureADAuthenticator.getTokenSingleCall(Mockito.anyString(),
Mockito.anyString(), Mockito.any(), Mockito.anyString(),
Mockito.anyBoolean())).thenThrow(httpException);

// Mock the tokenFetchRetryPolicy to verify retries.
ExponentialRetryPolicy exponentialRetryPolicy = Mockito.spy(
conf.getOauthTokenFetchRetryPolicy());
Field tokenFetchRetryPolicy = AzureADAuthenticator.class.getDeclaredField(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this kind of stuff is trouble as it makes maintenance a nightmare; you can't see where the field is access, all you have is a mocking test failing.

proposed: add a static setter to AzureADAuthenticator; mark as @VisibleForTesting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken

"tokenFetchRetryPolicy");
tokenFetchRetryPolicy.setAccessible(true);
tokenFetchRetryPolicy.set(ExponentialRetryPolicy.class,
exponentialRetryPolicy);

AccessTokenProvider tokenProvider = new MsiTokenProvider(authEndpoint,
tenantGuid, clientId, authority);
AzureADToken token = null;
intercept(AzureADAuthenticator.HttpException.class,
tokenProvider::getToken);

// If the status code doesn't qualify for retry shouldRetry returns false and the loop ends.
// It being called multiple times verifies that the retry was done for the throttling status code 429.
Mockito.verify(exponentialRetryPolicy,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so ExponentialRetryPolicy.getRetryCount() is there to let you pass a non-mocked policy in and then assert on it. how about using that here? it probably needs making the accessors public, rather than package scoped, but that's all. The less use we make of mockito, the less things will break with every mockito upgrade

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

taken

times(DEFAULT_AZURE_OAUTH_TOKEN_FETCH_RETRY_MAX_ATTEMPTS + 1))
.shouldRetry(Mockito.anyInt(), Mockito.anyInt());
}
}

/**
* Test to verify that token fetch is not retried for resource not found errors.
* @throws Exception
*/
@Test
public void testNoRetryForResourceNotFound() throws Exception {
AbfsConfiguration conf = getConfiguration();

// Exception to be thrown 404 error code.
AzureADAuthenticator.HttpException httpException
= new AzureADAuthenticator.HttpException(HttpURLConnection.HTTP_NOT_FOUND,
"abc", "abc", "abc", "abc", "abc");

String tenantGuid = "abcd";
String clientId = "abcd";
String authEndpoint = getTrimmedPasswordString(conf,
FS_AZURE_ACCOUNT_OAUTH_MSI_ENDPOINT,
DEFAULT_FS_AZURE_ACCOUNT_OAUTH_MSI_ENDPOINT);
String authority = getTrimmedPasswordString(conf,
FS_AZURE_ACCOUNT_OAUTH_MSI_AUTHORITY,
DEFAULT_FS_AZURE_ACCOUNT_OAUTH_MSI_AUTHORITY);

// Mock the getTokenSingleCall to throw exception.
try (MockedStatic<AzureADAuthenticator> adAuthenticator = Mockito.mockStatic(
AzureADAuthenticator.class, Mockito.CALLS_REAL_METHODS)) {
adAuthenticator.when(
() -> AzureADAuthenticator.getTokenSingleCall(Mockito.anyString(),
Mockito.anyString(), Mockito.any(), Mockito.anyString(),
Mockito.anyBoolean())).thenThrow(httpException);

// Mock the tokenFetchRetryPolicy to verify no retries.
ExponentialRetryPolicy exponentialRetryPolicy = Mockito.spy(
conf.getOauthTokenFetchRetryPolicy());
Field tokenFetchRetryPolicy = AzureADAuthenticator.class.getDeclaredField(
"tokenFetchRetryPolicy");
tokenFetchRetryPolicy.setAccessible(true);
tokenFetchRetryPolicy.set(ExponentialRetryPolicy.class,
exponentialRetryPolicy);

AccessTokenProvider tokenProvider = new MsiTokenProvider(authEndpoint,
tenantGuid, clientId, authority);
AzureADToken token = null;
intercept(AzureADAuthenticator.HttpException.class,
tokenProvider::getToken);

// If the status code doesn't qualify for retry shouldRetry returns false and the loop ends.
// It being called only once verifies that retry doesn't come into place..
Mockito.verify(exponentialRetryPolicy,
times(1))
.shouldRetry(Mockito.anyInt(), Mockito.anyInt());
}
}
}