Skip to content

Implemented Retries functionality #3307

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

Merged

Conversation

srsaikumarreddy
Copy link
Contributor

Implemented Retries functionality and wrote test cases.

Motivation and Context

Implemented Retries functionality and wrote test cases.

Modifications

Implemented Retries functionality and wrote test cases. Wrote new class for Retry Policy.

Testing

Wrote test cases and ran in IDE.

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Checklist

  • [X ] I have read the CONTRIBUTING document
  • [ X] Local run of mvn install succeeds
  • [ X] My code follows the code style of this project
  • [X ] My change requires a change to the Javadoc documentation
  • [ X] I have updated the Javadoc documentation accordingly
  • [ X] I have added tests to cover my changes
  • [ X] All new and existing tests passed
  • I have added a changelog entry. Adding a new entry must be accomplished by running the scripts/new-change script and following the instructions. Commit the new file created by the script in .changes/next-release with your changes.
  • My change is to implement 1.11 parity feature and I have updated LaunchChangelog

License

  • [X ] I confirm that this pull request can be released under the Apache 2 license

@srsaikumarreddy srsaikumarreddy requested a review from a team as a code owner July 18, 2022 05:41
Comment on lines +49 to +67
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Ec2MetadataRetryPolicy ec2MetadataRetryPolicy = (Ec2MetadataRetryPolicy) o;

if (!Objects.equals(numRetries, ec2MetadataRetryPolicy.numRetries)) {
return false;
}
return Objects.equals(backoffStrategy, ec2MetadataRetryPolicy.backoffStrategy);
}

