-
Notifications
You must be signed in to change notification settings - Fork 911
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
Implemented Retries functionality #3307
Conversation
core/imds/src/main/java/software/amazon/awssdk/imds/Ec2MetadataRetryPolicy.java
Outdated
Show resolved
Hide resolved
core/imds/src/main/java/software/amazon/awssdk/imds/Ec2MetadataRetryPolicy.java
Outdated
Show resolved
Hide resolved
@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() { | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add EqualsVerifier method
core/imds/src/main/java/software/amazon/awssdk/imds/Ec2MetadataRetryPolicy.java
Show resolved
Hide resolved
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); | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java
Outdated
Show resolved
Hide resolved
throw SdkServiceException.builder().message(sd.getMessage()).cause(sd).build(); | ||
} catch (IOException | SdkClientException io) { | ||
|
||
log.warn("Received an IOException {0} ", io); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use SdkLogger
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted.
core/imds/src/main/java/software/amazon/awssdk/imds/internal/DefaultEc2Metadata.java
Outdated
Show resolved
Hide resolved
thrown.expect(SdkClientException.class); | ||
thrown.expectMessage("Unable to contact EC2 metadata service."); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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 ++) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, handleErrorResponse(statusCode)
?
throw SdkClientException.builder() | ||
.message("Response body empty with Status Code " + statusCode).build(); |
There was a problem hiding this comment.
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. | ||
*/ | ||
|
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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
} catch (SdkClientException sd) { | ||
throw SdkClientException.builder().message(sd.getMessage()).cause(sd).build(); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
SonarCloud Quality Gate failed. |
…ac935f741 Pull request: release <- staging/ffbc8125-0078-4c6b-a206-909ac935f741
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
Checklist
mvn install
succeedsscripts/new-change
script and following the instructions. Commit the new file created by the script in.changes/next-release
with your changes.License