@Override
public int hashCode() {

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add tests for equals and hashcode

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add EqualsVerifier method

Comment on lines 159 to 206
for (int tries = 1 ; tries <= retryPolicy.numRetries() ; tries ++) {

try {
String token = getToken();
URI uri = URI.create(endpoint + path);
HttpExecuteRequest httpExecuteRequest = REQUEST_MARSHALLER.createDataRequest(uri, SdkHttpMethod.GET, token,
tokenTtl);
HttpExecuteResponse response = httpClient.prepareRequest(httpExecuteRequest).call();
int statusCode = response.httpResponse().statusCode();
Optional<AbortableInputStream> responseBody = response.responseBody();

if (statusCode == HttpURLConnection.HTTP_OK && responseBody.isPresent()) {
abortableInputStream = responseBody.get();
String data = IoUtils.toUtf8String(abortableInputStream);
metadataResponse = new MetadataResponse(data);
} else if (statusCode == HttpURLConnection.HTTP_NOT_FOUND) {
throw SdkServiceException.builder()
.message("The requested metadata at path ( " + path + " ) is not found ").build();
} else if (statusCode == HttpURLConnection.HTTP_OK) {
throw SdkClientException.builder()
.message("Response body empty with Status Code " + statusCode).build();
} else {
throw SdkClientException.builder()
.message("Instance metadata service returned unexpected status code " + statusCode)
.build();
}
} catch (SdkServiceException sd) {
throw SdkServiceException.builder().message(sd.getMessage()).cause(sd).build();
} catch (IOException | SdkClientException io) {

log.warn("Received an IOException {0} ", io);
if (tries == 3) {
throw SdkClientException.builder().message("Unable to contact EC2 metadata service.").cause(io).build();
}

Duration backoffTime = retryPolicy.backoffStrategy()
.computeDelayBeforeNextRetry(RetryPolicyContext.builder()
.retriesAttempted(tries - 1)
.build());

try {
Thread.sleep(backoffTime.toMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} finally {
IoUtils.closeQuietly(abortableInputStream, log);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you break up this block(line 159 - 206 in case multi-line comment is not working) a little bit? It looks like it's doing retry, sending request and handling response. Ideally, a method should just do one thing.
Per Clean Code,

The first rule of functions is that they should be small.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted. Will modularize the code.

throw SdkServiceException.builder().message(sd.getMessage()).cause(sd).build();
} catch (IOException | SdkClientException io) {

log.warn("Received an IOException {0} ", io);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use SdkLogger?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted.

Comment on lines 127 to 128
thrown.expect(SdkClientException.class);
thrown.expectMessage("Unable to contact EC2 metadata service.");
Copy link
Contributor

Choose a reason for hiding this comment

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

I think thrown.expect is deprecated. Can we use assertThatThrownBy? You can search it in this project to find usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted.

@@ -196,4 +208,192 @@ public void getAmiId_onTokenResource_200() throws IOException {
WireMock.verify(getRequestedFor(urlPathEqualTo(AMI_ID_RESOURCE)).withHeader(TOKEN_HEADER, equalTo("some-token")));
}

@Test
public void get_AmiId_on_2nd_try_getToken() throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: the naming pattern for unit test code should be method_when_expectedBehavior, eg: getAmiId_failedOnce_shouldSuceedOnSecondAttempt

/**
* Interface for specifying a retry policy to use when evaluating whether or not a request should be retried , and the gap
* between each retry. The {@link #builder()} can be used to construct a retry policy with numRetries and backoffStrategy.
*<p></p>
Copy link
Contributor

Choose a reason for hiding this comment

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

https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#styleguide

If you have more than one paragraph in the doc comment, separate the paragraphs with a

paragraph tag, as shown.

</p> should be removed

throw SdkClientException.builder()
.message("Instance metadata service returned unexpected status code " + statusCode)
.build();
for (int tries = 1 ; tries <= retryPolicy.numRetries() ; tries ++) {
Copy link
Contributor

@zoewangg zoewangg Jul 20, 2022

Choose a reason for hiding this comment

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

The max number of attempts should be retryPolicy.numRetries() + 1, right?
Nit, s/tries/attempts

throw SdkClientException.builder()
.message("Instance metadata service returned unexpected status code " + statusCode)
.build();
doTokenExceptionHandling(statusCode);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, handleErrorResponse(statusCode)?

Comment on lines 273 to 274
throw SdkClientException.builder()
.message("Response body empty with Status Code " + statusCode).build();
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this ever happen?

* Method to return the number of retries allowed.
* @return The number of retries allowed.
*/

Copy link
Contributor

Choose a reason for hiding this comment

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

nit, empty line

try {
doRetryBehavior(tries, io);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think interruptedException is already handled in doRetryBehavior


}

private void doRetryBehavior(int tries , Exception io) throws InterruptedException {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, pauseBeforeRetryIfNeeded?
throws InterruptedException can be removed

SdkStandardLogger.REQUEST_LOGGER.warn(() -> "Received an IOException {0} ", io);

if (tries == retryPolicy.numRetries()) {
throw SdkClientException.builder().message("Unable to contact EC2 metadata service.").cause(io).build();
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add more details such as "exceeded max number of retries"

return metadataResponse;
}

private void doDataExceptionBehavior(int statusCode, String path) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: handleErrorResponse?

Instead of using exception as control flow, can we only throw exceptions for non-retryable cases? This way, the retryable cases will just get retried in the for loop

return metadataResponse;
}

private void doDataExceptionBehavior(int statusCode, String path) {
if (statusCode == 404) {
throw SdkServiceException.builder()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it may be worth introducing IMDS specific exceptions, something like ImdsException. I understand this may be out of scope, but could you add a TODO somewhere

* between each retry. The {@link #builder()} can be used to construct a retry policy with numRetries and backoffStrategy.
* When using the {@link #builder()} the SDK will use default values for fields that are not provided.A custom BackoffStrategy
* can be used to construct a policy or a default {@link BackoffStrategy} is used .
* <p></p>
Copy link
Contributor

Choose a reason for hiding this comment

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

To clarify, we need to have <p> but not </p>. Please check out https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#styleguide

Comment on lines 182 to 183
} catch (SdkClientException sd) {
throw SdkClientException.builder().message(sd.getMessage()).cause(sd).build();
Copy link
Contributor

Choose a reason for hiding this comment

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

We should not catch and re-throw the same exception. No catch is needed for SdkClientException

} catch (SdkClientException sd) {
throw SdkClientException.builder().message(sd.getMessage()).cause(sd).build();
} catch (IOException io) {
SdkStandardLogger.REQUEST_LOGGER.warn(() -> "Received an IOException {0} ", io);
Copy link
Contributor

Choose a reason for hiding this comment

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

I meant using this Logger https://github.com/aws/aws-sdk-java-v2/blob/master/utils/src/main/java/software/amazon/awssdk/utils/Logger.java instead of LoggerFactory, not the sdk request logger

}
handleException(statusCode, path);
}
pauseBeforeRetryIfNeeded(tries);
Copy link
Contributor

Choose a reason for hiding this comment

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

It may be cleaner if we move pauseBeforeRetryIfNeeded(tries); to after finally block so that we can remove pauseBeforeRetryIfNeeded(tries); on line 186

@@ -253,10 +247,10 @@ private Optional<String> getToken() throws IOException {
}
handleErrorResponse(statusCode);
} catch (IOException e) {
SdkStandardLogger.REQUEST_LOGGER.warn(() -> "Received an IOException {0} ", e);
log.warn(() -> "Received an IOException {0} ", e);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: {0} should be removed, same as the other log statement

@zoewangg zoewangg enabled auto-merge (squash) July 21, 2022 19:41
@sonarqubecloud
Copy link

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 16 Code Smells

30.5% 30.5% Coverage
2.8% 2.8% Duplication

@zoewangg zoewangg merged commit 092a580 into aws:feature/master/imds Jul 21, 2022
aws-sdk-java-automation added a commit that referenced this pull request Oct 3, 2024
…ac935f741

Pull request: release <- staging/ffbc8125-0078-4c6b-a206-909ac935f741
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants